Consider the following code:
struct Base {};
struct Derived : public virtual Base {};
void f()
{
Base* b = new Derived;
Derived* d = static_cast<
I suppose, this is due to classes with virtual inheritance having different memory layout. The parent has to be shared between children, therefore only one of them could be laid out continuously. That means, you are not guaranteed to be able to separate a continuous area of memory to treat it as a derived object.
The technical problem is that there's no way to work out from a Base*
what the offset is between the start of the Base
sub-object and the start of the Derived
object.
In your example it appears OK, because there's only one class in sight with a Base
base, and so it appears irrelevant that the inheritance is virtual. But the compiler doesn't know whether someone defined another class Derived2 : public virtual Base, public Derived {}
, and is casting a Base*
pointing at the Base
subobject of that. In general[*], the offset between the Base
subobject and the Derived
subobject within Derived2
might not be the same as the offset between the Base
subobject and the complete Derived
object of an object whose most-derived type is Derived
, precisely because Base
is virtually inherited.
So there's no way to know the dynamic type of the complete object, and different offsets between the pointer you've given the cast, and the required result, depending what that dynamic type is. Hence the cast is impossible.
Your Base
has no virtual functions and hence no RTTI, so there certainly is no way to tell the type of the complete object. The cast is still banned even if Base
does have RTTI (I don't immediately know why), but I guess without checking that a dynamic_cast
is possible in that case.
[*] by which I mean, if this example doesn't prove the point then keep adding more virtual inheritance until you find a case where the offsets are different ;-)
Consider the following function foo
:
#include <iostream>
struct A
{
int Ax;
};
struct B : virtual A
{
int Bx;
};
struct C : B, virtual A
{
int Cx;
};
void foo( const B& b )
{
const B* pb = &b;
const A* pa = &b;
std::cout << (void*)pb << ", " << (void*)pa << "\n";
const char* ca = reinterpret_cast<const char*>(pa);
const char* cb = reinterpret_cast<const char*>(pb);
std::cout << "diff " << (cb-ca) << "\n";
}
int main(int argc, const char *argv[])
{
C c;
foo(c);
B b;
foo(b);
}
Although not really portable, this function shows us the "offset" of A and B. Since the compiler can be quite liberal in placing the A subobject in case of inheritance (also remember that the most derived object calls the virtual base ctor!), the actual placement depends on the "real" type of the object. But since foo only gets a ref to B, any static_cast (which works at compile time by at most applying some offset) is bound to fail.
ideone.com (http://ideone.com/2qzQu) outputs for this:
0xbfa64ab4, 0xbfa64ac0
diff -12
0xbfa64ac4, 0xbfa64acc
diff -8
Fundamentally, there's no real reason, but the intention is that
static_cast
be very cheap, involving at most an addition or a
subtraction of a constant to the pointer. And there's no way to
implement the cast you want that cheaply; basically, because the
relative positions of Derived
and Base
within the object may change
if there is additional inheritance, the conversion would require a good
deal of the overhead of dynamic_cast
; the members of the committee
probably thought that this defeats the reasons for using static_cast
instead of dynamic_cast
.
static_cast
is a compile time construct. it checks for the validity of cast at compile time and gives an compilation error if invalid cast.
virtual
ism is a runtime phenomenon.
Both can't go together.
C++03 Standard §5.2.9/2 and §5.2.9/9 ar relevant in this case.
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 not a virtual base class of D. The null pointer value (4.10) is converted to the null pointer value of the destination type. If the rvalue of type “pointer to cv1 B” points to a B that is actually a sub-object of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the result of the cast is undefined.
static_cast
can perform only those casts where memory layout between the classes is known at compile-time. dynamic_cast
can check information at run-time, which allows to more accurately check for cast correctness, as well as read run-time information regarding the memory layout.
Virtual inheritance puts a run-time information into each object which specifies what is the memory layout between the Base
and Derived
. Is one right after another or is there an additional gap? Because static_cast
cannot access such information, the compiler will act conservatively and just give a compiler error.
In more detail:
Consider a complex inheritance structure, where - due to multiple inheritance - there are multiple copies of Base
. The most typical scenario is a diamond inheritance:
class Base {...};
class Left : public Base {...};
class Right : public Base {...};
class Bottom : public Left, public Right {...};
In this scenario Bottom
consists of Left
and Right
, where each has its own copy of Base
. The memory structure of all the above classes is known at compile time and static_cast
can be used without a problem.
Let us now consider the similar structure but with virtual inheritance of Base
:
class Base {...};
class Left : public virtual Base {...};
class Right : public virtual Base {...};
class Bottom : public Left, public Right {...};
Using the virtual inheritance ensures that when Bottom
is created, it contains only one copy of Base
that is shared between object parts Left
and Right
. The layout of Bottom
object can be for example:
Base part
Left part
Right part
Bottom part
Now, consider that you cast Bottom
to Right
(that is a valid cast). You obtain a Right
pointer to an object that is in two pieces: Base
and Right
have a memory gap in between, containing the (now-irrelevant) Left
part. The information about this gap is stored at run-time in a hidden field of Right
(typically referred to as vbase_offset
). You can read the details for example here.
However, the gap would not exist if you would just create a standalone Right
object.
So, if I give you just a pointer to Right
you do not know at compile time if it is a standalone object, or a part of something bigger (e.g. Bottom
). You need to check the run-time information to properly cast from Right
to Base
. That is why static_cast
will fail and dynamic_cast
will not.
Note on dynamic_cast:
While static_cast
does not use run-time information about the object, dynamic_cast
uses and requires it to exist! Thus, the latter cast can be used only on those classes which contain at least one virtual function (e.g. a virtual destructor)