As stated in AnT's answer, this seems indeed to be an omission of the standard and the lacking of a correct syntax for expressing what you wish to do. A colleague of mine came up against this problem the other day and cited your question and its answer as proof that it couldn't be done. Well, I like a challenge, and yes, it can be done... but its not pretty.
First, it is important to realise that a pointer to member is basically an offset from a pointer to struct[1]. The language does have an offsetof operator that is suspiciously similar to this, and interestingly enough gives us the expressiveness required to do what we want.
The problem we hit immediately is that C++ prohibits casting member pointers. Well, almost... we have the union cast up our sleeves for this. As I said, this is not pretty!
Last of all, we also need to know the correct pointer type to cast to.
So without further ado, here is the code (tested on gcc and clang):
template <typename C, typename T, /*auto*/size_t P>
union MemberPointerImpl final {
template <typename U> struct Helper
{ using Type = U C::*; };
template <typename U> struct Helper<U&>
{ using Type = U C::*; };
using MemberPointer = typename Helper<T>::Type;
MemberPointer o;
size_t i = P; // we can't do "auto i" - argh!
static_assert(sizeof(i) == sizeof(o));
};
#define MEMBER_POINTER(C, M) \
((MemberPointerImpl<__typeof__(C), \
decltype(((__typeof__(C)*)nullptr)->M), \
__builtin_offsetof(__typeof__(C), M) \
>{ }).o)
Let's look at the macro MEMBER_POINTER
first. It takes two arguments. The first, C
, is the struct which will be the base for the member pointer. Wrapping it in __typeof__
is not strictly necessary, but allows for passing either a type or a variable. The second argument, M
, provides an expression to the member of which we want the pointer.
The MEMBER_POINTER
macro extracts two additional pieces of information from these arguments and passes them as parameters to the MemberPointerImpl
template union. The first piece is the type of the member pointed to. This is done by constructing an expression using a null pointer upon which we use decltype
. The second piece is the offset from the base struct to the member in question.
Inside MemberPointerImpl
we need to construct the MemberPointer
type which will be what is returned by the macro. This is done with a helper struct that removes references which unhelpfully occur if our member is an array element, allowing support for this too. It also enables gcc and clang to give us a nice fully expanded type in diagnostics if we assign the returned value to a variable with mismatched type.
So, to use MEMBER_POINTER
, simply change your code from:
bool MyStruct::* toto = &MyStruct::inner.c;
to:
bool MyStruct::* toto = MEMBER_POINTER(MyStruct, inner.c);
[1] Ok, caveat moment: this may not be true for all architectures/compilers, so portable code-writers look away now!
(mystruct.*(&MyStruct::inner.c)) = true
and have it be identical tomystruct.inner.c = true
. – Basidiumbool MyStruct::*
to point toMyStruct::InnerStruct
members, as in the OP's example. However, the language does not allow that. There's simply no syntax for that. – OswaldC
is not always constant, but depends on what most derived object it is contained in. But once you took the member pointer, the offset is known (say, if you do(int C::*)&C::a
hitting anA
member, the vtable or similar is used to get a correct offset starting fromC
), and when converted to a member pointer to another class, offset adjustments with the help of a vtable or similar can be done. – Basidiumint A::*
toint C::*
is forbidden by the Standard - i think because of this complication with virtual bases. I always thought such a thing is valid, but apparently, you cannot do this cast, because of these complications. – Basidium