3

I tried creating a class that inherits from multiple classes as followed, getting a "diamond"
(D inherits from B and C. B and C both inherits from A virtually):

  A
  / \
B   C
  \ /
  D

Now, I have a container with a linked list that holds pointers to the base class (A).
When I tried doing explicit casting to a pointer (after typeid check) I got the following error:
"cannot convert a pointer to base class "A" to pointer to derived class "D" -- base class is virtual"

But when I use dynamic casting it seems to work just fine.
Can anyone please explain to me why I have to use dynamic casting and why does the virtual inheritance causes this error?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
David Tzoor
  • 987
  • 4
  • 16
  • 33
  • 6
    Consider fixing your design. :) –  Dec 23 '13 at 20:01
  • could you add some code please? – dkrikun Dec 23 '13 at 20:02
  • What type of cast fails to work (static, reinterpret, or C-style)? – Mark B Dec 23 '13 at 20:02
  • 1
    @rightfold Every time I see a post regarding pointer casting etc people are suggesting to fix the design. But if I want to use a general container how can I do it differently? – David Tzoor Dec 23 '13 at 20:11
  • 1
    @David Tzoor Have your base class provide a handy abstract interface that your derived classes implement. Then you don't need to know the concrete type. – Mark B Dec 23 '13 at 20:13
  • 1
    @DavidTzoor The problem doesn’t lie in the casting of pointers or the contains. It’s the dreaded diamond of death itself that is the problem. (And in the ideal case, inheritance isn’t visible at all, but merely an implementation detail.) –  Dec 23 '13 at 20:15
  • 1
    @rightfold Since when can diamonds be "dreaded diamonds"? I thought diamonds were precious. – curiousguy Dec 24 '13 at 17:48
  • @MarkB That works only if the abstract method makes sense in all derived classes. If you are casting then it almost certainly does not. I would always *insist* that if a design results in 'not implemented - do not call' methods in subclasses, then it is reworked to eliminate this serious design error (even if the consequence is that you have to use multiple inheritance (or interfaces)). – Ian Goldby Dec 24 '13 at 18:38
  • @rightfold While I agree that the design should be fixed, it doesn't answer the actual question about a situation that is completely legal in C++. At least provide the answer WITH your alternative suggestion. – derpface Dec 24 '13 at 18:50

2 Answers2

4

"Virtual" always means "determined at runtime". A virtual function is located at runtime, and a virtual base is also located at runtime. The whole point of virtuality is that the actual target in question is not knowable statically.

Therefore, it is impossible to determine the most-derived object of which you are given a virtual base at compile time, since the relationship between the base and the most-derived object is not fixed. You have to wait until you know what the actual object is before you can decide where it is in relation to the base. That's what the dynamic cast is doing.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • But why can't I "force" explicit casting after I have made sure the type of the object? What is the difference between explicit casting after typeid check and dynamic cast? @Kerrek SB – David Tzoor Dec 23 '13 at 20:09
  • @DavidTzoor Take a look here: http://stackoverflow.com/questions/15921372/c-virtual-table-layout-of-mimultiple-inheritance – Johan Dec 23 '13 at 20:16
  • 2
    @DavidTzoor: Again, you don't *know* the structure, no matter how much you know the type. Imagine you want to cast an `A*` to a `B*` in your example. But to do that, you must know whether the object is actually a `B` or a `D`! The answer is a different one in each case. The virtual base does not "belong" to `B`, it belongs to the most-derived object. – Kerrek SB Dec 23 '13 at 20:31
2

When I tried doing explicit casting to a pointer (after typeid check)

After a successful typeid(x) == typeid(T) you know the dynamic type of x, and you could in theory avoid any other runtime checking involved in dynamic_cast at that point. OTOH, the compiler is not required to do this kind of static analysis.

static_cast<T&>(x) does not convey to the compiler the knowledge that the dynamic type of x really is T: the precondition is weaker (that a T object has x as a subobject base class).

C++ could provide a static_exact_cast<T&>(x) which is only valid if x designates an object of dynamic type T (and not some type derived from T, unlike static_cast<T&>). This hypothetical static_exact_cast<T&>(x), by assuming the dynamic type of x is T, would skip any runtime check and compute the correct address from knowledge of T object layout: because in

D d;
B &br = d;

no runtime offset computation is necessary, in static_exact_cast<D&>(br) the reverse adjustment would involve no runtime offset computation.

Given

B &D_to_B (D &dr) {
  return dr;
}

a runtime offset computation is needed in D_to_B (except if whole program analysis shows that no class derived from D has different offset of base class A); given

struct E1 : virtual A
struct E2 : virtual A
struct F : E1, D, E2

the layout of the D subobject of F will be different from the layout of a D complete object: the A subobject will be at a different offset. The offset needed by D_to_B will be given by the vtable of D (or stored directly in the object); it means that D_to_B will not just involve a constant offset as a simple "static" upcast (before entering the constructor of the object, the vptr is not set up so such casting cannot work; be careful with up casts in constructor init list).

And BTW, D_to_B (d) is not different from a static_cast<B&> (d), so you see that the runtime computation of an offset can be done inside a static_cast.

Consider the following code compile naively (assuming no fancy analysis showing that ar has dynamic type F):

F f;
D &dr = f; // static offset
A &ar = dr; // runtime offset
D &dr2 = dynamic_cast<D&>(ar);

Finding the A base class subject from a reference to D (a lvalue of unknown dynamic type) requires a runtime check of the vtable (or equivalent). Going back to the D subobject requires a non trivial computation:

  • finding out the address of the complete object (which happens to be of type F), using the vtable of A
  • finding out the address of the unambiguous and publicly derived D base class subobject of f, using the vtable again

This is not trivial as dynamic_cast<D&>(ar) statically knows nothing specific about F (the layout of F, the layout of the vtable of F); everything is fetched from the vtable. All dynamic_cast knows is that there is a derived class of A and the vtable has all the information.

Of course, there is no static_exact_cast<> in C++, so you have to use dynamic_cast, with the associated runtime checks; dynamic_cast is a complex function, but the complexity covers the base class cases; when the dynamic type is given to dynamic_cast, the base classes tree walking is avoided, and the test is fairly simple.

Conclusion:

Either you name the dynamic type in dynamic_cast<T> and dynamic_cast is fast and simple anyway, or you don't and the complexity of dynamic_cast is really needed.

curiousguy
  • 8,038
  • 2
  • 40
  • 58