315

I've seen some examples of C++ using template template parameters (that is templates which take templates as parameters) to do policy-based class design. What other uses does this technique have?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 6
    I came from the other direction (FP, Haskell etc) and landed on this: http://stackoverflow.com/questions/2565097/higher-kinded-types-with-c – Erik Kaplun Mar 02 '14 at 03:49

10 Answers10

265

I think you need to use template template syntax to pass a parameter whose type is a template dependent on another template like this:

template <template<class> class H, class S>
void f(const H<S> &value) {
}

Here, H is a template, but I wanted this function to deal with all specializations of H.

NOTE: I've been programming c++ for many years and have only needed this once. I find that it is a rarely needed feature (of course handy when you need it!).

I've been trying to think of good examples, and to be honest, most of the time this isn't necessary, but let's contrive an example. Let's pretend that std::vector doesn't have a typedef value_type.

So how would you write a function which can create variables of the right type for the vectors elements? This would work.

template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
    // This can be "typename V<T, A>::value_type",
    // but we are pretending we don't have it

    T temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

NOTE: std::vector has two template parameters, type, and allocator, so we had to accept both of them. Fortunately, because of type deduction, we won't need to write out the exact type explicitly.

which you can use like this:

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator

or better yet, we can just use:

f(v); // everything is deduced, f can deal with a vector of any type!

UPDATE: Even this contrived example, while illustrative, is no longer an amazing example due to c++11 introducing auto. Now the same function can be written as:

template <class Cont>
void f(Cont &v) {

    auto temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

which is how I'd prefer to write this type of code.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • 1
    If f is a function defined by the user of a library, it is ugly that the user needs to pass std::allocator as an argument. I would have expected that the version without the std::allocator argument have worked using the default parameter of std::vector. Are there any updates on this wrt C++0x? – amit kumar Jan 13 '11 at 05:19
  • 1
    Well, you don't have to provide allocator. What's important is that template template parameter was defined over correct number of arguments. But the function should not care what's their "types" or meaning, following works well in C++98: `template – pfalcon Jan 14 '13 at 02:13
  • I wonder why instantiation is `f` and not `f>`. – bobobobo Dec 17 '13 at 16:15
  • 2
    @bobobobo These two mean different things. `f` means `f`, `f>` means `f` – user362515 Jun 12 '14 at 21:06
  • @phaedrus: (much later...) good points, improved the example to make the allocator generic and the example more clear :-) – Evan Teran Aug 03 '15 at 14:24
  • @qqibrow, yup, fixed :-) – Evan Teran Aug 20 '15 at 15:48
  • "`H` is a type which is templated." `H` isn't a type. `vector` isn't a type. They are templates, not types. Specifically, `vector` is a class template (as opposed to a function template), and `vector` is a template class (i.e. a particular kind of class) – Aaron McDaid Aug 20 '15 at 16:00
  • @amit I was curious about this too, so I tried it in C++11, C++14, and C++17 (gcc v10 over on wandbox.org) and it was only in C++17 that I was able to successfully remove the template parameter for the allocator when using a vector. I.e. `template – Loss Mentality Nov 02 '19 at 05:13
  • @Arron McDaid You're absolutely right about `std::vector` being a "class template". However, I'm not sure about the further distinction you make - that `std::vector` is a "template class". At that point, it's just a class - a concrete type - so I'm not sure what information the term "template class" gives us, as I don't see any difference between a `std::vector` and, say, some `class foo`. I realize your comment is old, but if you happen to notice this, I'd be happy to hear your reasoning. In the meantime, I wanted to give future readers another point of view. – Loss Mentality Nov 02 '19 at 06:31
  • I am curious why your first definition doesn't work if call `f({1})` even I drop the reference in the function signature. `{1}` has a type of `std::initializer` and is a rvale. I thought in that case, `H` would be deduced to std::initializer_list and `S` would be deduced to `int`. But GCC complains, `template argument deduction/substitution failed: couldn't deduce template parameter 'template class H'`. Thought? – HCSF Nov 14 '21 at 07:28
194

