C++ Logo

std-proposals

Advanced search

Re: std::parent_of_member

From: Brian Bi <bbi5291_at_[hidden]>
Date: Fri, 5 Nov 2021 20:12:49 -0400
`reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<ParentType
*>(0)->*member_ptr))` is UB. You have to use the magic `offsetof` macro.
P1278 <http://wg21.link/p1278> proposes to add a similar non-macro facility.

The problem is how to then perform the pointer arithmetic and convert the
result into a pointer to the parent type. This cannot be done in user code
in C++17, so even at runtime, it would require compiler magic. If P1839
<http://wg21.link/P1839> gets accepted, it will be possible to implement it
in user code.

If you decide to write a formal proposal, you should take into account
these two possibilities: in the world where P1839 doesn't get accepted, we
clearly need a magic library function like this, to replace the
`offsetof`-using code which became UB in C++17. This case is (I think) easy
to make, considering that getting a pointer to the parent object is one of
the main use cases of `offsetof`. In the world where P1839 does get
accepted, I think it would be important to explain some situations where
you need this functionality *at compile time*. Most people will probably
think "why would I need an intrusive list at compile time?"

On Fri, Nov 5, 2021 at 7:10 PM Michael Scire via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> 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
>


-- 
*Brian Bi*

Received on 2021-11-05 19:13:04