1

We have a multiple inheritance hierarchy:

// A  B
// \ /
//  C
//

Both A and B are abstract classes. C is actually a templated class, so downcasting is near impossible if you have a B and you want to access a member of it as an A.

All A's and B's must be C's, since that is the only concrete class in the hierarchy. Therefore, all A's must be B's, and vice versa.

I'm trying to debug something quickly where I have a B that I need to access A::name of. I can't downcast to C because I don't know the templated type of it. So I'm writing code like below and surprisingly it doesn't work; and I'm wondering what gives.

struct A { virtual void go() = 0; std::string name; };
struct B { virtual void go() = 0; };
struct C : A, B { void go() override { } };

int main()
{

    C c;
    c.name = "Pointer wonders";
    puts(c.name.c_str()); // Fine.
    B* b = (B*)&c;
    //puts(b->name.c_str()); // X no from compiler.

    A* a1 = (A*)&c;
    puts(a1->name.c_str()); // As expected this is absolutely fine

    // "Cross" the hierarchy, because the B really must be a __C__, because of the purely virtual functions.
    // neither A nor B can be instantiated, so every instance of A or B must really be a C.    
    A* a2 = (A*)b;
    puts(a2->name.c_str()); // Why not??

    // If you downcast first, it works
    C* c2 = (C*)b;
    A* a3 = (A*)c2;
    puts(a3->name.c_str()); // fine

}
bobobobo
  • 64,917
  • 62
  • 258
  • 363
  • 2
    Does this answer your question? https://stackoverflow.com/a/37865380/1466970 – Richard Chambers May 06 '22 at 01:06
  • 3
    Your statement, *"All A's and B's must be C's, since that is the only concrete class in the hierarchy."* is only true because of the existence of C and its derivation. Pretend the *code* is agnostic to your design wishes, and rather take C out of the equation. Just look at A and B in a vacuum. They are utterly unrelated. You can either give them both a common base and then down+up or give them a common derivation and then up+down (which is what your "what works" does, albeit brutally with C-style casts). – WhozCraig May 06 '22 at 01:07
  • 2
    See as well https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – Richard Chambers May 06 '22 at 01:07
  • 3
    If all As and Bs must be Cs, then why have A and B at all? – Kevin May 06 '22 at 01:15
  • 2
    Consider that your hierarchy is not necessarily sealed - IIUC nothing prevents you from loading a dynamic library, or linking statically, or whatnot, against a different translation unit that *does* include other classes derived from A but not B but vice versa. – nanofarad May 06 '22 at 01:20
  • Having to cross the hierarchy is either a red flag for something wrong with the class design (e.g. `B` should drive from `A`, or `A` and `B` should both drive virtually from `NameClass`), or that the code having to do the casts should have stored a `C` in the first place. Or perhaps the interface for getting `name` should not be a public member variable, but a virtual function of a common ancestor of `A` and `B` (e.g. a `dump` function, which works for classes with names and for nameless classes). – Sebastian May 06 '22 at 03:48
  • You wrote it is for debugging purposes. Then first casting to `C` is the right way, as you know `C` is the implementation. Casting back to `A` is unnecessary, because `C` inherits `name`. You should use `dynamic_cast` (compiler possibly does runtime check of type and validity of conversion) or `static_cast` (if you know it is a `C` and the inheritance is non-`virtual`). – Sebastian May 06 '22 at 03:56
  • 1
    You wrote `C` is templated, so `C` is actually not uniquely known. Either manually store a pointer to a `A` in `B`, which is initialized by the constructor of `C` with `this`; or use a side-cast (or cross-cast) with `dynamic_cast`. See https://stackoverflow.com/questions/35959921/what-is-side-cast-or-cross-cast-in-dynamic-cast-in-c/51669399#51669399 – Sebastian May 06 '22 at 04:23

1 Answers1

2

First of all, stop using C style cast. The compiler won't complain if you do something wrong (C style cast usually do not works in multiple inheritance).

Any cast that cause run-time error in you example would not compile with a static_cast. While it is a bit longer to type, you get instant feedback when used improperly instead of undefined behavior that will sometime corrupt data and cause problem long afterward when that data is use.

As A and B contains virtual function, you can easily use dynamic_cast without knowing C. If you know C, you could use static_cast to C if you know there is a derived C for sure. But why not use virtual functions and not do any crossing between siblings?

The reason it does not works is because C-style cast can do any of the following cast:

  • static_cast
  • reinterperet_cast
  • const_cast

Also, C style cast will do a reinterpret_cast if the definition of a class is missing. You also need to be very careful with void *as you must convert back to original type.

As a simplified rule, you can imagine that C cast is like doing either a single static_cast (known child or parent class or primitive types like int) or reinterpret_cast (unknown type, not a parent/child class) followed by a const_cast if necessary.

C * --> void * --> B * won't work with any C or C++ cast.

Th primary reason that such cast don't works is that the compiler must adjust this pointer when doing a cast and multiple inheritance is used. This is required to take into account that the A and B part start at a distinct offset.

Alternatively, you can add a virtual function A * GetA() = 0 in B and implemente it in C to have your own way to navigate. That can be an option if is unknown and RTTI must be disabled (for ex. on embedded systems).

Honestly, you should avoid multiple inheritance and casting as it make the code harder to maintain as it increase coupling between classes and it can cause hard to find bug particularily when mixing both together.

Phil1970
  • 2,605
  • 2
  • 14
  • 15