C++ Logo

std-proposals

Advanced search

Re: std::parent_of_member

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Sat, 06 Nov 2021 00:26:17 +0000
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


-------- Original Message --------
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?

Received on 2021-11-05 19:26:29