The two existing solutions trade dynamic polymorphism for static polymorphism. Without more details on the problem at hand, it is not possible to know whether that is a valid approach or not, as it basically breaks the polymorphic hierarchy: with CRTP there is no single base class, but rather a family of them. You cannot hold objects of Derived1
and Derived2
in the same container as they are unrelated... It is a fine solution if all you need is to share the code, but not if you need dynamic polymorphism. Take a look at the Visitor pattern and at double-dispatch for similar problems.
If you need dynamic polymorphism, you could try to implement double dispatch (it is a pain, but feasible if the hierarchy is small enough. Basically create two different hierarchies, one rooted at Base
and another that serves as some short of manual dispatcher. The hierarchy rooted at Base
will have a virtual method apply
, and the second hierarchy will have virtual functions for each one of the types in the first hierarchy:
class Base;
class Derived1; // inherits from Base, implements Visitor
class Derived2; // inherits from either Base or Derived2
struct Visitor {
virtual void visit( Base& ) = 0; // manually unrolled for all types
virtual void visit( Derived1& ) = 0;
virtual void visit( Derived2& ) = 0;
};
struct Base {
virtual void apply( Visitor& v ) { // manually replicate this in Derived1, 2
v.visit( *this );
}
template <typename T> void foo(T); // implement
};
template <typename T>
struct FooCaller : Visitor {
T& ref_value;
FooCaller( T& v ) : ref_value(v) {}
template <typename U> void call_foo( U& o ) {
o.foo(ref_value);
}
virtual void visit( Base & b ) { call_foo(b); }
virtual void visit( Derived1 & d1 ) { call_foo(d1); }
virtual void visit( Derived2 & d2 ) { call_foo(d2); }
};
The names I have used are common in the Visitor pattern, and this approach is quite similar to that pattern (I don't dare call it the Visitor pattern, but the approach is similar, so I just borrowed the naming convention).
User code would be similar to:
int main() // main returns int, not void!!!
{
Base* BasePtr = new Derived1();
int i = 5;
FooCaller<int> c(i)
BasePtr->apply(c); // [1] magic happens here
}
The requirement of declaring i
and c
before hand can be released by changing (if possible) the arguments to the functions from references to const-references. The actual magic is that in [1] the C++ single dispatch mechanism sill dispatch the call to Derived1::apply
, since that is the dynamic type of the object pointed by BasePtr
. At that point it will call Visitor::visit( Derived1& )
with itself as the argument. That will again be dispatched through the single dispatch mechanism to FooCaller<int>::visit( Derived1& )
, and at that point both objects have been resolved to their static types. When FooCaller<int>::visit
calls call_foo
the argument U
is deduced to be Derived1
, when it calls Derived1::foo
the argument is deduced to be int
and it ends up calling Derived1::foo<int>
... though a couple of loops and indirections...
Depending on your particular use case this might be too complex (if static polymorphism like CRTP would work) or too hard to maintain (if the hierarchy is big: for each new element in the Base
hierarchy you will have to update all types in the hierarchy of the Visitor
), so if you can avoid this complexity, perfect. In some cases, though, you need this.
Also note that this is the most complex fully dynamic solution, there are other options in between, depending on what is it that you need to be runtime polymorphism... It might be the case that your hierarchy models a visitor of shorts, and that you only need to manually unroll the different virtual functions that will dispatch to the template internally, in which case half of the above complexity will be gone.
Also note that this is quite unusual in C++, and that if you explain the actual problem at hand, there might be better simpler solutions, what you have stated are the requirements of your solution to the original problem: dynamically dispatch to a template.
dynamic_cast
to determine actual type of youBasePtr
object.dynamic_cast<Derived1>(BasePtr)
returnsNULL
if BasePtr isn't pointed toDerived1
class object andDerived1*
otherwise. After getting pointer toDerived1
you can call it's member functions. – Ultraconservative