Reputation: 4679
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
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
Reputation: 170065
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
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