C++ Logo

std-proposals

Advanced search

Re: std::parent_of_member

From: Michael Scire <sciresm_at_[hidden]>
Date: Fri, 5 Nov 2021 17:33:57 -0700
The standard currently supports changing the active member of a union
during constant evaluation -- this implies, to avoid UB, that conforming
implementations must be able to tell whether an access to a reference is
really an access to a union member, so that it can be detected whether a
reference points to a union member made inactive due to changing the active
member, right?

My understanding is that as such, then, that it should be reasonable to ask
an implementation to know whether an access to a reference is really an
access to a structure member, and correspondingly be able to get a
reference to the parent. And at runtime, get-parent could just be the usual
offsetof subtraction.

Maybe I'm mistaken there, but that's my understanding of how things are and
how I'd like them to be.

"then you can do multiple inheritance and downcast" yes, this is what I do
for the BaseNode trait, but this does not allow specifying where in the
layout the member node goes, and has some other downsides.

On Fri, Nov 5, 2021 at 5:27 PM Lénárd Szolnoki via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Hi,
>
> I don't think a non-UB implementation is possible here. I have the
> impression that the pointer to the parent type is intentionally unreachable
> and in theory compilers are allowed to optimize based on that.
>
> Having said that I never saw a compiler optimize based on that, although I
> experimented in the area.
>
> Example:
>
> struct A {
> int i;
> int j;
> };
>
> void foo(int*);
>
> void bar() {
> A a{1,2};
> foo(&a.j);
> return a.i; //can be optimized to return 1;
> }
>
> Also notable that if you want something like this and you control the
> types then you can do multiple inheritance and downcast.
>
> Cheers,
> Lénárd
>
> ------------------------------
> *From:* Michael Scire via Std-Proposals <std-proposals_at_[hidden]>
> *Sent:* November 5, 2021 11:09:14 PM GMT+00:00
> *To:* sotrdg sotrdg via Std-Proposals <std-proposals_at_[hidden]>
> *Cc:* Michael Scire <sciresm_at_[hidden]>
> *Subject:* [std-proposals] std::parent_of_member
>
> Hi,
>
> I wanted to propose a utility for working with intrusive members at
> compile-time.
>
> Tentatively, I would propose
>
> template<typename ParentType, typename MemberType> // Could we have
> template<auto MemberPtr> with ParentType and MemberType deduced?
> constexpr ParentType &parent_of_member(MemberType ParentType::*member_ptr,
> MemberType &member) {
> if (std::is_constant_evaluated()) {
> // Compiler magic needed here
> } else {
> const uintptr_t member_address =
> reinterpret_cast<uintptr_t>(std::addressof(member));
> const uintptr_t parent_address = member_address -
> reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<ParentType
> *>(0)->*member_ptr));
>
> return *reinterpret_cast<ParentType *>(parent_address);
> }
> }
>
> Where parent_of_member would take in a pointer-to-member and a reference
> to a member, and return a reference to the parent object if the reference
> really is to the relevant member, and be undefined behavior otherwise.
>
> The example run-time implementation above may not avoid undefined
> behavior, but I do believe it is possible to implement without undefined
> behavior at runtime. Please correct me if I'm wrong.
>
> However, the reinterpret_casts/arithmetic above cannot be done at
> compile-time, and so compiler-assistance would be necessary.
>
> I think the above should be implementable -- both clang and gcc use
> symbolic representations of objects at compile-time, and so the compiler
> should have the ability to verify that the member reference is a member
> reference in truth.
>
>
> The above function is particularly useful for e.g. the case of intrusive
> lists. My use case looks something like:
>
> struct IntrusiveListNode {
> IntrusiveListNode *prev;
> IntrusiveListNode *next;
>
> // ...
> };
>
> class IntrusiveListImpl {
> // Common list management object for IntrusiveListNode, non-templated
> and shared for all types using intrusive list nodes.
> // ...
> };
>
> template<class Traits>
> class IntrusiveList {
> // Thin wrapper around IntrusiveListImpl for a specific intrusive list
> member.
> }
>
> template<auto MemberPtr, class Derived = /* Get ParentType from MemberPtr
> */>
> class IntrusiveListMemberTraits;
>
> template<class ParentType, IntrusiveListNode ParentType::*MemberPtr, class
> Derived>
> class IntrusiveListMemberTraits<MemberPtr, Derived> {
> public:
> using ListType = IntrusiveList<IntrusiveListMemberTraits>;
> public:
> static constexpr IntrusiveListNode &GetNode(Derived &parent) {
> return parent.*MemberPtr;
> }
>
> static constexpr IntrusiveListNode const &GetNode(Derived const
> &parent) {
> return parent.*MemberPtr;
> }
>
> static constexpr Derived &GetParent(IntrusiveListNode &node) {
> return static_cast<Derived &>(std::parent_of_member(MemberPtr,
> node));
> }
>
> static constexpr Derived const &GetParent(IntrusiveListNode const
> &node) {
> return static_cast<const Derived
> &>(std::parent_of_member(MemberPtr, node));
> }
> };
>
>
> struct ExampleTypeWithNode {
> int data;
> IntrusiveListNode node;
> };
>
> using ExampleTypeList =
> IntrusiveListMemberTraits<&ExampleTypeWithNode::node>::ListType;
>
> At which point all works as expected, with ExampleTypeList being a list of
> ExampleTypeWithNode using the member `node`.
>
> I have all this implemented and working at run-time, with two primary
> "Traits" variants -- one when node is a class member, and one where the
> class derives from IntrusiveListBaseNode<T>.
>
> When the intrusive node is a base of the class, GetParent/GetNode can be
> implemented at compile-time (static_cast<Derived
> &>(static_cast<IntrusiveListBaseNode<ParentType> &>(node))), and the code
> functions.
>
> It would be very useful to me to be able to have both variants of
> intrusive list function at compile-time, instead of only one -- and there
> are other intrusive members which would benefit greatly from this.
>
> I also think that a standard-blessed way to use a pointer-to-member to go
> from member to parent would be useful, rather than everybody using
> reinterpret_casts/arithmetic.
>
> My only thought is that it might be nice if the syntax could be e.g.
> std::parent_of_member<MemberPtr>(member), rather than
> std::parent_of_member(MemberPtr, member);
>
> Thoughts?
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2021-11-05 19:34:14