Actually, usecase for template template parameters is rather obvious. Once you learn that C++ stdlib has gaping hole of not defining stream output operators for standard container types, you would proceed to write something like:

template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
{
    out << '[';
    if (!v.empty()) {
        for (typename std::list<T>::const_iterator i = v.begin(); ;) {
            out << *i;
            if (++i == v.end())
                break;
            out << ", ";
        }
    }
    out << ']';
    return out;
}

Then you'd figure out that code for vector is just the same, for forward_list is the same, actually, even for multitude of map types it's still just the same. Those template classes don't have anything in common except for meta-interface/protocol, and using template template parameter allows to capture the commonality in all of them. Before proceeding to write a template though, it's worth to check a reference to recall that sequence containers accept 2 template arguments - for value type and allocator. While allocator is defaulted, we still should account for its existence in our template operator<<:

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

Voila, that will work automagically for all present and future sequence containers adhering to the standard protocol. To add maps to the mix, it would take a peek at reference to note that they accept 4 template params, so we'd need another version of the operator<< above with 4-arg template template param. We'd also see that std:pair tries to be rendered with 2-arg operator<< for sequence types we defined previously, so we would provide a specialization just for std::pair.

Btw, with C+11 which allows variadic templates (and thus should allow variadic template template args), it would be possible to have single operator<< to rule them all. For example:

#include <iostream>
#include <vector>
#include <deque>
#include <list>

template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
    os << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main()
{
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

Output

std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
1.1 2.2 3.3 4.4 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
a b c d 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
1 2 3 4 
Community
  • 1
  • 1
pfalcon
  • 6,724
  • 4
  • 35
  • 43
  • 20
    This is such a sweet example of template template parameters, as it shows a case that everybody has had to deal with. – Ravenwater Feb 04 '13 at 00:24
  • 4
    This is the most awakening answer for me in C++ templates. @WhozCraig How did you get the template expansion details? – Arun Sep 14 '14 at 05:51
  • 4
    @Arun gcc supports a macro called `__PRETTY_FUNCTION__`, which, among other things, reports template parameter descriptions in plain text. clang does it as well. A most-handy feature sometimes (as you can see). – WhozCraig Sep 14 '14 at 05:53
  • 26
    The template template parameter here is not really adding any value. You might as well just use a regular template parameter as any given instance of a class template. – David Stone Oct 04 '15 at 17:15
  • 2
    Doesn't the last version make collision with the std's `operator<<(ostream, string)`? I tried it because it seemed so awesome, but I could not make it work: `ambiguous overload for ‘operator<<’ (operand types are ‘std::basic_ostream’ and ‘std::string {aka std::basic_string}’)` – jmmut Apr 25 '16 at 17:19
  • Thanks you! This is the only example I found for template template that actually described it well enough I fully grasped it. – Daniel Johnson Jun 25 '16 at 19:21
  • 13
    I have to agree with David Stone. There is no point to the template template parameter here. It would be much simpler and equally effective to make a plain template (template ). I know this post is quite old, so I am only adding my 2 cents for people who stumble across this answer looking for info about template templates. – Jim Vargo Jul 13 '16 at 21:27
  • why typename T is required in variadic case we can simply have template – PapaDiHatti May 19 '17 at 11:51
  • Also how to have single template function that can work with container of containers something like std::vector > vf { {1.1, 2.2, 3.3, 4.4} }; – PapaDiHatti May 19 '17 at 12:31
  • This does not work for std::array, no? (Fails for me). – Mankka Aug 15 '17 at 06:52
  • this is could a bit late, but in your last ostream overload I don't quite understand why you have such long template parameters, whats special about `typename `T in the template parameters list and further ??? I mean this `template class C, class... Args>` --- could be shorter ------ `template< template class C, class ...Args>` – ampawd Jan 23 '18 at 19:32
  • I ran into a problem with ambiguous matching when using stream manipulators on VS2019. `oss << "StructuredException : " << std::hex << std::setw(8) << std::setfill('0') << std::uppercase << u;` @Mankka The array container has a type template parameter instead of class template parameters. – lkreinitz Nov 02 '20 at 00:59
  • As to whether the template template parameter is necessary: In practice, a `templatestd::ostream& operator <<(std::ostream& os, const CONT& objs) { ... }` would likely catch way too much. One then either needs type traits to get rid of the non-container values again. Or use template template parameter to force a certain structure of the arguments. Or even use a combination of both techniques. – Kai Petzke Feb 02 '23 at 10:36
78

Here is a simple example taken from 'Modern C++ Design - Generic Programming and Design Patterns Applied' by Andrei Alexandrescu:

He uses a classes with template template parameters in order to implement the policy pattern:

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

He explains: Typically, the host class already knows, or can easily deduce, the template argument of the policy class. In the example above, WidgetManager always manages objects of type Widget, so requiring the user to specify Widget again in the instantiation of CreationPolicy is redundant and potentially dangerous.In this case, library code can use template template parameters for specifying policies.

The effect is that the client code can use 'WidgetManager' in a more elegant way:

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

Instead of the more cumbersome, and error prone way that a definition lacking template template arguments would have required:

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
yoav.aviram
  • 1,802
  • 15
  • 19
  • 2
    The question specifically requested for examples other than the policy pattern. – user2913094 Feb 12 '15 at 11:59
  • I came to this question exactly from this book. A worthy note is that the template template parameters also appear in the Typelist chapter and the *Class generation with Typelists* chapter. – Victor Jan 29 '20 at 16:30
22

Here's another practical example from my CUDA Convolutional neural network library. I have the following class template:

template <class T> class Tensor

which is actually implements n-dimensional matrices manipulation. There's also a child class template:

template <class T> class TensorGPU : public Tensor<T>

which implements the same functionality but in GPU. Both templates can work with all basic types, like float, double, int, etc And I also have a class template (simplified):

template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
    TT<T> weights;
    TT<T> inputs;
    TT<int> connection_matrix;
}

