5

Is there a way to downcast from a virtual base class to a derived class when there are no virtual functions involved? Here's some code to demonstrate what I'm talking about:

struct Base1
{
  int data;
};

struct Base2
{
  char odd_size[9];
};

struct ViBase
{
  double value;
};


struct MostDerived : Base1, Base2, virtual ViBase
{
  bool ok;
};


void foo(ViBase &v)
{
  MostDerived &md = somehow_cast<MostDerived&>(v);  //but HOW?
  md.ok = true;
}


int main()
{
  MostDerived md;
  foo(md);
}

Please note that the code is for demonstration only. My real scenario is fairly complex and involves template parameters and casting from one to another, knowing only that the first one is a base of the second one; it can be a normal or virtual base and it may or may not have virtual functions. (See simplified example at the bottom). I can detect the polymorphic case and the virtual/non-virtual base case using type traits, and solve all of them except the non-polymorphic virtual base. So that's what I'm asking about.

I can't really think of a way to do the cast:

  • Implicit conversions are right out; these only do upcasts.

  • static_cast is explicitly forbidden for casting from a virtual base class:

    5.2.9/2 ... and B is neither a virtual base class of D nor a base class of a virtual base class of D. ...

  • dynamic_cast can't do it either, as downcasts require a polymorphic class

    5.2.7/6 Otherwise, v shall be a pointer to or a glvalue of a polymorphic type (10.3).

    10.3/1 ... A class that declares or inherits a virtual function is called a polymorphic class.

  • reinterpret_cast doesn't apply here at all.

If MostDerived had at least one virtual function, this could of course be solved with dynamic_cast. But when it does not, is there a way to do the cast?

(NOTE All quotes are taken from C++11 draft N3485)


In light of comments focusing on the above example code too much, here's a sketch of what my real situation is:

template <class T_MostDerived>
struct Bar
{
  template <class T_Base>
  void foo(T_Base &b, typename std::enable_if<std::is_base_of<T_Base, T_MostDerived>::value>::type * = nullptr)
  {
    T_MostDerived &md = somehow_cast<T_MostDerived>(b);
    do_stuff_with(md);
  }
};

That is, I know that T_Base is a base class of T_MostDerived (and I know that T_MostDerived is really the most derived type), but I don't know anything else about them; Bar is my code, part of a library, which unknown clients can use. I can detect that it's a non-polymorphic virtual base, but I can't cast it in such case.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Why exactly can't you make `ViBase` polymorphic? (e.g., you could make its destructor virtual) – isekaijin Jul 23 '14 at 20:27
  • 3
    If ViBase is not polymorphic, why use it as a virtual base class? – Niall Jul 23 '14 at 20:27
  • Remove the `virtual` keyword. Then reinterpret happily the references/pointers as you are now dealing with plain structs... – Anonymous Jul 23 '14 at 20:28
  • 1
    As long `Base1` and `Base2` are unrelated to `ViBase`, which effect should the `virtual` inheritance take here? – πάντα ῥεῖ Jul 23 '14 at 20:31
  • @Niall I think it doesn't necessarily to be polymorphic to be used with `virtual` inheritance. That should only guarantee it's initialized once, if found multiple times in the `MostDerived`'s inheritance tree, as far as I understood the feature so far. – πάντα ῥεῖ Jul 23 '14 at 20:37
  • @πάνταῥεῖ I've edited the question a bit - the code is only to demonstrate what exactly I'm talking about. In my real situation, the classes are outside my control. – Angew is no longer proud of SO Jul 23 '14 at 20:51
  • @Angew From my interpretation, `virtual` will guarantee you'll get an unambiguously resolved _'instance'_ of your base class if using a `dynamic_cast(this)` (where this refers to `MostDerived`). – πάντα ῥεῖ Jul 23 '14 at 20:58
  • I'm reading from n3797, 5.2.7/2, If T is an lvalue reference type, v shall be an lvalue of a complete class type,and the result is an lvalue of the type referred to by T. I think this applies here, so I would think dynamic_cast is what you need. – Niall Jul 23 '14 at 21:04
  • @Niall Unfortunately, point 5 of that section solves upcasts only, and then point 6 says "otherwise, `v` shall be polymorphic" (exact quote is in the question). – Angew is no longer proud of SO Jul 23 '14 at 21:09
  • @Angew: thanks for teaching me something new today also. each day I learn something a new I can rejoice that I'm still a thinking being. – Cheers and hth. - Alf Jul 23 '14 at 21:24
  • 1
    I think you may be out of luck, http://stackoverflow.com/a/10524622/3747990. Frankly though, I think you should be able to do what you want to do. Consider approaching the committee on this. – Niall Jul 23 '14 at 21:29
  • In your sketch of your actual problem, are you the one that define `T_MostDerived`, or is that still framework stuff you don't own? Also, I am assuming you are in full control over the implementation of `foo()`? – jxh Jul 23 '14 at 23:24
  • @jxh Yes, I am in full control of `foo()`, but that's about it. What I am writing is a library, so `Bar` is the end product given away to unknown clients who can instantiate it with anything they wish. – Angew is no longer proud of SO Jul 24 '14 at 05:09

