c++templatesgeneric-programming

dynamic_cast Template class with "Known" Inheritance


I have a type that inherits from a template-defined type. The template-defined type is guaranteed to have a given base class. What I want to do is to be able to dynamic_cast or otherwise find types in a container that match my derived type, irrespective of the template parameter.

// A bunch of classes exist which inherit from Base already.
class Base{};
class Base2 : public Base {};
class BaseN : public Base {};

// Some new classes can inherit from any Base-derived class,
// but also have special attributes (i.e. function "f").
template<typename T = Base>
class Derived : public T
{
    static_assert(std::is_base_of<Base, T>::value, 
        "Class must inherit from a type derived from Base.")

    public:
        void f();
};

//
// Now process a collection of Base pointers.  
//

std::vector<Base*> objects;

// The vector may contain classes that are not "Derived".
// I only care about the ones that are.
// I want the cast here to return non-null for Derived<Base>, 
// Derived<Base2>, Derived<BaseN>, but null for Base, Base2, etc.

// This will be Null, Good.
objects.push_back(new Base)
auto dcast0 = dynamic_cast<Derived<Base>*>(objects[0]);

// This will be Non Null, Good.
objects.push_back(new Derived<Base>);
auto dcast1 = dynamic_cast<Derived<Base>*>(objects[1]);

// This will be Null, BAD! HELP!
objects.push_back(new Derived<Base2>);
auto dcast2 = dynamic_cast<Derived<Base>*>(objects[2]);

Solution

  • As suggested by Creris' in comments, you need a base class is that is common to all your Derived<T> template classes when T is not Base. In addition, the inheritance to Base itself should be virtual, so that there is only a single Base instance when Derived<> is instantiated.

    struct Base { virtual ~Base () = default; };
    struct Base2 : virtual Base {};
    
    struct DerivedBase : virtual Base {};
    
    template <typename BASE = Base>
    struct Derived : DerivedBase, BASE {};
    
        Base *b = new Derived<Base2>;
        assert(dynamic_cast<DerivedBase *>(b));
    

    However, you could use template specialization so that the common base class is actually Derived<Base> itself.

    struct Base { virtual ~Base () = default };
    struct Base2 : virtual Base {};
    
    template <typename = Base> struct Derived;
    
    template <>
    struct Derived<Base> : virtual Base {};
    
    template <typename BASE>
    struct Derived : Derived<Base>, BASE {};
    
        Base *b = new Derived<Base2>;
        assert(dynamic_cast<Derived<> *>(b));
    

    Try it online!