Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to downcast from non-polymorphic virtual base class?

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.

like image 754
Angew is no longer proud of SO Avatar asked Jul 23 '14 20:07

Angew is no longer proud of SO


People also ask

Is it possible to use dynamic_cast on a class that is not polymorphic?

<< std::endl; } Note that dynamic_cast is not possible on a class that is not polymorphic. You'd need at least one virtual function in the class or its parents to be able to use it.

What is a virtual base class in C++?

Virtual base class in C++. Virtual base classes are used in virtual inheritance in a way of preventing multiple “instances” of a given class appearing in an inheritance hierarchy when using multiple inheritances. Need for Virtual Base Classes: Consider the situation where we have one class A .This class is A is inherited by two other classes B...

How do you create a virtual base class in Java?

Syntax for Virtual Base Classes: Syntax 1: class B : virtual public A { }; Syntax 2: class C : public virtual A { }; Note: virtual can be written before or after the public. Now only one copy of data/function member will be copied to class C and class B and class A becomes the virtual base class.

Do I need a downcast for a function?

If you don't need any downcast, your design would be perfect. However, you may need sometimes to downcast. A typical example is when you want to invoke a non virtual function that exist only for the child class. Consider for example circles.


2 Answers

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.

like image 178
Cheers and hth. - Alf Avatar answered Nov 15 '22 00:11

Cheers and hth. - Alf


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.

like image 38
jxh Avatar answered Nov 15 '22 01:11

jxh