Moule Lin
← All notes

Template Template Parameters

Templates as template parameters

template <class T1, template<class T2> 
class Container>

class XCls{

    private:
    Container<T> c;
    public:
    ...
}
template<class T>
using Lst = List<T,allocator<T>>;


XCls<string, Lst> mylst;

Because the second parameter in the declaration of the Stack class template is a type (typename Container), when we define a concrete stack type with Stack<int, std::deque>, the second argument must be std::deque, not std::deque. In this definition we write int twice, and the repetition is unavoidable.

To avoid this repetition, we can make the second parameter of Stack a template itself rather than a concrete type.

template<typename T,
         template<typename> class Container = std::vector>
struct Stack
{
    void push(const T& elem)
    {
        elems.push_back(elem);
    }

    T pop()
    {
        if(empty()) throw std::out_of_range("Stack<>::pop: empty!");

        auto elem = elems.back();
        elems.pop_back();
        return elem;
    }

    bool empty() const
    {
        return elems.empty();
    }

private:
    Container<T> elems;
};

As shown above, the second parameter of the Stack class template becomes template class Container. It is still named Container, but it is now a template that takes one type parameter. Since the name of Container's own template parameter is never used, we can omit it. According to the standard, the keyword before Container here must be class, not typename. Finally, a template template parameter can also have a default value; here we set it to std::vector.

With this definition, we would expect to use Stack like this: Stack<int, std::deque> intStack, but the compiler teaches us a lesson.

In the STL, the std::deque class template is defined with two type parameters: the first is the element type and the second is the allocator type. Although std::deque's second type parameter has a default value, when the compiler substitutes std::deque for Container it matches the parameters strictly and ignores the default value.

So we change the definition of Stack as follows:

template<typename T,
         template<typename Elem, typename Allocator = std::allocator<Elem>> class Container = std::vector>
struct Stack
{
    void push(const T& elem)
    {
        elems.push_back(elem);
    }

    T pop()
    {
        if(empty()) throw std::out_of_range("Stack<>::pop: empty!");

        auto elem = elems.back();
        elems.pop_back();
        return elem;
    }

    bool empty() const
    {
        return elems.empty();
    }

private:
    Container<T> elems;
};