Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(Why) can we assign non-static class members to static variables in initialization?

In a larger code base I have encountered code like this (see on godbolt):

struct Foo {};

struct Base {
    Foo* a;
};

struct Bar : public Base {
    static Foo* Bar::*mybar[];
};

Foo* Bar::Bar::*mybar[] = {
    &Base::a
};

To be honest, I'm baffled. This looks like it's initializing a static array of Foo pointers in Bar with a non-static member variable of Base. How is that even possible without an object?

(Disclaimer: This is found in production code that actually works - hopefully not relying on UB?)

Also, is there any problem if I remove the qualified name lookup like here? I'd like to refactor the code and make it more readable. All these Bar::s seem quite superfluous, but since I'm not feeling so comfortable with the code I'd rather understand the implications first.

like image 854
andreee Avatar asked Dec 10 '22 00:12

andreee


2 Answers

Unlike normal pointers, class member pointers can be though of as an offset into the class, where they are telling you which member of an object they point to. So in your code, mybar is an array of class member pointers. When you do

Foo *Bar::Bar::*mybar[] = {
    &Base::a
};

you initialize the array with a pointer to the a member of Base. This isn't actually pointing to an a, it just tells the compiler which member of the object to return if you access it with an object. That would look like

Base foo; // now we have an actual object
foo.*mybar[0]; // access the `a` member of `foo` by using the "offset"
like image 154
NathanOliver Avatar answered Dec 12 '22 14:12

NathanOliver


How is that even possible without an object?

It is possible to create pointer to member variables without an object. The pointers can be used to dereference an actual member only in the presence of an object.

Simpler example:

struct Foo { int m; int n};

using MemberPtr = int Foo::*;

MemberPtr p1 = &Foo::m;  // Instance of Foo is not needed.
MemberPtr p2 = &Foo::n;  // Instance of Foo is not needed.

*p1 = 10; //  Not allowed.
*p2 = 20; //  Not allowed.

Foo a;
a.*p1 = 10;  // Changes a.m
a.*p2 = 20;  // Changes a.n

Foo b;
b.*p1 = 30;  // Changes b.m
b.*p2 = 40;  // Changes b.n

Please note that you are able to change values of members of two instances of the class using the same pointer to member variables.

like image 27
R Sahu Avatar answered Dec 12 '22 12:12

R Sahu