C++ cannot convert from base A to derived type B via virtual base A

32,757

Solution 1

In order to understand the cast system, you need to dive into the object model.

The classic representation of a simple hierarchy model is containment: if B derives from A then the B object will, in fact, contain an A subobject alongside its own attributes.

With this model downcasting is a simple pointer manipulation by an offset known at compilation time, which depends on the memory layout of B.

This is what static_cast does: a static cast is dubbed static because the computation of what is necessary for the cast is done at compile-time, be it pointer arithmetic or conversions (*).

However, when virtual inheritance kicks in, things tend to become a bit more difficult. The main issue is that with virtual inheritance all subclasses share the same instance of the subobject. In order to do that, B will have a pointer to an A, instead of an A proper, and the A base class object will be instantiated outside of B.

Therefore, it's impossible at compilation time to be able to deduce the necessary pointer arithmetic: it depends on the runtime type of the object.

Whenever there is a runtime type dependency, you need RTTI (RunTime Type Information), and making use of RTTI for casts is the job of dynamic_cast.

In summary:

  • compile-time downcast: static_cast
  • run-time downcast: dynamic_cast

The other two are also compile-time casts, but they are so specific that it's easy to remember what they are for... and they are smelly, so better not use them at all anyway.

(*) As noted by @curiousguy in the comments, this only holds for downcasting. A static_cast allows upcasting regardless of virtual or simple inheritance, though then the cast is also unnecessary.

Solution 2

As far as I know, you need to use dynamic_cast because the inheritance is virtual and you're downcasting.

Solution 3

You can't use static_cast in this situation because the compiler doesn't know the offset of B relative to A at compile time. The offset must be calculated at run-time based on the exact type of the most derived object. Therefore you must use dynamic_cast.

Solution 4

Yes, you have to use a dynamic_cast, but you'll have to make the base class A polymorphic, e.g. by adding a virtual dtor.

Solution 5

According standard docs,

Section 5.2.9 - 9, for Static Cast,

