Is Pointer-to- " inner struct" member forbidden?
Solution 1:
Yes, it is forbidden. You are not the first to come up with this perfectly logical idea. In my opinion this is one of the obvious "bugs"/"omissions" in the specification of pointers-to-members in C++, but apparently the committee has no interest in developing the specification of pointers-to-members any further (as is the case with most of the "low-level" language features).
Note that everything necessary to implement the feature in already there, in the language. A pointer to a-data-member-of-a-member is in no way different from a pointer to an immediate data member. The only thing that's missing is the syntax to initialize such a pointer. However, the committee is apparently not interested in introducing such a syntax.
From the pure formal logic point of view, this should have been allowed in C++
struct Inner {
int i;
int j[10];
};
struct Outer {
int i;
int j[10];
Inner inner;
};
Outer o;
int Outer::*p;
p = &Outer::i; // OK
o.*p = 0; // sets `o.i` to 0
p = &Outer::inner.i; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.i` to 0
p = &Outer::j[0]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[0]` to 0
// This could have been used to implement something akin to "array type decay"
// for member pointers
p = &Outer::j[3]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[3]` to 0
p = &Outer::inner.j[5]; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.j[5]` to 0
A typical implementation of pointer-to-data-member is nothing more than just an byte-offset of the member from the beginning of the enclosing object. Since all members (immediate and members of members) are ultimately laid out sequentially in memory, members of members can also be identified by a specific offset value. This is what I mean when I say that the inner workings of this feature are already fully implemented, all that is needed is the initialization syntax.
In C language this functionality is emulated by explicit offsets obtained through the standard offsetof
macro. And in C I can obtain offsetof(Outer, inner.i)
and offsetof(Outer, j[2])
. Unfortunately, this capability is not reflected in C++ pointers-to-data-members.
Solution 2:
The InnerStruct you care about happens to be contained in an instance of a MyStruct, but that doesn't affect how you get a pointer to the member of the InnerStruct.
bool InnerStruct::* toto2 = &InnerStruct::c;
Edit: Rereading your question, I'm guessing you want to define a pointer to member of the outer struct, and have it point directly at a member of the inner struct. That's simply not allowed. To get to the member of the inner struct that's contained in the outer struct, you'd have to create a pointer to the inner struct itselft, then to its member. To use it, you'd dereference both pointers to members:
// Pointer to inner member of MyStruct:
InnerStruct MyStruct::* toto = &MyStruct::inner;
// Pointer to c member of InnerStruct:
bool InnerStruct::* toto2 = &InnerStruct::c;
// Dereference both to get to the actual bool:
bool x = mystruct.*toto.*toto2;
Solution 3:
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!