c++typelist

Basic Typelist functionality


I'm having a bit of trouble getting my head around TypeLists or lists of templates. That is:

class nulltype{};

template <typename HEAD, typename TAIL>
struct tlist
{
    typedef HEAD head;
    typedef TAIL tail;
}; 

template <class TList>
class OutputClass
{
public:
    void output(Type t) 
    {
        std::cout << t << endl;
    }
};

typedef tlist<double,tlist<float,NullType> > floatingPoints;    
typedef tlist<std::string,tlist<char *,NullType> > text;   

int main()
{
    OutputClass<floatingPoints> floats = OutputClass<floatingPoints>();
    floats.output(1.0f);

    OutputClass<text> strings = OutputClass<text>();
    floats.output("hello");
    return 0;
}

Basically my goal is that I would like OutputClass.output to output the parameter passed to it, but only if that class instances typelist contains the type passed into the function. ie. referring to the above code: floats can only output float type and double type as defined by its typelist, "floatingPoints". Should a string or int get passed in, I would hope it would error.

I'm having one hell of a time trying to find any examples on how to do this, I've found the indexing examples and length examples a million times over, and they have helped me a lot, but i just can't seem to figure this last bit out. Any and all help will be appreciated.


Solution

  • We first need some helper templates. The first one checks if two types are the same:

    template <typename T, typename U>
    struct is_same
    {
        // Default case: T and U are not the same type
        static const bool value = false;
    };
    
    template <typename T>
    struct is_same<T, T>
    {
        // Specialization: both template arguments are of the same type
        static const bool value = true;
    };
    

    We can now use the boolean is_same<T, U>::value to determine if the types T and U are equivalent.

    Using is_same we can now write a template which checks if a specific type occurs in a type list:

    template <typename TList, typename T>
    struct contains
    {
        static const bool value =
             is_same<typename TList::head, T>::value   // Base case
          || contains<typename TList::tail, T>::value; // Recursion
    };
    
    template <typename T>
    struct contains<nulltype, T>
    {
        // Termination condition
        static const bool value = false;
    };
    

    This is a recursive template, using a specialization on nulltype to terminate the recursion.

    One final helper template we need is enable_if:

    template <bool Cond, typename T=void>
    struct enable_if
    {
        // Default case: Cond assumed to be false, no typedef
    };
    
    template <typename T>
    struct enable_if<true, T>
    {
        // Specialization: Cond is true, so typedef
        typedef T type;
    };
    

    enable_if<true, T>::type yields T, whereas enable_if<false, T>::type is undefined. We exploit the SFINAE rule the enable or disable a function depending on its template argument, like so:

    template <typename TList>
    class OutputClass
    {
    public:
        // Only enable the function if T is in TList (by SFINAE)
        template <typename T>
        typename enable_if<contains<TList, T>::value>::type
        output(T t) 
        {
            std::cout << t << std::endl;
        }
    };
    

    Quite a trip, but if you understand all of this you're well on your way to master template metaprogramming. For an in-depth discussion of template metaprogramming I recommend you pick up a copy of C++ Template Metaprogramming (ISBN-13: 9780321227256).