Pergunta

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.

Foi útil?

Solução

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top