1

I am currently working to implement a "safe cast" for a small subset of my project, using a meta-type system, because a) RTTI and dynamic_cast was intentionally disabled in my project environment and b) such functionality would greatly simplify and clarify the code.

In my project, there is a multi-level class hierarchy, with multiple inheritance in some cases. There is, however, a single “root” class that is virtually inherited, directly or indirectly, by all other classes. (This is the only virtual inheritance in the hierarchy.) Is there a safe, legal way to perform downcasts in such a hierarchy, without using RTTI / dynamic_cast? I have searched the web and considered several approaches, but none seem to hit the mark. In my case, the class hierarchy is known - every object knows their (meta)type and their ancestors, essentially via the use of tags/enums. (Is there other information, such as base class initialization order, that must be known to implement a “safe cast”?)

One approach I tried was to downcast via a virtual function call that returns the “this” pointer. From what I've read (and my own experience), I understand that the "this" pointer in C++ is of the type corresponding to the class in which the member function is defined, with the cv qualifications from the member function applied to it. (As described here => Type of 'this' pointer). I tried to get the pointer having the dynamic (run-time) type of an object by returning the "this" pointer. Since I want all my objects to implement this function, I make it a pure virtual function in my base class, and then I define it in my derived classes using a covariant return type (a pointer of the implementing class). Dispatching through the base pointer works as expected, but the returned pointer seems to have the type based on the invoking pointer, not the type of the invoked implementation.
Here's code that illustrates the problem:

#include <iostream>
struct Base { 
   virtual ~Base(){} 
   virtual Base* foo( void ) = 0 ; 
} ;
struct Derived : public Base { // <= XXX
   Derived* foo( void ){ std::cout << "In Derived::foo()" << std::endl ; return this ; } 
   void foo2( void ) { std::cout << "In Derived::foo2()" << std::endl ; }
} ;
int main ( int argc, char* argv[] ) {
   Base* base = new Derived ;
   base->foo( ) ;  // Dispatches to Derived::foo()
//   Derived* derived = base->foo( ) ;  // PROBLEM!
   Derived* derived = static_cast< Derived* >( base->foo( ) ) ;  // Works, as long as inheritance at XXX is non-virtual
   derived->foo2( ) ;
   delete base ;
   return 0 ;
}

As written, the above code compiles and runs without problems. But if I uncomment the line labeled "PROBLEM" and comment out the line below it, I get the following compiler error (when using g++ version 4.83 in a cygwin environment - NOT my ultimate target environment):

test.cpp: In function ‘int main(int, char**)’:
test.cpp:13:34: error: invalid conversion from ‘Base*’ to ‘Derived*’ [-fpermissive]
    Derived* derived = base->foo( ) ;  // PROBLEM!
                                  ^

I have tried this with versions of icc, clang, and g++ at http://gcc.godbolt.org/ and received similar results, so I believe this is not a compiler bug and I am missing something very fundamental.

What is the type of a returned "this" pointer from a virtual member function definition with a covariant return type? When I invoke foo() via a Base*, I obviously execute the implementation of Derived::foo(), and that implementing member function is declared to return a Derived*. So why is a downcast necessary to perform the assignment from the returned pointer? Using static_cast is a problem because I do have a virtually inherited base class in my project code, and static_cast will fall down on that. I am under the impression that using a C-style cast or reinterpret_cast for a downcast from a base class is not safe (at least, in the general case), as they are “blind” to object data / virtual table layout. Can these be made “safe” by following some rules/restrictions?

Community
  • 1
  • 1
youngmj
  • 109
  • 8
  • 4
    why would one intentionally disable RTTI? – Walter Dec 04 '14 at 18:06
  • you could use a visitor pattern to discover the type of the object - or change your design: since you disabled RTTI, something tells me you wanted to force your design away from inheritance, which is a valid thing to do – BeyelerStudios Dec 04 '14 at 18:06
  • 2
    "Without `dynamic_cast`" is kinda like "how do I open the doors to my flat without using the keys I have to it?" - entirely pointless. – Griwes Dec 04 '14 at 18:56
  • @Walter - It was not my decision to disable RTTI. I realize that my post may have made it sound like I had more control ("my project"), but, in reality, I cannot affect that decision. As to "why?" - there is a perception, particularly in embedded circles, and it may have been true at one time, that RTTI and exceptions bloated object code. I certainly don't agree with that thinking. There are macros in the code that work one way with RTTI and a different way when it's disabled - after 10 years, the product platform I'm working on simply cannot risk turning it back on and breaking the world. – youngmj Dec 04 '14 at 19:04
  • @youngmj they do, that said they are rarely loaded. In systems with virtual memory (of any sort) it's usually not an issue. That said, they usually provide more benefits than detractions. I'd be more concerned about the heavy macro usage, as yous should be able to do almost everything with templates at this point. – Mgetz Dec 04 '14 at 19:08
  • @Griwes - While I agree that I'm fighting with one arm tied behind my back, I don't think this is entirely pointless. We are all constrained by the tools we have available to us, and most of us don't control world we live in. I would love to have and use dynamic_cast, but the reality is, I don't have it and can't use it. I don't think I'm alone in this; I believe there are several (particularly embedded) environments where RTTI isn't available. – youngmj Dec 04 '14 at 19:10

1 Answers1

2

