c++template-meta-programmingcrtptypelistc++-loki

c++ Typelist of derived class


Using CRTP (curiously recurring template pattern) you can provide a base class with knowledge of classes derived from it. It is not that hard to create an array that stores an instance of each class that derives from a base class (see example)

class Base{
    public:
        static std::vector<Base *> m_derivedInstances;
};

template <class Derived>
class CRTPBase : public Base {
    public:
        static bool m_temp;
        static bool addInstance()
        {
            m_derivedInstances.push_back(new Derived);
            return true;
        }
};
template <class Derived>
CRTPBase<Derived>::m_temp = CRTPBase<Derived>::addInstance();

I was wondering if it was possible to create a Typelist (see http://www.research.ibm.com/designpatterns/pubs/ph-jun00.pdf) of all the types of derived classes. The problem is that every time the compiler sees a new class that derives from Base it will need to append a new type to the list, but Typelists are immutable (it is possible to create a new list with the new type appended to it, but adding an element to a list is impossible as far as I know. In the end I would like to have something like this:

struct DerivedClassHolder {
    typedef Loki::TL::MakeTypeList</*list all derived classes here*/>::Result DerivedTypes;
};

The ultimate goal is to be able to iterate over all the classes that derive from Base.


Solution

  • It can be done, using a pseudo type-map. Here is some example code using boost::mpl. The explicit definition of "Implem" can be done with a macro in each corresponding implem header.

    #include <iostream>
    #include <boost/mpl/vector.hpp>
    #include <boost/mpl/eval_if.hpp>
    #include <boost/mpl/identity.hpp>
    #include <boost/mpl/for_each.hpp>
    #include <boost/mpl/push_front.hpp>
    #include <boost/mpl/empty_sequence.hpp>
    #include <boost/type_traits/is_same.hpp>
    
    using namespace boost::mpl;
    using namespace boost;
    
    
    // A type map. Implem #N of type Key is type (default: void)
    
    template <typename Key, int N>
    struct Implem
    {
      public:
        typedef void type;
    };
    
    
    // Type vector building functions
    // void, the default type, is used to stop the recursion
    
    template <typename Key, int N = 1>
    struct ImplemToList;
    
    template <typename Key, typename Item, int N>
    struct ImplemListItem
    {
      public:
        typedef typename push_front<typename ImplemToList<Key, N + 1>::type, Item>::type type;
    };
    
    template <typename Key, int N>
    struct ImplemToList
    {
      public:
        typedef typename Implem<Key, N>::type item;
        typedef typename eval_if<is_same<item, void>,
                                 identity<vector<> >,
                                 ImplemListItem<Key, item, N> >::type type;
    };
    
    
    // Example code: an interface with two implems
    
    class Interface
    {
      public:
        virtual const char* name() const = 0;
    };
    
    class Implem1 : public Interface
    {
      public:
        virtual const char* name() const { return "implem_1"; }
    };
    
    class Implem2 : public Interface
    {
      public:
        virtual const char* name() const { return "implem_2"; }
    };
    
    template <>
    struct Implem<Interface, 1>
    {
      public:
        typedef Implem1 type;
    };
    
    template <>
    struct Implem<Interface, 2>
    {
      public:
        typedef Implem2 type;
    };
    
    
    void print(Interface const& i)
    {
      std::cout << i.name() << std::endl;
    }
    
    int main()
    {
      typedef ImplemToList<Interface>::type IList;
      for_each<IList>(&print);
    }