Why can't static_cast be used to down-cast when virtual inheritance is involved?
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 ;-)
How can static_cast be used with virtual inheritance?
When you have a pointer to a Derived
in your case, it is clear which Base
is to be used and you can even implicitly convert the pointer to Derived
to a pointer to Base
! If any address adjustment is needed the compiler will figure out how to do so using embedded pointer, a vtable, or whatever: the exact approach isn't prescribed by the C++ standard. What is done exact depends on the ABI. For example, for the Itanium C++ ABI it seems the offsets for virtual bases are store in the virtual tables.
Why static upcast with virtual inheritance is always correct for GCC?
Not sure, but it seems like you confuse upcasting with downcasting. In your code there are only upcasts, and those are fine. You get the expected compiler error for example with this:
D obj;
A* a4 = static_cast<A*>(&obj);
D* d = static_cast<D*>(a4);
gcc reports:
<source>: In function 'int main()':
<source>:26:28: error: cannot convert from pointer to base class 'A' to pointer to derived class 'D' because the base is virtual
26 | D* d = static_cast<D*>(a4);
| ^
If you remove the virtual inheritance in your example the static_cast
would fail as well, because then the cast is ambiguous.
Static cast - Cannot cast through virtual inheritance
class A {virtual ~A {}}
class B {virtual ~B {}}
class C : A, B {}
C o;
// Now, some implicit conversions:
A& a = c;
B& b = c;
// static casts back:
C& ca = static_cast<C>(a);
C& cb = static_cast<C>(b);
// dynamic casts over the type:
A& ab = dynamic_cast<A>(b);
B& ba = dynamic_cast<B>(a);
The dynamic cast can cast accross the hierarchy (or checked to derived types), while the static casts can only cast up and down the hierarchy, where statically proovable sound, aside from the object actually being of the target type.
Where possible, compilers reduce a dynamic cast to a static one for efficiency.
C++ cannot convert from base A to derived type B via virtual base A
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.
Downcasting in C++ with virtual base class
Is this simply impossible to do?
Yes.
How does the C++ standard (11 or 14) state that this is impossible?
It is stated under the requirements of static_cast
.
[expr.static.cast] - emphasis mine
11 A prvalue of type “pointer to cv1 B,” where B is a class type,
can be converted to a prvalue of type “pointer to cv2 D,” where D is a
class derived (Clause [class.derived]) from B, if a valid standard
conversion from “pointer to D” to “pointer to B” exists ([conv.ptr]),
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.
As you can see, Base
cannot be virtual or even a non-virtual base of another virtual base.
What is the underlying implementation of a standard compiler like gcc which makes this feature impossible?
Virtual inheritance is a mechanism that allows the same Base
subobject to be shared between different intermediate base classes. For instance, if you were to add:
class Derived2: public virtual Base {};
class MostDerived: public Derived, public Derived2 {};
Then MostDerived
will have a sub-object of type Derived
and type Derived2
, but unlike regular inheritance, they won't each have their own Base
sub-object (thus 2 Base
sub-objects in MostDerived
). Instead there will be only one Base
sub-object, and both Derived
and Derived2
will refer to it.
This is commonly accomplished by Derived
and Derived2
accessing the Base
indirectly through some pointer. They cannot rely on it being placed inside themselves at a certain offset. The location of their Base
is determined by the most derived object. So if you have a Base*
, there is no one well-determined way to go to the Derived
object that refers to it.
In the case of non-virtual inheritance, the Base
would be located at a place known to the compiler relative to the Derived
sub-object that contains it fully. So the compiler could do the pointer arithmetic required to obtain the Derived
sub-object. But in your case, it cannot, because there is no guarantee of relative placement. The access is (generally) indirect.
You didn't ask, but I might as well address that too. The reason a dynamic_cast
works for polymorphic classes, is that polymorphic classes can have RTTI associated with them in their vtable (if they have one). And because that Base*
will point to the vtable of MostDerived
(or Derived
), the dynamic_cast
mechanism can leverage information written in that table to figure out how to obtain the sub-object it needs. The table can contain all the offset information it needs to get from any one sub-object to any other sub-object. And because it is the table of the most derived object (at run-time), that information is fixed and reliable.
When is static cast safe when you are using multiple inheritance?
A cross-cast:
struct Base1 { virtual void f1(); };
struct Base2 { virtual void f2(); };
struct Derived : Base1, Base2 {};
Base1* b1 = new Derived();
Base2* b2 = dynamic_cast<Base2*>(b1);
requires use of dynamic_cast
, it cannot be done with static_cast
(static_cast
should have caused a compile-time error). dynamic_cast
will also fail if either base class is not polymorphic (the presence of virtual functions is NOT optional).
See this explanation on MSDN
Can you static_cast this to a derived class in a base class constructor then use the result later?
In my opinion this is well-defined according to the current wording of the standard: the C
object exists at the time of the static_cast
, although it is under construction and its lifetime has not yet begun. This would seem to make the static_cast
well-defined according to [expr.static.cast]/11, which reads in part:
... If the prvalue of type “pointer to cv1
B
” points to aB
that is actually a base class subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
It doesn't say that the D
object's lifetime must have begun.
We might also want to look at the explicit rule about when it becomes legal to perform an implicit conversion from derived to base, [class.cdtor]/3:
To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class
X
to a pointer (reference) to a direct or indirect base classB
ofX
, the construction ofX
and the construction of all of its direct or indirect bases that directly or indirectly derive fromB
shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member of an objectobj
, the construction ofobj
shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
According to this rule, as soon as the compiler starts constructing the base class A<C>
, it is well-defined to implicitly convert from C*
to A<C>*
. Before that point, it results in UB. The reason for this, basically, has to do with virtual base classes: if the path by which A<C>
is inherited by C
contains any virtual inheritance, the conversion may rely on data that are set up by one of the constructors in the chain. For a conversion from base to derived, if there is indeed any virtual inheritance on the chain, static_cast
will not compile, so we don't really need to ask ourselves the question, but are those data sufficient for going the other way?
I really can't see anything in the text of the standard, nor any rationale, for the static_cast
in your example not being well-defined, nor in any other case of static_cast
ing from base to derived when the reverse implicit conversion (or static_cast
) would be allowed (excepting the case of virtual inheritance, which as I said before, leads to a compile error anyway).
(Would it be well-defined to do it even earlier? In most cases this won't be possible; how could you possibly attempt to static_cast
from B*
to D*
before the conversion from D*
to B*
is allowed, without having obtained the B*
pointer precisely by doing the latter? If the answer is that you got from D*
to B*
through an intermediate base class C1
whose constructor has started, but there is another intermediate base class C2
sharing the same B
base class subobject and its construction hasn't started yet, then B
is a virtual base class, and again, this means the compiler will stop you from then trying to static_cast
from B*
back down to D*
. So I think there are no issues left to resolve here.)
How to downcast from non-polymorphic virtual base class?
There is an implicit unambigious conversion from MostDerived&
to its ViBase&
. A static_cast
can express such a conversion explicitly, and can also do the opposite conversion. That’s the kinds of conversions that static_cast
does.
As the OP noted a static_cast
down from virtual base is invalid.
The source code below illustrates why:
#include <iostream>
using namespace std;
struct B { virtual ~B(){} };
struct D: virtual B {};
struct E: virtual B {};
struct X: D, E {};
auto main() -> int
{
X x;
B& b = static_cast<E&>( x );
// Can't do the following for the address adjustment that would work for
// D sub-object won't work for E sub-object, yet declarations of D and E
// are identical -- so the address adjustment can't be inferred from that.
//
//static_cast<D&>( b );
// This is OK:
dynamic_cast<D&>( b );
}
Essentially, as this shows, you can't infer the address adjustment from the declaration of D
(or E
) alone. And neither can the compiler. This also rules out reinterpret_cast
.
Related Topics
Preparation for Std::Iterator Being Deprecated
Faster Bulk Inserts in SQLite3
Dynamic Aligned Memory Allocation in C++11
Detecting the Parameter Types in a Spirit Semantic Action
Vector Push_Back Calling Copy_Constructor More Than Once
Qt: Best Practice for a Single Instance App Protection
Why Is 'I = ++I + 1' Unspecified Behavior
When Should I Use Raw Pointers Over Smart Pointers
Relative Performance of Std::Vector VS. Std::List VS. Std::Slist
Are C++ Enums Signed or Unsigned
Detecting Constexpr with Sfinae
How to Recover View Space Position Given View Space Depth Value and Ndc Xy
What's the Behavior of an Uninitialized Variable Used as Its Own Initializer