2 Answers2

4

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • True (and +1), but it's more of an observation. Shall I read the answer as "there is no way?" Perhaps you could state it more explicitly? – Angew is no longer proud of SO Jul 23 '14 at 21:47
  • 1
    @Angew: Since we have exhausted all means of casting with well-defined effect down from non-polymorphic virtual base class, there is no such. That doesn't mean "no way", but it means that whatever you do will have some cost, e.g. portability (for formally UB code) or complexity and memory footprint (for dynamically registering all `Base` objects), or efficiency (for scanning for known pattern). I think the main issue to focus on is that a non-polymorphic virtual base class as the statically known type, indicates a design-level failure to retain proper type. As I see it. – Cheers and hth. - Alf Jul 23 '14 at 22:10
2

This requires a hack. A downcast requires math since multiple inheritance may put the base class in some arbitrary position within the derived class. However, if you know the base class is virtually inherited, then there should only be one instance of it in the derived class. This means you can create a conversion function:

struct MostDerived : Base1, Base2, virtual ViBase
{
  bool ok;
  template <typename T> static MostDerived * somehow_cast (T *v) {
    static MostDerived derived;
    static T &from = derived;
    static size_t delta
      = reinterpret_cast<char *>(&from) - reinterpret_cast<char *>(&derived);
    char *to = reinterpret_cast<char *>(v);
    return reinterpret_cast<MostDerived *>(to - delta);
  }
};

What the special C++ casts give you that this function does not is type safety. This function blindly assumes that the passed in ViBase has an appropriate derived child to cast into, which is generally not the case.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • 1
    A constraint here is that it must be known that the `*v` object's most derived object is really a `MostDerived` and not a class derived from `MostDerived`. – Cheers and hth. - Alf Jul 23 '14 at 21:36
  • @Cheersandhth.-Alf +1, true in general. Fortunately, in my real case, I will know that for certain. – Angew is no longer proud of SO Jul 23 '14 at 21:43
  • +1; that's exactly what I plan to do. Unfortunately, it's technically undefined behaviour. – Angew is no longer proud of SO Jul 23 '14 at 21:44
  • 1
    If you want to avoid undefined behavior, you will need to create a lookup table that maps from `ViBase` to `MostDerived` for you. – jxh Jul 23 '14 at 21:56
  • Is there any way to avoid the need for MostDerived to be default-constructible? And how exactly would a lookup table avoid UB? Wouldn't you still need to invoke UB when populating the table? – celticminstrel Dec 14 '21 at 19:28
  • @celticminstrel C uses a similar kind of computation for `offsetof()`, and it uses NULL pointer cast to the containing (`MostDerived`) structure. I was uncomfortable presenting that, since a compiler is allowed to do funny math with NULL, but regular code is not really supposed to. – jxh Dec 14 '21 at 23:41
  • @celticminstrel A lookup table would be populated at the time the `MostDerived` was instantiated. So, there would not be any UB. So if you had `unordered_map`, then it would be populated from the `MostDerived` constructor like: `map[this] = this;` – jxh Dec 14 '21 at 23:43