1

After learnt from : Why can't static_cast be used to down-cast when virtual inheritance is involved?

I'm expecting following code give me the result that shows the static_cast is wrong and dynamic_cast is right.

#include <stdio.h>
class A {
 public:
  virtual ~A() {}
  int a;
};

class B : public virtual A {
  int b;
};

class C : public virtual A {
  int c;
};

class D : public B, public C {
  int d;
};

int main() {
  D obj;
  A* a1 = &obj;
  A* a2 = (A*)&obj;
  A* a3 = dynamic_cast<A*>(&obj);
  A* a4 = static_cast<A*>(&obj);

  C* c = &obj;
  A* a5 = c;
  A* a6 = (A*)(c);
  A* a7 = dynamic_cast<A*>(c);
  A* a8 = static_cast<A*>(c);

  B* b = &obj;
  A* a9 = b;
  A* a10 = (A*)b;
  A* a11 = dynamic_cast<A*>(b);
  A* a12 = static_cast<A*>(b);

  printf("D: %llx %llx %llx %llx %llx\n", &obj, a1, a2, a3, a4);
  printf("C: %llx %llx %llx %llx %llx\n", c, a5, a6, a7, a8);
  printf("B: %llx %llx %llx %llx %llx\n", b, a9, a10, a11, a12);
}

However, gcc 8/9 gives me following reuslt, showing both static cast and dynamic cast is correct:

D: 7ffddb098a80 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0
C: 7ffddb098a90 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0
B: 7ffddb098a80 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0 7ffddb098aa0

So what's the magic involved here? How can I make a case that reproduce the static_cast problem?

Note: gcc8 with -fdump-lang-class gives me following output:

Class D
   size=48 align=8
   base size=32 base align=8
D (0x0x7f0756155000) 0
    vptridx=0 vptr=((& D::_ZTV1D) + 24)
  B (0x0x7f0755f834e0) 0
      primary-for D (0x0x7f0756155000)
      subvttidx=8
    A (0x0x7f075614c0c0) 32 virtual
        vptridx=40 vbaseoffset=-24 vptr=((& D::_ZTV1D) + 104)
  C (0x0x7f0755f83548) 16
      subvttidx=24 vptridx=48 vptr=((& D::_ZTV1D) + 64)
    A (0x0x7f075614c0c0) alternative-path
timrau
  • 22,578
  • 4
  • 51
  • 64
Cauly
  • 370
  • 1
  • 2
  • 12
  • 4
    Upcasting and downcasting are two different things – UnholySheep Jul 22 '22 at 08:16
  • 2
    the question is unclear. Title states that downcast is problematic, then you have some code with upcast, then ask how to reproduce the problems. DId you expect to see the same issue with up- as with downcast? – 463035818_is_not_an_ai Jul 22 '22 at 08:17
  • are you asking why upcast is fine while downcast may be not ? – 463035818_is_not_an_ai Jul 22 '22 at 08:17
  • 1
    When upcasting the compete definitions of all base classes is available at compile time and the compiler can therefore check the validity of any `static_cast`. When down casting the compiler does not know and cannot check __at compile time__ that the cast to the derived object is valid thus we need `dynamic_cast` which includes a runtime check. – Richard Critten Jul 22 '22 at 08:34
  • So can I conclude that, in any situations, static cast is reliable for virtual inheritance? It seems conflict from what I learnt from https://stackoverflow.com/a/44466962/376698, which says: "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. " – Cauly Jul 22 '22 at 09:08
  • You should start by carefully describing your mental model of casts and your expectations of what they can or can't do. What do you believe the compiler is allowed to do for a `static_cast`? – curiousguy Aug 02 '22 at 05:12

2 Answers2

2

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

static_cast can correctly upcast to a virtual base class because the standard requires it to. In fact, this conversion can even be done implicitly, and static_cast can perform all implicit conversions as well as certain other kinds of conversions.

When compiling a static_cast (or an implicit conversion) to a virtual base class, the compiler must generate code that consults the vtable in order to determine how to find the virtual base class, unless it can determine the dynamic type of the argument in which case the conversion can be "devirtualized", i.e., the statically known offset can be used.

A misconception is that static_cast is "static" in the sense that it never needs to consult a vtable. Evidently, this is not the case, but we are stuck with the name.

Another misconception is that conversion to a virtual base class requires "RTTI" and thus requires dynamic_cast. It does not---just like how calling a virtual function does not require RTTI and can still be done even when RTTI has been disabled.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 1
    The way virtual _bases_ is implemented can vary a lot more than the rest of the inheritance and virtual functions stuff which in practice is always done pretty much the same way (I can't stand ppl saying "the vtable is just one possible implementation for virtual functions" when it's the one and only implementation used in practice). Not all compilers go through the vtable for access to virtual bases. – curiousguy Aug 02 '22 at 05:15