Here is a bit of a hack that will allow bijective mapping between a member and the struct, doing mere pointer arithmetic on constants at run time. static_assert( std::is_pod<T>::value, "POD needed!" );
might be a good idea:
template<typename T, typename N, N T::* member>
struct member_access {
static N& ContainerToMember( T& t ) { return t.*member; }
static N const& ContainerToMember( T const& t ) { return t.*member; }
template<T* ptr=nullptr>
static constexpr std::size_t offset_of() {
return reinterpret_cast<std::size_t>(reinterpret_cast<char*>(&((ptr)->*member)));
}
static T& MemberToContainer( N& n ) {
return *reinterpret_cast<T*>(reinterpret_cast<char*>(&n)-offset_of());
}
static T const& MemberToContainer( N const& n ) {
return *reinterpret_cast<T const*>(reinterpret_cast<char const*>(&n)-offset_of());
}
};
which gets rid of your offsetof
requirement.
Privacy is hard. There is the explicit specialization side effect hole in the privacy type system, but it results in runtime member pointers, and we want compile time values.
A better approach might be to stuff the linked lists into some metaprogramming:
template< typename T, unsigned index >
struct list_node:list_node<T, index-1> {
list_node* next;
list_node* prev;
T* self() { static_cast<T*>(this); }
T const* self() const { static_cast<T const*>(this); }
};
template<typename T>
struct list_node<T, 0> {
list_node* next;
list_node* prev;
T* self() { static_cast<T*>(this); }
T const* self() const { static_cast<T const*>(this); }
};
template<typename T, unsigned N>
struct listable : list_node<T, N-1> {};
template<typename T>
struct listable<T, 0> {};
then simply create your listable data like:
struct data: listable<data, 10> {
std::string s;
};
We can then do this:
template<unsigned N, typename T>
list_node<T, N>& get_node( T& t ) { return t; }
template<unsigned N, typename T>
list_node<T, N> const& get_node( T const& t ) { return t; }
template<unsigned N, typename T>
T& get_data( list_node<T, N>& n ) { return *n.self(); }
template<unsigned N, typename T>
T const& get_data( list_node<T, N> const& n ) { return *n.self(); }
which is all legal C++11.
template<typename T, unsigned index>
struct linked_list {
typedef list_node<T, index> node;
};
which is sort of nice. Downside: the list_node
for each linked_list
is a different type. Getting around that is a bit tricky.
We could stay in the realm of defined behavior by pushing all of the next
and prev
pointers to a POD array beyond the root of the topmost list_node
. We then work with pointers into that. We can legally do pointer arithmetic within that array, and a pointer to the first element of the most ancestral struct can be legally reinterpreted to being a pointer to that struct.
From that, we can static_cast
down to a T*
directly (which is probably no binary change in the pointer value at all).
struct link { link* next; link* prev; };
template<typename T, unsigned N>
struct listable {
std::array<link, N> links;
static listable* array_to_list( std::array<link, N>* a ) { return reinterpret_cast<listable>(a); }
template<unsigned I>
static listable* link_to_list( link* l ) {
return array_to_list( reinterpret_cast<std::array<link, N>>(l - I) );
}
template<unsigned I>
link* get_link() { return &links[I]; }
T* listable_to_T() {
static_assert( std::is_base_of< listable, T >::value, "CRTP vailure" );
return static_cast<T*>(this);
}
template<unsigned I>
static T* link_to_T(link* l) {
return link_to_list<I>(l)->listable_to_T()
}
};
which at most requests that a pointer-to-first-element and a pointer-to-array be layout compatible (I hope so!)
Again, under this model, your link listable data does:
struct Bob : listable<Bob, 10> {};
to say that there are 10 embedded lists in Bob
. Converting from a Bob
to a link*
involves invoking bob.get_link<5>()
, while converting from a link*
to a Bob*
is listable<Bob, 10>::link_to_T<5>( l )
, assuming we are talking about sub-list 5.