Is Pointer-to- " inner struct" member forbidden?
Asked Answered
R

3

24

i have a nested struct and i'd like to have a pointer-to-member to one of the nested member:

is it legal?

struct InnerStruct
{
    bool c;
};
struct MyStruct {
    bool t;
    bool b;
    InnerStruct inner;
}; 

this:

MyStruct mystruct;
//...
bool MyStruct::* toto = &MyStruct::b;

is ok but:

bool MyStruct::* toto = &MyStruct::inner.c;

is not. any idea?

thanks

Here are some details Yes it is &MyStruct::b and not mystruct::b; The code is from a custom RTTI/Property system. For each specified class we keep an array of "Property", including a Ptr-to-member It is used like this:

//somewhere else in code...
( myBaseClassWithCustomRTTIPointer)->* toto = true;
Resurrection answered 18/12, 2009 at 18:20 Comment(11)
I'm confused: do you mean to be referring to MyStruct::b and MyStruct::inner.c vs mystruct.b and mystruct.inner.c in your references? And what is your goal with specifying MyStruct::* in your two assignments?Papilionaceous
I think he wants to do (mystruct.*(&MyStruct::inner.c)) = true and have it be identical to mystruct.inner.c = true.Basidium
@Joe: A pointer-to-data-member in C++ is nothing else than a language-level iomplementation of the notion of "offset". I.e. internally a pointer-to-data-member is nothing more than just an offset of the member from the beginning of the entire object. Obviously, members of subobjects also have their own offsets in the entire object, so it would be perfectly logical to allow pointers of type bool MyStruct::* to point to MyStruct::InnerStruct members, as in the OP's example. However, the language does not allow that. There's simply no syntax for that.Oswald
thank you all for your fast and precise answers. I think ptr-to-member is a bit more complex than just offset from base class, as it can handle multiple and virtual inheritance. I might change my property system so it uses offsetof and forbid "dreaded diamond"Resurrection
@benoitj: No, you are overcomplicating things. Issues with multiple inheritance, virtual inheritance and various "diamonds" arise only with pointers to member functions. With pointers to data members no such complications exist. Pointers to data members are much simpler. And yes, a pointer to a data member is just an offset (in a typical implementation).Oswald
@AndreyT: no i am not, or i am missing something. check #1130394 's answer. In the virtual inheritance case accessing a member with offsetof crash whereas ptr-to-member worksResurrection
@benoitj, look at #1697520 . The offset for a member in C 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 an A member, the vtable or similar is used to get a correct offset starting from C), and when converted to a member pointer to another class, offset adjustments with the help of a vtable or similar can be done.Basidium
Actually, i just found that a conversion from int A::* to int 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
@benoitj: No, you are missing the fact that converting data-member pointers along the class hierarchy will generaly require the compiler to modify the offset, using the internal information abvailable to the compiler. This does not change the fact that pointer to data member is offset. Just keep in mind that it is not an always fixed offset, once the conversions are involved. But it is an offset nevertheless.Oswald
@Johannes: All member pointers are not just offset. But data members pointers are plain and simple offset in a typical implementation. Offset alone is prefectly sufficient to fully implement the requirements of the standard with regard to data member pointers.Oswald
@AndreyT yes, indeed. Nothing wrong :) I was just surprised we couldn't convert if there is a virtual base in between. :)Basidium
O
26

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.

Oswald answered 18/12, 2009 at 18:34 Comment(3)
In C++0x, we are allowed to do sizeof(Outer::inner.i) and decltype(Outer::inner.i). I wonder what the reason is that makes member pointers to such sub-elements more complicated?Basidium
for your example regarding the pointer to the array, you can do: Outer o; int (Outer::*p_array)[10]; p_array = &Outer::j; o.*p_array[3] = 0;Rarefied
@Grim Fandango: Yes, but that happens to be beside the point. What we are talking about is the ability to point into subobjects.Oswald
O
20

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;
Optimize answered 18/12, 2009 at 18:29 Comment(2)
@JerryCoffin : Thanks for the great answer. How would you use this notation to reference the, say, j[3] element out of the <code>struct Outer { int i; int j[5]; }</code>Rarefied
@GrimFandango: You'd create the pointer to member like: int (Outer::* ptr_j)[5] = &Outer::j;. Assuming some instance x of Outer, you'd dereference it like: (x.*ptr_j)[3] = 1;.Optimize
N
5

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!

Nevillenevin answered 8/11, 2016 at 9:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.