69

A cool thing with C++ is that it lets you create variables of pointer-to-member types. The most common use case seems to be to get a pointer to a method:

struct foo
{
    int x() { return 5; }
};

int (foo::*ptr)() = &foo::x;
foo myFoo;
cout << (myFoo.*ptr)() << '\n'; // prints "5"

However, messing around, I realized that they can just as well point to member variables:

struct foo
{
    int y;
};

int foo::*ptr = &foo::y;
foo myFoo;
myFoo.*ptr = 5;
cout << myFoo.y << '\n'; // prints "5"

This is pretty rad. It led me to a further experiment: what if you could get a pointer to a sub-member of a structure?

struct foo
{
    int y;
};

struct bar
{
    foo aFoo;
};

int bar::*foo::*ptr;

This actually compiles.

However, I have no idea how to assign it anything useful. None of the following works:

int bar::*foo::*ptr = &bar::foo::y; // no member named "foo" in "bar"
int bar::*foo::*ptr = &bar::aFoo::y; // no member named "aFoo" in "bar" (??)
int bar::*foo::*ptr = &foo::y; // can't init 'int bar::*foo::*' with 'int foo::*'

Furthermore, according to the error that this generates, it appears that this type is not exactly what I have in mind:

int bar::*foo::*ptr = nullptr;
bar myBar;
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible
                // with object type ‘bar’

It appears that this concept evades me. Obviously, I can't rule out that it simply gets parsed in a way entirely different from what I would expect.

Would anyone please explain me what an int bar::*foo::* actually is? Why does gcc tell me that a pointer to a member of bar is incompatible with a bar object? How would I use an int bar::*foo::*, and how would I construct a valid one?

zneak
  • 134,922
  • 42
  • 253
  • 328
  • This looks like a roundabout way attempting what `decltype()` with appropriate templates already delivers? – slashmais Oct 05 '14 at 08:14
  • 6
    Maybe I don't know enough about the subject, but if it helps, `int bar::*foo::*ptr = *new decltype(ptr);` compiles, but I don't know what it does and I can't seem to do anything with the result. It's also a memory leak, but sometimes memory must be sacrificed in the name of science. – IllusiveBrian Oct 05 '14 at 08:24
  • Geez, when the compiler writers implemented this, I swear they swore a lot about who the heck will ever use this. – marczellm Oct 06 '14 at 08:57
  • your very first code example has a syntax error. it must be: int (foo::*ptr)() = &foo::x; – null0 Oct 06 '14 at 11:15
  • It might be like reflection in Java... very useful for certain esoteric things, such as implementing libraries like BeanUtils, but almost never a good idea to use otherwise. – le3th4x0rbot Nov 11 '14 at 08:32

5 Answers5

53

Here's a "valid" way of initializing such a monstrosity:

struct bar;

struct foo
{
    int y;    
    int bar::* whatever;
};

struct bar
{
    foo aFoo;
};

int bar::* foo::* ptr = &foo::whatever;

As we can see, ptr is a pointer to a member of foo (foo::*, reading right to left), where that member is itself a pointer to a member of bar (bar::*), where that member is an int.

How would I use an int bar::* foo::*

You wouldn't, hopefully! But if you are under duress, try this!

struct bar
{
    foo aFoo;

    int really;
};

int bar::* foo::* ptr = &foo::whatever;
foo fleh;
fleh.whatever = &bar::really;
bar blah;
blah.*(fleh.*ptr) = 42;
std::cout << blah.really << std::endl;
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 40
    I'm not sure I understand how this gets parsed, but that wouldn,t be the first time I don't understand how C++ is parsed. – zneak Oct 05 '14 at 17:32
  • This is not always a monstrosity. Consider the case of iterator foo that needs for some reason to refer to its container and container bar that holds, for example, the head iterator. – Michael Oct 06 '14 at 23:25
19

That would be a pointer to a data member that is itself a pointer to a data member (an int member of bar).

Don't ask me what it is actually useful for - my head is spinning a little :)

EDIT: Here's a full example of it in action:

#include <iostream>

struct bar {
    int i;
};

struct foo {
    int bar::* p;
};

int main()
{
    bar b;
    b.i = 42;

    foo f;
    f.p = &bar::i;

    int bar::*foo::*ptr = &foo::p;
    std::cout << (b.*(f.*ptr));
}

Output is, of course, 42.

It can get even more fun - here's some pointers to member functions that return pointers to member functions:

#include <iostream>

struct bar {
    int f_bar(int i) { return i; };
};

struct foo {
    int(bar::*f_foo())(int)
    {
        return &bar::f_bar;
    }
};

