Calling virtual member functions of a class using a pointer to the base class is of course a very common thing to do in C++. So I find it strange that it seems impossible to do the same thing when you have a member pointer instead of a normal pointer. Please consider the following code:
struct B
{
virtual void f();
};
struct D : B
{
virtual void f();
};
struct E
{
B b;
D d;
};
int main()
{
E e;
// First with normal pointers:
B* pb1 = &e.b; // OK
B* pb2 = &e.d; // OK, B is a base of D
pb1->f(); // OK, calls B::f()
pb2->f(); // OK, calls D::f()
// Now with member pointers:
B E::* pmb1 = &E::b; // OK
B E::* pmb2 = &E::d; // Error: invalid conversion from ‘D E::*’ to ‘B E::*’
(e.*pmb1).f(); // OK, calls B::f()
(e.*pmb2).f(); // Why not call D::f() ???
return 0;
}
Visual C++ goes on to say:
error C2440: 'initializing' : cannot convert from 'D E::* ' to 'B E::* '
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
I don't understand why these are 'unrelated'. Why is this not possible?
Edit:
I am trying to keep this a C++ question and not about the particular problem I am trying to solve, but this is essentially what I want to do:
std::vector<B E::*> v;
v.push_back( &E::b ); // OK
v.push_back( &E::d ); // Error
B& g( E& e, int i )
{
return e.*v[i];
}
E is a class containing several members derived from B. The vector v is used to organize (eg reorder) member pointers to these members. Vector v changes infrequently. The function g() allows you to select one of the members of E using an index into v. It is called very often and each time with a different E.
If you think about it, v is just a lookup table of offsets. The function g() simply selects one of these offsets and add it to the E* in order to return the B*. The function g() is inlined by the compiler and compiles to just 4 CPU instructions, which is exactly what I want:
// g( e, 1 )
mov rax,qword ptr [v (013F7F5798h)]
movsxd rcx,dword ptr [rax+4]
lea rax,[e]
add rcx,rax
I cannot think of any reason why the standard would not allow a D E::* to be converted to a B E::*.
Derived class pointer cannot point to base class.
Explanation: A base class pointer can point to a derived class object, but we can only access base class member or virtual functions using the base class pointer because object slicing happens when a derived class object is assigned to a base class object.
The simple answer, is that C++ does not define the conversion you are attempting and thus your program is ill formed.
Consider standard conversions (C++11§4/1):
Standard conversions are implicit conversions with built-in meaning. Clause 4 enumerates the full set of such conversions.
Since you are not performing any cast, nor do you have any custom conversions defined, you are indeed performing such a standard conversion. Without enumerating all possible such conversions, two are of explicit interest for your example: pointer conversions and pointer to member conversions. Note that C++ does not consider pointer to member types to be a subset of pointer types.
Pointer to member conversions are defined in C++11§4.11 and consist of exactly two conversions:
“pointer to member of B of type cv T”, where B is a class type, can be converted to a [...] “pointer to member of D of type cv T”, where D is a derived class [...] of B
void
(4.10/2) which allows the conversion of any pointer type to pointer to void
.A [...] “pointer to cv D”, where D is a class type, can be converted to a [...] “pointer to cv B”, where B is a base class [...] of D
C++ does not allow this conversion, and many others like it, because it would complicate the implementation of virtual inheritance.
struct A { int a; };
struct B : virtual A { int b; };
struct C : virtual A { int c; };
struct D : B, C { int d };
Here's how a compiler might try to lay out these classes:
A: [ A::a ]
B: [ B::b ] [ A ]
C: [ C::c ] [ A ]
D: [ D::d ] [ B ] [ C ] [ A ]
If we have a pointer to B, it's not an easy task to get a pointer to its base class A, because it's not at a fixed offset from the beginning of the B object. A may be located right next to B::b, or it may be somewhere else, depending on whether our object is a standalone B or a B that is a base of D. There is no way to know which case we have!
To make a cast, the program needs to actually access the B object and get a hidden base pointer from it. So the real layout would be more like this:
A: [ A::a ]
B: [ B::b | B::address-of-A ] [ A ]
C: [ C::c | C::address-of-A ] [ A ]
D: [ D::d | D::address-of-A ] [ B ] [ C ] [ A ]
where address-of-A
s are hidden members added by the compiler.
That's all fine and dandy while we are talking about regular pointers. But when we have a pointer-to-member, we don't have any object to go and fetch the hidden base pointer from. So if we have only B X::*
, there is absolutely no way to convert it to A X::*
without having an actual X object.
While it is in theory possible to allow conversions like this, it would be hugely complicated. For example, a pointer-to member would need to hold variable amount of data (all the hidden pointer-to-base values the original object has).
In theory C++ could allow such conversions of pointers-to-members only to non-virtual base classes (in this example, D X::*
to B X::*
or C X::*
, but not A X::*
). Or at least I don't see why it could be an insurmountable problem for implementations. I guess this is not done because it would introduce additional complexity to the standard for very little benefit. Or maybe the standard doesn't want to preclude unusual implementations of inheritance. For instance an implementation may want to implement all inheritance with hidden pointer-to-base members, as if it's always virtual (for debugging purposes, or for compatibility with other languages, or whatever). Or perhaps it is just overlooked. Maybe in another revision of the standard (2020?)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With