Having said that I never saw a compiler optimize based on that, although I experimented in the area.
Also notable that if you want something like this and you control the types then you can do multiple inheritance and downcast.
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?