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.

13
  • Why exactly can't you make ViBase polymorphic? (e.g., you could make its destructor virtual) Commented Jul 23, 2014 at 20:27
  • 3
    If ViBase is not polymorphic, why use it as a virtual base class? Commented Jul 23, 2014 at 20:27
  • Remove the virtual keyword. Then reinterpret happily the references/pointers as you are now dealing with plain structs... Commented Jul 23, 2014 at 20:28
  • 1
    As long Base1 and Base2 are unrelated to ViBase, which effect should the virtual inheritance take here? Commented Jul 23, 2014 at 20:31
  • 1
    I think you may be out of luck, 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. Commented Jul 23, 2014 at 21:29

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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: 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.
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.

7 Comments

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.
@Cheersandhth.-Alf +1, true in general. Fortunately, in my real case, I will know that for certain.
+1; that's exactly what I plan to do. Unfortunately, it's technically undefined behaviour.
If you want to avoid undefined behavior, you will need to create a lookup table that maps from ViBase to MostDerived for you.
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?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.