An rvalue of type “pointer to cv1 B,” where B is a class type, can be converted to an rvalue of type “pointer to cv2 D,” where D is a class derived (clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D.

Hence, it is not possible and you should use dynamic_cast...

Share:
32,757
Panayiotis Karabassis
Author by

Panayiotis Karabassis

Updated on October 23, 2020

Comments

  • Panayiotis Karabassis
    Panayiotis Karabassis over 3 years

    I have three classes:

    class A {};
    
    class B : virtual public A {};
    class C : virtual public A {};
    
    class D: public B, public C {};
    

    Attempting a static cast from A* to B* I get the below error:

    cannot convert from base A to derived type B via virtual base A
    
  • Liton
    Liton over 13 years
    or adding at least one virtual method.
  • undu
    undu almost 12 years
    Nice answer which made me understand how virtual inheritance actually works ! +1
  • Panayiotis Karabassis
    Panayiotis Karabassis over 11 years
    Are you sure this is necessary?
  • CoffeDeveloper
    CoffeDeveloper over 11 years
    that's specific to your problem. a re-design should prevent this in most cases (avoid my example if you can). oh dear I forget a "const".
  • Panayiotis Karabassis
    Panayiotis Karabassis over 11 years
    I mean are you sure you need it? What if I give doSomething an int pointer? That would fail disastrously. Why not use a dynamic cast, and check the result? I don't know what your exact requirements are, but I believe, if you bring in the static polymorphism system (e.g. CRTP pattern) you can come up with something safer.
  • CoffeDeveloper
    CoffeDeveloper almost 11 years
    static polymorphism is not suitable in lot of cases, anyway in my actual code I'm doing that in a type-safe way, so users have no chance to passa "int*" around. Originally I had a "ID" system, but I found later better system.. still times faster than a dynamic cast.
  • CoffeDeveloper
    CoffeDeveloper almost 11 years
    if you are still interested how that code can be usefull you can take a look at a my recent project. void* tricks are used in more than one place.: code.google.com/p/infectorpp
  • h9uest
    h9uest over 8 years
    I like your answer, but the OP was apparently asking about an error for DOWNCASTING rather than upcasting.
  • Matthieu M.
    Matthieu M. over 8 years
    @h9uest: Thanks for pointing out the slip-up, I changed "up-casting" to "downcasting" and everything is now well.
  • curiousguy
    curiousguy over 8 years
    False. static_cast can be used to convert a derived ptr to a virtual base ptr.
  • curiousguy
    curiousguy over 8 years
    When converting a derived to a virtual base, you can use static_cast.
  • Matthieu M.
    Matthieu M. over 8 years
    @curiousguy: Indeed... although you don't even need a cast then, so I didn't think about taking it into account. Which begs the question why downcasting is not available too.
  • Yakov Galka
    Yakov Galka over 8 years
    @curiousguy: yes, but the question is about converting base to derived.
  • curiousguy
    curiousguy over 8 years
    Converting to a base class is possible for non-polymorphic classes. dynamic_cast is only possible for polymorphic classes: dynamic_cast means "uses the vptr" (in most cases). A conversion to a virtual base class uses either the vptr or an internal pointer (or a fixed offset when the final complete type is known).
  • curiousguy
    curiousguy over 8 years
    It isn't clear why your argument doesn't apply to derived to base also.
  • Yakov Galka
    Yakov Galka over 8 years
    @curiousguy: well, if you're asking why, then I think it is a good question and I'm not sure what the answer is. I speculate that this is because the derived to base cast is a matter of a simple indirection through the vtable, For the base to derived cast, on the other hand, you must perform the same (non constant time) algorithm as dynamic_cast does for the non-virtual inheritance. Thus there otherwise would not be a difference between static and dynamic casts for virtual inheritance. Or put another way, there is no analogy for non-virtual static_cast in the virtual-inheritance case.
  • Dr. Gut
    Dr. Gut over 4 years
    @curiousguy, @matthieu-m: I do not agree. static_cast makes the cast at run time. Upcast (without a cast expression) is also done at run time. Although it is true, that the compiled code is shorter than for dynamic_cast. For pointers static_cast compiles an if != nullptr, and then adds an offset known at compile time. However, if virtual inheritance is included, static_cast and upcast also compiles an if != nullptr, and then adds an offset known at run time. Example. static_cast can also call ctors., which is also entirely run time.
  • curiousguy
    curiousguy over 4 years
    @Dr.Gut You made many pts here. 1) "can also call ctors." Indeed static_cast can do many thing. I thought we were discussing ptr casts here, not implicit conv in general or how to construct an obj. 2) Can we agree that the context is: doing the same conversion from type X to type Y either impl., w/ some casting oper or another. 3) Can you describe X and Y such that these choices are valid and give diff compiled code?
  • Dr. Gut
    Dr. Gut over 4 years
    @curiousguy: 1) The (*) sentence ends with "or conversions". So I thought it wants to be general. I edited the answer. It seems clearer now. Hope you will like it. First part is about containment: offset is known at compile-time whether up or downcast. Second part: about virtual inheritance (downcast gives error message, upcast possible, but offset is known at run-time). 2) Let's focus on casts between related class type pointers. Otherwise the terms compile-time and run-time get easily misinterpreted. 3) I don't get what you are asking for. What choices?
  • curiousguy
    curiousguy over 4 years
    @Dr.Gut 1) You wanted to be general but seemed to imply that a static_cast would always apply a fixed constant offset. 3) The choices of two conversion X and Y, from U->V between a "new style" cast *_cast, old style cast, implicit conversion for two given types U,V. In which case are X and Y a) both valid b) different at code gen level?
  • Dr. Gut
    Dr. Gut over 4 years
    @curiousguy: Still not sure what you are asking for. This probably is not new to you. So I do not know. Maybe there is no such example. Ask it as a question here on StackOverflow.