andreee
andreee

Reputation: 4679

(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.

Upvotes: 4

Views: 99

Answers (3)

R Sahu
R Sahu

Reputation: 206567

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.

Upvotes: 3

To start with, Bar::mybar is an array of pointers to members. Those aren't actual pointers. They are more like an abstraction over an offset into the object. They allow accessing members indirectly. Given a Base object (or one derived from it), we can invoke them, as follows

aBar.*mybar[0] // This resolve to a Foo* inside aBar

The other thing of note, is that in your example the object in namespace scope is not the definition of Bar::mybar. It's an unrelated array. The correct definition would be

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

It is because your definition was wrong that removing some of the qualified name had no effect. When you removed it, you were left with

static Foo *mybar[];

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

The types of the declaration and "definition" mismatch. But you got no error because those are in fact different objects.

In the correct definition, every Bar:: qualifier is required and serves a different purpose. One serves to create the correct type (pointer to member of Bar), the other designates the static member of Bar that is being defined, and is required in every static class member definition.

Upvotes: 2

NathanOliver
NathanOliver

Reputation: 180500

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"

Upvotes: 8

Related Questions