What are some uses of template template parameters?
Asked Answered
H

10

321

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?

Humor answered 17/10, 2008 at 20:38 Comment(1)
I came from the other direction (FP, Haskell etc) and landed on this: https://mcmap.net/q/82281/-higher-kinded-types-with-cBenkley
O
277

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.

Oneida answered 17/10, 2008 at 20:51 Comment(10)
If f is a function defined by the user of a library, it is ugly that the user needs to pass std::allocator<T> 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?Tactful
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<template<class, class> class C, class T, class U> void f(C<T, U> &v)Coalfield
I wonder why instantiation is f<vector,int> and not f<vector<int>>.Minnaminnaminnie
@Minnaminnaminnie These two mean different things. f<vector,int> means f<ATemplate,AType>, f<vector<int>> means f<AType>Cufic
@phaedrus: (much later...) good points, improved the example to make the allocator generic and the example more clear :-)Oneida
@qqibrow, yup, fixed :-)Oneida
"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<int> is a template class (i.e. a particular kind of class)Protrusile
@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 <template<class> class V, class T> void f(V<T> &v) {...}; worked in C++17. However, it did not work with clang v10, so this looks like non-standard operation from gcc.Gandhi
@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<int> 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<int> 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.Gandhi
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<int> 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> class H'. Thought?Broaden
C
200

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 
Coalfield answered 14/1, 2013 at 2:44 Comment(14)
This is such a sweet example of template template parameters, as it shows a case that everybody has had to deal with.Intrastate
This is the most awakening answer for me in C++ templates. @WhozCraig How did you get the template expansion details?Messmate
@Messmate 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).Lustful
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.Warner
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<char>’ and ‘std::string {aka std::basic_string<char>}’)Gentlemanatarms
Thanks you! This is the only example I found for template template that actually described it well enough I fully grasped it.Microbalance
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 <typename Container>). 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.Guncotton
why typename T is required in variadic case we can simply have template<template<class,class...> class C, class... Args> std::ostream& operator <<(std::ostream& os, const C<Args...>& objs)Formality
Also how to have single template function that can work with container of containers something like std::vector<std::vector<float> > vf { {1.1, 2.2, 3.3, 4.4} };Formality
This does not work for std::array, no? (Fails for me).Lisbethlisbon
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<typename T, template<class,class...> class C, class... Args> --- could be shorter ------ template< template<class ...> class C, class ...Args>Orthopedics
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; @Lisbethlisbon The array container has a type template parameter instead of class template parameters.Sweetsop
As to whether the template template parameter is necessary: In practice, a template<typename CONT>std::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.Caracas
That doesn't compile (at least without a hack) in Visual Studio due to the lack of the __PRETTY_FUNCTION__ macro: #48858387Jobyna
S
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;
Scharf answered 18/10, 2008 at 11:3 Comment(2)
The question specifically requested for examples other than the policy pattern.Damian
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.Quiberon
G
23

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

Gustie answered 17/7, 2011 at 19:13 Comment(2)
Can you force the deduction of T with something like: "template <class T, template <T> TT> CLayerT" and "class CLayerCuda: public CLayerT<TensorGPU<float>>" ? In case you didn't need a TT<otherT>Lord
NEVER MIND: template<template<class T> class U> class B1 { }; from ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/… from a quick google searchLord
M
16

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();
}
Mainly answered 29/5, 2014 at 10:17 Comment(0)
C
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.

Coltish answered 9/10, 2012 at 18:47 Comment(4)
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<derived, VALUE> type;Jetty
@Jetty it works basically because the corresponding template parameter being filled is defined as a template <typename>. 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?Coltish
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.Unific
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.Coltish
F
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;
};
Ferrigno answered 19/5, 2015 at 23:13 Comment(1)
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): #12362863Cordwood
L
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;
}
Landslide answered 19/2, 2015 at 1:52 Comment(0)
F
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.

Foskett answered 30/8, 2017 at 18:35 Comment(1)
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.Mild
C
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).

Cochran answered 29/9, 2019 at 17:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.