The reason here to have template template syntax is because I can declare implementation of the class

class CLayerCuda: public CLayerT<TensorGPU, float>

which will have both weights and inputs of type float and on GPU, but connection_matrix will always be int, either on CPU (by specifying TT = Tensor) or on GPU (by specifying TT=TensorGPU).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Mikhail Sirotenko
  • 960
  • 1
  • 11
  • 16
14

This is what I ran into:

template<class A>
class B
{
  A& a;
};

template<class B>
class A
{
  B b;
};

class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{

};

Can be solved to:

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};

or (working code):

template<class A>
class B
{
public:
    A* a;
    int GetInt() { return a->dummy; }
};

template< template<class> class B>
class A
{
public:
    A() : dummy(3) { b.a = this; }
    B<A> b;
    int dummy;
};

class AInstance : public A<B> //happy
{
public:
    void Print() { std::cout << b.GetInt(); }
};

int main()
{
    std::cout << "hello";
    AInstance test;
    test.Print();
}
oHo
  • 51,447
  • 27
  • 165
  • 200
Cookie
  • 12,004
  • 13
  • 54
  • 83
13

Say you're using CRTP to provide an "interface" for a set of child templates; and both the parent and the child are parametric in other template argument(s):

template <typename DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived<int>, int> derived_t;

Note the duplication of 'int', which is actually the same type parameter specified to both templates. You can use a template template for DERIVED to avoid this duplication:

template <template <typename> class DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED<VALUE>*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived, int> derived_t;

Note that you are eliminating directly providing the other template parameter(s) to the derived template; the "interface" still receives them.

This also lets you build up typedefs in the "interface" that depend on the type parameters, which will be accessible from the derived template.

The above typedef doesn't work because you can't typedef to an unspecified template. This works, however (and C++11 has native support for template typedefs):

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

You need one derived_interface_type for each instantiation of the derived template unfortunately, unless there's another trick I haven't learned yet.

clickMe
  • 993
  • 11
  • 33
