How Template Template Parameters Can Simplify Template Classes
Fluent C++ reader Sam wrote to me asking how to make a template expression simpler. This is an interesting question and I’m grateful to Sam for bringing it up.
We’re going to examine this case where template parameters became unwieldy, and see how to simplify them by using template template parameters.
The case
Let’s have a look at the original code. It uses two independent template classes. Here is the first one:
template<typename fsm, typename state> class fsm_state { };
And here is the second one:
template<typename Sampler, typename Led, typename Sender> class MyFsm { };
A third class uses the two above classes:
template<typename Sampler, typename Led, typename Sender> class SamplingState : public fsm_state<MyFsm<Sampler, Led, Sender>, SamplingState<Sampler, Led, Sender>> { };
fsm_state
is used as a CRTP base class on its second parameter: SamplingState
passes itself as a template parameter of its base class.
In this usage of the CRTP, the derived class is itself a template class. This is not a requirement of the CRTP, as the CRTP only requires a base template class and the derived class can be anything. But in this case the derived class happens to be a template as well.
The derived class therefore has to pass itself entirely as a template parameter of the base class, and this implies passing its own template parameters along with itself.
Repeated template parameters
Our goal is now to simplify the code of SampligState
. The problem of SamplingState
is that it has a long declaration, because of repeated template parameters: Sampler
, Led
, Sender
.
How would you go about making the definition of SamplingState
more expressive?
A natural solution for this in C++ is to use template template parameters.
Template template parameters
In most of the examples we see in C++ programming courses, and in most template code out there (that I’ve seen), the parameters used in template declarations are types. They are defined with typename
(or class
):
template<typename T> class MyTemplateClass { // ... };
The type can then be instantiated with a type parameter:
MyTemplateClass<int> x;
But we can put quite a few other other things than types in template parameters. Another parameter we can pass is a template (as opposed to a type). This means that the parameter we pass is itself a template:
template<template <typename T> typename Templ> class MyTemplateTemplateClass { // ... };
We can then instantiate this type with a template parameter:
MyTemplateTemplateClass<MyTemplateClass> y;
Let’s now use this C++ feature to simplify our initial template code.
Factoring template parameters
Let’s go back to our initial SampleState
class:
template<typename Sampler, typename Led, typename Sender> class SamplingState : public fsm_state<MyFsm<Sampler, Led, Sender>, SamplingState<Sampler, Led, Sender>> { };
To remove the duplication of the template parameters packs, we can pass template template parameters instead of template type parameters:
template<typename Sampler, typename Led, typename Sender> class SamplingState: public fsm_state<MyFsm, SamplingState, Sampler, Led, Sender> { };
This is indeed shorter and has less angle brackets. To make this code compile we need to adapt the definition of fsm_state
to accept template template parameters:
template<template<typename Sampler, typename Led, typename Sender> typename fsm, template<typename Sampler, typename Led, typename Sender> typename state, typename Sampler, typename Led, typename Sender> class fsm_state { };
The definition of SamplingState
became more concise, but at the expense of the one of fsm_state
that swelled! Indeed, here is how it was before:
template<typename fsm, typename state> class fsm_state { };
Is this change worth it overall?
This is a trade-off, and there are several ways to decide which side has the more benefits.
One way to look at it is in terms of conciseness. If there is one CRTP base class fsm_state
and many classes such as SampleState
that use it by repeating template parameters, then making the one fsm_state
less concise by using template template parameters is an overall gain.
On a less technical perspective, the code using template template parameters has a different design. Template template parameters give more flexibility to fsm_state
, because it can instantiate the templates with more elaborate types than those passed. By contrast, the original code passed types that were already instantiated by the call site of fsm_state
.
In any case, template template parameters are good to know about and a useful tool in our C++ (template) toolbox.
Thanks again to Sam for this question on template classes!
You will also like
- Default Parameters With Default Template Parameters Types
- Strong Templates
- Function Templates Partial Specialization in C++
- How to Generate a Collection of Random Numbers in Modern C++
Share this post!