23

Why static_cast cannot downcast from a virtual base ?

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

int main()
{
  D d;
  A& a = d;
  D* p = static_cast<D*>(&a); //error
}  

g++ 4.5 says:

 error: cannot convert from base ‘A’ to derived type ‘D’ via virtual base ‘A’

The solution is to use dynamic_cast ? but why. What is the rational ?

-- edit --
Very good answers below. No answers detail exactly how sub objects and vtables end up to be ordered though. The following article gives some good examples for gcc:
http://www.phpcompiler.org/articles/virtualinheritance.html#Downcasting

log0
  • 10,489
  • 4
  • 28
  • 62

2 Answers2

11

The obvious answer is: because the standard says so. The motivation behind this in the standard is that static_cast should be close to trivial—at most, a simple addition or subtraction of a constant to the pointer. Where s the downcast to a virtual base would require more complicated code: perhaps even with an additional entry in the vtable somewhere. (It requires something more than constants, since the position of D relative to A may change if there is further derivation.) The conversion is obviously doable, since when you call a virtual function on an A*, and the function is implemented in D, the compiler must do it, but the additional overhead was considered inappropriate for static_cast. (Presumably, the only reason for using static_cast in such cases is optimization, since dynamic_cast is normally the preferred solution. So when static_cast is likely to be as expensive as dynamic_cast anyway, why support it.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • For me there is no need of dynamic information here so if there is a cost, the cost should be 100% compile time. – log0 May 18 '11 at 12:47
  • 2
    You're wrong. The relative positions of 'A' and 'D' depend on the total inheritance hierarchy, as other answerers have already pointed out. For any given most derived class, it is a constant, but given an `A*`, the compiler can't know the most derived class without accessing dynamic information. – James Kanze May 18 '11 at 12:53
  • 2
    +1 for this *"The motivation behind this in the standard is that static_cast should be close to trivial—at most, a simple addition or subtraction of a constant to the pointer. Where s the downcast to a virtual base would require more complicated code"* – Nawaz May 18 '11 at 12:54
  • @James_Kanze I don't think you need to know the actual most derived class. `static_cast` is supposed to let you downcast to the "wrong" type. The only thing you need is the gap between types `A` and `D`. I also agree with the part quoted by Nawaz. – log0 May 18 '11 at 13:11
  • @Ugo `static_cast` doesn't verify the target type, but it does return the correct address if the object actually is the target type. And the gap between types `A` and `D` is not a compile time constant. It depends on the dynamic type of the object. – James Kanze May 18 '11 at 13:20
  • @James_Kanze I don't understand why the offset between `A` and `D` could change. it should always be something like `static const D* d; static const A& a = *d; static const ptrdiff_t offset = (const char*)(void*)(&a) - (const char*)(void*)(d);` – log0 May 18 '11 at 13:38
  • 1
    @Ugo Certainly. And the results of the final subtraction are not known at compile time; they depend on the actual type of the object `d` points to (and whether `d` is a null pointer, of course). If I have an `struct E : D, virtual A { int i; }`, and `d` actually points to an `E`, the results of the subtraction will be different than if it points to a `D`. Try it. – James Kanze May 18 '11 at 13:46
  • I don't agree that the motivation is that `static_cast` should be close to trivial. Triviality is not an issue here, but the fact that `static_cast` does not depend on any runtime information, and to perform that downcast dynamic information is needed, thus the `dynamic_cast` requirement. – David Rodríguez - dribeas May 18 '11 at 14:27
  • @David I guess it depends on what you mean by "close to trivial". Back when it was being discussed in the committee, people used words like "extremely simple" or "almost trivial", which is what I meant by "close to trivial". But I'm pretty sure that no runtime information was part of what they meant by "simple" or "trivial". As a said: adding or subtracting a constant, and not much else. – James Kanze May 18 '11 at 17:26
10

Because if the object was actually of type E (derived from D), the location of A subobject relative to D subobject could be different than if the object is actually D.

It actually already happens if you consider instead casting from A to C. When you allocate C, it has to contain instance of A and it lives at some specific offset. But when you allocate D, the C subobject refers to the instance of A that came with B, so it's offset is different.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • I do not understand this. Can you explain a little better? – Björn Pollex May 18 '11 at 12:30
  • Doesn't make sense to me: There is no type 'E' in this example. Did you mean "... actually of type D ..."? – C.J. May 18 '11 at 12:32
  • 5
    @Space_COwbOy, @C Johnson: The issue is that virtual inheritance means that there will be a single subobject of `A`. Now if you added `struct E : virtual A, UnrelatedType, D {}` to the hierarchy, you can obtain a `D` pointer to the `E` object, but the offset of `A` from that pointer will be different depending on type of the pointed object. – David Rodríguez - dribeas May 18 '11 at 12:34
  • 1
    @C Johnson: No, I mean of some other type derived from those in the example. – Jan Hudec May 18 '11 at 12:34
  • Good answer but can't this offset be computed at compile time ? – log0 May 18 '11 at 13:33
  • @Ugo: the offset is stored in the virtual table, and that is how `dynamic_cast` finds it. As to whether it can be calculated at compile time, the problem is that given a function that takes a `A&`, at compile time you cannot possibly know what is the type of the object that has been passed (consider that in the code above it the real type can be `B`, `C`, `D` or `E` in the extended version) Once the type is known, getting the offset is trivial for the compiler, but when processing the function it cannot possibly know (also consider that the same function could be called with different objects) – David Rodríguez - dribeas May 18 '11 at 13:59
  • @David Sorry If I am missing something obvious but what you want is casting to `D`. if the real type of the object is `E`, then the cast is wrong anyway. But that's the point of `static_cast` to trust you and let you do it. – log0 May 18 '11 at 14:09
  • @Ugo, what you are missing is that `E` inherits from `D`, and that makes the cast valid, but the offset of the start of the object from the `A` sub object to the `D` subobject can be different if the actual type is `D` or another type `E` derived from `D`. Virtual inheritance imposes specific constraints on the layout of the objects that are not present in non-virtual inheritance, where each subobject location is known at compile time. – David Rodríguez - dribeas May 18 '11 at 15:12
  • Ok I think I got the point. The example with `C` and `D` is good actually. Sub-object of `C` are allocated the following way `[AC]`, while D sub-objects are order like this `[ABCD]`. Thus in the first case casting from `A` to `C` is a shift of 1, while in the second case there is a shift of 2. – log0 May 18 '11 at 15:17