Mark McKenna
  • 2,857
  • 1
  • 17
  • 17
  • I needed this exact solution for some code (thanks!). Although it works, I don't understand how the template class `derived` can be used without its template arguments, i.e. the line `typedef typename interface type;` – Carlton Jan 26 '18 at 16:13
  • @Carlton it works basically because the corresponding template parameter being filled is defined as a `template `. In a sense you can think of the template parameters as having a 'metatype'; the normal metatype for a template parameter is `typename` which means it needs to be filled by a regular type; the `template` metatype means it needs to be filled with a reference to a template. `derived` defines a template that accepts one `typename` metatyped parameter, so it fits the bill and can be referenced here. Make sense? – Mark McKenna Jan 27 '18 at 23:03
  • C++11 yet still `typedef`. Also, you can avoid the duplicate `int` in your first example by using a standard construct such as a `value_type` in the DERIVED type. – rubenvb Jun 19 '18 at 07:37
  • This answer doesn't actually target C++11; I referenced C++11 just to say you can get around the `typedef` problem from block 2. But point 2 is valid I think... yeah, that would probably be a simpler way to do the same thing. – Mark McKenna Jun 20 '18 at 14:03
7

Here's one generalized from something I just used. I'm posting it since it's a very simple example, and it demonstrates a practical use case along with default arguments:

#include <vector>

template <class T> class Alloc final { /*...*/ };

template <template <class T> class allocator=Alloc> class MyClass final {
  public:
    std::vector<short,allocator<short>> field0;
    std::vector<float,allocator<float>> field1;
};
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • I also ran into this use case recently preparing to write my own STL-compatible container, but see this thread and the corresponding answers for why this isn't the approach the standard library actually takes (TL;DR —it means it's not possible for callers to pass an allocator that takes any more than one template parameter): https://stackoverflow.com/questions/12362363/why-is-allocatorrebind-necessary-when-we-have-template-template-parameters/18682138 – saxbophone Aug 08 '20 at 22:38
4

In the solution with variadic templates provided by pfalcon, I found it difficult to actually specialize the ostream operator for std::map due to the greedy nature of the variadic specialization. Here's a slight revision which worked for me:

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>

namespace containerdisplay
{
  template<typename T, template<class,class...> class C, class... Args>
  std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
  {
    std::cout << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
      os << obj << ' ';
    return os;
  }  
}

template< typename K, typename V>
std::ostream& operator << ( std::ostream& os, 
                const std::map< K, V > & objs )
{  

  std::cout << __PRETTY_FUNCTION__ << '\n';
  for( auto& obj : objs )
  {    
    os << obj.first << ": " << obj.second << std::endl;
  }

  return os;
}


int main()
{

  {
    using namespace containerdisplay;
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';
  }

  std::map< std::string, std::string > m1 
  {
      { "foo", "bar" },
      { "baz", "boo" }
  };

  std::cout << m1 << std::endl;

    return 0;
}
4

It improves readability of your code, provides extra type safety and save some compiler efforts.

Say you want to print each element of a container, you can use the following code without template template parameter

template <typename T> void print_container(const T& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

or with template template parameter

template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

Assume you pass in an integer say print_container(3). For the former case, the template will be instantiated by the compiler which will complain about the usage of c in the for loop, the latter will not instantiate the template at all as no matching type can be found.

Generally speaking, if your template class/function is designed to handle template class as template parameter, it is better to make it clear.

colin
  • 101
  • 2
  • 4
  • I disagree; doesn't your example just reduce the utility of the `print_container` function by arbitrarily limiting its scope to containers created using templates? If someone were to write a class that works in a range-based `for` loop, but the nature of the class only makes sense with a specific type, they wouldn't be able to use it with `print_container`, even though the function is written in a way where it would otherwise work fine. – flarn2006 Oct 24 '22 at 03:58
3

I use it for versioned types.

If you have a type versioned through a template such as MyType<version>, you can write a function in which you can capture the version number:

template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
    assert(Version > 2 && "Versions older than 2 are no longer handled");
    ...
    switch (Version)
    {
    ...
    }
}

So you can do different things depending on the version of the type being passed in instead of having an overload for each type. You can also have conversion functions which take in MyType<Version> and return MyType<Version+1>, in a generic way, and even recurse them to have a ToNewest() function which returns the latest version of a type from any older version (very useful for logs that might have been stored a while back but need to be processed with today's newest tool).

cd127
  • 121
  • 1
  • 4