Reputation: 25527
$4.11/2 states -
An rvalue of type “pointer to member of
B
of type cvT
,” whereB
is a class type, can be converted to an rvalue of type “pointer to member ofD
of type cvT
,” whereD
is a derived class (clause 10) ofB
. IfB
is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class ofD
, a program that necessitates this conversion is ill-formed.
My question is why we have the restriction of B
not being a virtual base class of D
?
Upvotes: 6
Views: 493
Reputation: 52179
Consider a situation involving a non-virtual base class:
class A { int a; }
class B : public A { int b; }
class C : public A { int c; }
class D : public B, public C { int d; }
Here's a possible memory layout:
+-------------+
| A: int a; |
+-------------+
| B: int b; |
+-------------+
| A: int a; |
+-------------+
| C: int c; |
+-------------+
| D: int d; |
+-------------+
D
ends up with two A
subobjects because it inherits from B
and C
and both each have an A
subobject.
Pointers to member variables are typically implemented as an integer offset from the start of the object. In this case, the integer offset for int a
in an A
object is zero. So a "pointer to int a
of type A
" may be simply an integer offset of zero.
To convert a "pointer to int a
of type A
" to a "pointer to int a
of type B
," you just need an integer offset to the A
subobject located in B
(the first A
subobject).
To convert a "pointer to int a
of type A
" to a "pointer to int a
of type C
," you just need an integer offset to the A
subobject located in C
(the second A
subobject).
Since the compiler knows where B
and C
is relative to A
, the compiler has enough information on how to downcast from A
to B
or C
.
Now consider a situation involving a virtual base class:
struct A { int a; }
struct B : virtual public A { int b; }
struct C : virtual public A { int c; }
struct D : public B, public C { int d; }
Possible memory layout:
+-------------+
| B: ptr to A | ---+
| int b; | |
+-------------+ |
| C: ptr to A | ---+
| int c; | |
+-------------+ |
| D: int d; | |
+-------------+ |
| A: int a; | <--+
+-------------+
Virtual base classes are typically implemented by having B
and C
(which virtually derive from A
) contain a pointer to the single A
subjobject. The pointers to the A
subobject are required because the location of A
relative to B
and C
is not constant.
If all we had was a "pointer to int a
of type A
," we won't be able to cast it to a "pointer to int a
of type B
", since the location of the B
and C
subobjects can vary relative to A
. A
doesn't have back-pointers to B
nor C
, so we simply don't have enough information for the downcast to work.
Upvotes: 5
Reputation: 254631
With non-virtual inheritance, the base-class and derived-class members can be laid out contiguously in memory, with the base class first, so that the each base-class member is at the same location relative to the object's address whether the object is a B
or a D
. This makes it easy to convert a pointer-to-member-of-B
to a pointer-to-member-of-D
; both can be represented as an offset from the object's address.
With virtual inheritance, base-class members must be accessed via a pointer (or equivalent) in the derived object, indicating where the base class is located. This would require adding extra information to the pointer-to-member representation to indicate that this indirection is needed, and would necessitate a run-time check when using any pointer-to-member.
A general principle behind much of C++ is to avoid run-time overhead wherever possible. In this case, the choice was between a run-time check on quite a common operation, versus disallowing quite an obscure conversion, and it seems that that principal was applied here.
Upvotes: 1
Reputation: 5715
Really interesting question. Learned something new today. This is what I could find related to the subject: Casting member function pointers from derived class to virtual base class does not work
Upvotes: 0