A covariant return type allows a subclass to return a type different from but inherited from the return type of the base class. This allows you to get a different value returned if you call from the derived class, but you get the correct type when called through the base class.

So in your example, the value returned by Derived is of type Derived, but when you're calling that function through a Base pointer, the compiler has to decide at compile time whether the returned value can be legally assigned. At runtime the correct instance of foo() will be called since foo() is declared virtual, but the compiler has to use the same return type no matter what subclass of Base is actually used at runtime. That's why covariant return types can't be completely arbitrary. That is, you can't return an int in your base class and a string in your derived class. The return type of the derived class has to be a subtype of the type returned in the base class.

It's probably worth noting here that while you were able to cast the return type and have everything compile correctly, you could also have cast the pointer and also gotten the correct value:

Derived* derived = static_cast< Derived* >(base)->foo( ); // OK

Now to answer your original question, if your objective is to start with a base pointer of unknown type and downcast to a derived pointer, you will need some runtime information. That's what RTTI does, using the information stored with the vtable to determine the actual type. If you don't have RTTI but you have some embedded enumeration, then you could certainly use that. I can't see your classes, but something like this would work:

class Base
{
    int myId;

protected:
    Base(int id) : myId(id)

public:
    template <class T>
    T* downcast()
    {
        if (myId == T::ID) { return static_cast<T*>(this); }
        return nullptr;
    }
};
class Derived1
{
public:
    static const int ID = 1;
    Derived1() : Base(ID) {}
};
class Derived2
{
public:
    static const int ID = 2;
    Derived2() : Base(ID) {}
};


Base* b = new Derived1();

Derived1* d1 = b->downcast<Derived1>(); // Ok, returns a value
Derived2* d2 = b->downcast<Derived2>(); // returns nullptr

Note: this was an attempt to answer the question I thought you were asking, but I don't actually think this is a good idea. As suggested in the comments, I would prefer to use a visitor pattern than to cast and check the type, or implement the behavior in virtual methods, or some other thing, even if I did have RTTI available.

More clarification: the point of this whole workaround is based on the assumption that you are holding onto a Base* and want to cast it to a Derived* but you aren't sure if the object being held actually is a Derived* and you want some sort of check done, the way dynamic_cast does. If you know what the derived type is and just want to cast it, static_cast is the way to go. static_cast won't check to make sure the runtime type you hold in the base pointer is actually the type that you're casting to, but it will do a compile time check to ensure the cast is legitimate. So you can't for instance use static_cast to cast from an int* to a string*. reinterpret_cast will allow this, but then you're just forcing what is probably a Bad Thing and you don't want to do that.

Jay Miller
  • 2,184
  • 12
  • 11
  • the "something like this" you're describing is run time type info: you can only evaluate it (the member variable stored in the object) at runtime! – BeyelerStudios Dec 04 '14 at 18:02
  • 1
    This emulates RTTI. The question is: is this more efficient than using `typeid`? – Walter Dec 04 '14 at 18:07
  • 1
    This emulates RTTI _poorly_. I suspect it's slower, and I think this code fails for virtual inheritance, and definitely fails for a inheritance tree more than one deep. I think you'll have to have the pointer casting logic in a virtual function implemented either in all derived classes, or in CRTP, or some other neat trick. – Mooing Duck Dec 04 '14 at 18:07
  • I never said this was a *good* improvement over RTTI, but the OP said he didn't have access to RTTI as is looking for a workaround. If you are downcasting from a base type of unknown derived type, you have no choice but to have some kind of runtime information. Any other trick with CRTP or whatnot will work only if you actually know the derived type, in which case `static_cast` will work fine and you don't need a special safe cast. – Jay Miller Dec 04 '14 at 18:27
  • @Jay Miller - I tried your code - add a body to the Base ctor, and specify the inheritance in Derived definitions. The problem is when you do the latter, and make it ": public virtual Base", the static_cast is invalid and will cause compilation errors. – youngmj Dec 04 '14 at 19:21
  • The comments on covariant return types is making me think - so, the return type is determined according to the static (compile-time) type of the invoking pointer, not the actual (run-time) type that's being returned or the variable type that return value is assigned to. Where in the C++ standard is this covered? – youngmj Dec 04 '14 at 19:30
  • § 10.3.8: If the class type in the covariant return type of D::f differs from that of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D. When the overriding function is called as the final overrider of the overridden function, its result is converted to the type returned by the (statically chosen) overridden function. – Jay Miller Dec 04 '14 at 20:22
  • And sorry, I had missed the "virtual inheritance" part of the question, and it's true, this does fail for virtual inheritance. A useful SO question there is http://stackoverflow.com/questions/7484913/why-cant-static-cast-be-used-to-down-cast-when-virtual-inheritance-is-involved. If you must use virtual inheritance then I see no way around `dynamic_cast` to get this effect. Virtual inheritance is another code smell, though. You should think hard about whether it is really necessary, or whether you can get the same results from encapsulation instead of inhertance. – Jay Miller Dec 04 '14 at 20:26
  • Refactored the code base and eliminated the virtual inheritance - not a perfect design, but it works. Used a "tag", but also a static map mapping the tag value to a set of parent tags to allow for multilevel inheritance, and then used static_cast, similar to described here. Not quite what I had hoped for, but it will work for my immediate purposes. – youngmj Dec 05 '14 at 13:35