int main()
{
    int(bar::*((foo::*ptr)()))(int) = &foo::f_foo;

    bar b;
    foo f;

    std::cout << (b.*((f.*ptr)()))(42);
}
jrok
  • 54,456
  • 9
  • 109
  • 141
  • Just when I thought I'm beginning to understand some of these statements, your second example smashed my hopes. – Cengiz Can Oct 09 '14 at 09:46
  • I understand why the second example smashed your hopes :). – hlide Feb 13 '16 at 20:46
  • 1) bar defines a method called f_bar having an integer argument and returning an integer. Easy to understand so far. 2) foo defines a method f_foo having no arguments and returning a pointer to a method of bar having an integer argument and returning an integer. Less easy to read I admit. 3) main first defines a local 'ptr' as being a pointer to a method of foo having no argument and returning a pointer to a method of bar having an integer argument and returning an integer and assigns it with f_foo method of foo. Gosh it is so long to describe it ! to be continued ! – hlide Feb 13 '16 at 21:03
  • 4) Since f_bar and f_foo are methods, you need to define b and f so you can call `(f.*ptr)()` to return `&bar::f_bar`. So `std::cout << (b.*((f.*ptr)()))(42);`is basically same as `std::cout << (b.*(&bar::f_bar))(42);`. 5) Oh wait ! calling '(b.*(&bar::f_bar))(42);' is same as calling 'b.f_bar(42);` ! So you indeed output `42`. – hlide Feb 13 '16 at 21:04
13

Let's parse the declaration int bar::*foo::*ptr;.

§8.3.3 [dcl.mptr]/p1:

In a declaration T D where D has the form

nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1

and the nested-name-specifier denotes a class, and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the identifier of D is “derived-declarator-type-list cv-qualifier-seq pointer to member of class nested-name-specifier of type T”.

  • Step 1: This is a declaration of the above form where T = int, nested-name-specifier = bar::, and D1 = foo::* ptr. We first look at the declaration T D1, or int foo::* ptr.

  • Step 2: We apply the same rule again. int foo::* ptr is a declaration of the above form where T = int, nested-name-specifier = foo::, and D1 = ptr. Obviously the type of the identifier in int ptr is "int", so the type of the identifier ptr in the declaration int foo::* ptr is "pointer to member of class foo of type int".

  • Step 3. We go back to the original declaration; the type of the identifier in T D1(int foo::* ptr) is "pointer to member of class foo of type int" per step 2, so the derived-declarator-type-list is "pointer to member of class foo of type". Substitution tells us that this declaration declares ptr to be "pointer to member of class foo of type pointer to member of class bar of type int".

Hopefully you will never need to use such a monstrosity.

T.C.
  • 133,968
  • 17
  • 288
  • 421
3

In case anyone is wondering, you can't create a pointer-to-member which nests multiple layers deep. The reason for this is that all pointer-to-members are actually way more complicated that what they look at a first glance; they are not simply containing a particular offset for that specific member.

Using a simple offset does not work due to virtual inheritance and the likes; basically it can happen that, even within a single type, the offsets of a particular field vary between instances, and thus pointer-to-member resolution needs to be done at runtime. Mostly this is due to the fact that the standard does not specify how the internal layout for non-POD types might work, so there's no way to make it work statically.

If this is the case, doing two-level deep resolution cannot be done with a normal pointer-to-member, but would need the compiler to generate a pointer such that it contains double the information of a one-deep pointer-to-member.

I imagine that since pointers-to-member are not that common, there is no need to actually create a syntax to allow for setting multiple-layer deep members, when you can still use multiple pointers to achieve the same result.

Svalorzen
  • 5,353
  • 3
  • 30
  • 54
1

First, to help the "readability" you could use parenthesis (compiling will work) :

struct bar;

struct foo
{
    int y;
    int (bar:: *whatever); // whatever is a pointer upon an int member of bar.
};

struct bar
{
    foo aFoo;
};

// ptr is a pointer upon a member of foo which points upon an int member of bar.
int (bar:: *(foo:: *ptr)) = &foo::whatever;

Note that

int (bar:: *whatever)

is equivalent to

int (*whatever)

with a constraint about membership of bar.

As for

int (bar:: *(foo:: *ptr))

, it is equivalent to

int (*(*ptr))

with two constraints about memberships of foo and bar.

They are just pointers. They do not check if bar or foo really have a compatible member because that would prevent from using a forward declaration of class bar and class bar does not check if other classes are referring to its members through pointers. Besides, you may also need to refer an opaque class (that is, having a class bar defined in a separate unit).

What about the usefulness ? maybe for C++ reflection as a way to set/get the value of a member of a class through a class wrapper ?

template< typename Class, typename Type >
struct ClassMember
{
    using MemberPointer = Type (Class:: *);
    MemberPointer member;
    ClassMember(MemberPointer member) : member(member) {}
    void operator()(Class & object, Type value) { object.*member = value; }
    Type operator()(Class & object)  { return object.*member; }
};

template< typename Class, typename Type > ClassMember< Class, Type > MakeClassMember(Type(Class:: *member))
{
    return ClassMember< Class, Type >(member);
}

struct Rectangle
{
    double width;
    double height;

    Rectangle(double width = 0., double height = 0.) : width(width), height(height) {}
};

int main(int argc, char const *argv[])
{
    auto r = Rectangle(2., 1.);

    auto w = MakeClassMember(&Rectangle::width);
    auto h = MakeClassMember(&Rectangle::height);

    w(r, 3.);
    h(r, 2.);

    printf("Rectangle(%f, %f)\n", w(r), h(r));

    return 0;
}

Sure, this example does not show a particular usage of a double member pointer because I do not see a simple way to illustrate it here or a good reason to do so conceptually speaking.

hlide
  • 237
  • 2
  • 10