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