On Fri, 5 Nov 2021 at 20:27, Lénárd Szolnoki via Std-Proposals <std-proposals@lists.isocpp.org> 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.
If it's a standard library function, there's always an implementation: __builtin_parent_of_member

That being said, isn't this undefined behaviour anyways for anything other than the first member of a standard-layout type? Either that or the definition of reachability would have to be extended to include pointers to any subobject.
 
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@lists.isocpp.org>
Sent: November 5, 2021 11:09:14 PM GMT+00:00
To: sotrdg sotrdg via Std-Proposals <std-proposals@lists.isocpp.org>
Cc: Michael Scire <sciresm@gmail.com>
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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals