Question

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.

Était-ce utile?

La 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).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top