What is an `int foo::*bar::*`?

C++Pointer to-Member

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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;

Solution 2 - C++

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);
}

Solution 3 - C++

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.

Solution 4 - C++

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.

Solution 5 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionzneakView Question on Stackoverflow
Solution 1 - C++John ZwinckView Answer on Stackoverflow
Solution 2 - C++jrokView Answer on Stackoverflow
Solution 3 - C++T.C.View Answer on Stackoverflow
Solution 4 - C++SvalorzenView Answer on Stackoverflow
Solution 5 - C++hlideView Answer on Stackoverflow