Commit 2a5f9cb4 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Add template template parameters in the advanced template notebook.

parent adf1a1c7
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Templates](/notebooks/4-Templates/0-main.ipynb) - [Hints to more advanced concepts with templates](/notebooks/4-Templates/5-MoreAdvanced.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Curiously-recurrent-template-pattern-(CRTP)" data-toc-modified-id="Curiously-recurrent-template-pattern-(CRTP)-1">Curiously recurrent template pattern (CRTP)</a></span></li><li><span><a href="#Traits" data-toc-modified-id="Traits-2">Traits</a></span></li><li><span><a href="#Policies" data-toc-modified-id="Policies-3">Policies</a></span></li><li><span><a href="#Variadic-templates" data-toc-modified-id="Variadic-templates-4">Variadic templates</a></span></li></ul></div>
<div class="toc"><ul class="toc-item"><li><span><a href="#Curiously-recurrent-template-pattern-(CRTP)" data-toc-modified-id="Curiously-recurrent-template-pattern-(CRTP)-1">Curiously recurrent template pattern (CRTP)</a></span></li><li><span><a href="#Traits" data-toc-modified-id="Traits-2">Traits</a></span></li><li><span><a href="#Policies" data-toc-modified-id="Policies-3">Policies</a></span></li><li><span><a href="#Variadic-templates" data-toc-modified-id="Variadic-templates-4">Variadic templates</a></span></li><li><span><a href="#Template-template-parameters-(not-a-mistake...)" data-toc-modified-id="Template-template-parameters-(not-a-mistake...)-5">Template template parameters (not a mistake...)</a></span></li></ul></div>
%% Cell type:markdown id: tags:
We have barely scratched the surface of what can be done with templates; I will here just drop few names and a very brief explanation to allow you to dig deeper if it might seem of interest for your codes (a Google search for either of them will give you plenty of references) and also avoid you frowing upon a seemingly daunting syntax...
## Curiously recurrent template pattern (CRTP)
One of my own favourite idiom (so much I didn't resist writing an [entry](/notebooks/7-Appendix/Crtp.ipynb) about it in the appendix).
The idea behind it is to provide a same set of a given functionality to classes that have otherwise nothing in common.
The basic example is if you want to assign a unique identifier to a class of yours: the implementation would be exactly the same in each otherwise different class in which you need this:
* Initializing properly this identifier at construction.
* Check no other objects of the same class use it already.
* Provide an accessor `GetUniqueIdentifier()`.
Usual inheritance or composition aren't very appropriate to put in common once and for all (DRY principle!) these functionalities: either they may prove dangerous (inheritance) or be very wordy (composition).
The **curiously recurrent template pattern** is a very specific inheritance:
````class MyClass : public UniqueIdentifier<MyClass>````
where your class inherits from a template class which parameter is... your class itself.
%% Cell type:markdown id: tags:
## Traits
A **trait** is a member of a class which gives exclusively an information about type. For instance let's go back to the `HoldAValue` class we wrote [earlier](/notebooks/4-Templates/2-Specialization.ipynb) in our template presentation:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
template<class T>
class HoldAValue
{
public:
HoldAValue(T value);
T GetValue() const;
private:
T value_;
};
template<class T>
HoldAValue<T>::HoldAValue(T value)
: value_(value)
{ }
template<class T>
T HoldAValue<T>::GetValue() const
{
return value_;
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl;
HoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl;
}
```
%% Cell type:markdown id: tags:
This class was not especially efficient: the accessor `GetValue()`:
- Requires `T` is copyable.
- Copy `T`, which is potentially a time-consuming operator.
We could replace by `const T& GetValue() const`, but it's a bit on the nose (and less efficient) for plain old data type. The best of both world may be achieved by a trait:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
template<class T>
class ImprovedHoldAValue
{
public:
// Traits: information about type!
using return_value =
typename std::conditional<std::is_trivial<T>::value, T, const T&>::type;
ImprovedHoldAValue(T value);
return_value GetValue() const;
private:
T value_;
};
template<class T>
ImprovedHoldAValue<T>::ImprovedHoldAValue(T value)
: value_(value)
{ }
```
%% Cell type:markdown id: tags:
Beware the trait that acts as the return value must be scoped correctly in the definition:
%% Cell type:code id: tags:
``` C++17
template<class T>
typename ImprovedHoldAValue<T>::return_value ImprovedHoldAValue<T>::GetValue() const
{
return value_;
}
```
%% Cell type:markdown id: tags:
And the result remain the same, albeit more efficient as a copy is avoided:
%% Cell type:code id: tags:
``` C++17
{
ImprovedHoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl;
ImprovedHoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl;
}
```
%% Cell type:markdown id: tags:
In fact sometimes you may even have **traits class**: class which sole purpose is to provide type informations! Such classes are often used as template parameters of other classes.
%% Cell type:markdown id: tags:
## Policies
Policies are a way to provide a class for which a given aspect is entirely configurable by another class you provide as a template parameter.
STL uses up policies: for instance there is a second optional template parameter to `std::vector` which deals with the way to allocate the memory (and only with that aspect). So you may provide your own way to allocate the memory and provide it to `std::vector`, which will use it instead of its default behaviour. \cite{Alexandrescu2001} dedicates a whole chapter of his book to this example: he wrote an allocator aimed at being more efficient for the allocation of small objects.
The syntax of a policy is a template class which also derives from at least one of its template parameter:
%% Cell type:code id: tags:
``` C++17
template<class ColorPolicyT>
class Car : public ColorPolicyT
{ };
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
struct Blue
{
void Print() const
{
std::cout << "My color is blue!" << std::endl;
}
};
```
%% Cell type:code id: tags:
``` C++17
#include <string>
// Let's assume in the future a car provides a mechanism to change its color at will:
class Changing
{
public:
void Display() const // I do not use `Print()` intentionally to illustrate there is no constraint
// but in true code it would be wise to use same naming scheme!
{
std::cout << "Current color is " << color_ << "!" << std::endl;
}
void ChangeColor(const std::string& new_color)
{
color_ = new_color;
}
private:
std::string color_ = "white";
};
```
%% Cell type:code id: tags:
``` C++17
{
Car<Blue> blue_car;
blue_car.Print();
Car<Changing> future_car;
future_car.Display();
future_car.ChangeColor("black");
future_car.Display();
}
```
%% Cell type:markdown id: tags:
## Variadic templates
If you have already written some C, you must be familiar with `printf`:
%% Cell type:code id: tags:
``` C++17
#include <cstdio>
{
int i = 5;
double d = 3.1415;
printf("i = %d\n", i); // Currently not printed due to Xeus-cling limitation...
printf("i = %d and d = %lf\n", i, d);
}
```
%% Cell type:markdown id: tags:
This function is atypical as it may take an arbitrary number of arguments. You can devise similar function of your own in C (look for `va_arg` if you insist...) but it was not recommended: under the hood it is quite messy, and limits greatly the checks your compiler may perform on your code.
C++ 11 introduced **variadic templates**, which provides a much neater way to provide this kind of functionality (albeit with a very tricky syntax: check all the `...` below... and it becomes worse if you need to propagate them).
%% Cell type:code id: tags:
``` C++17
#include <iostream>
// Overload when one value only.
template<class T>
void Print(T value)
{
std::cout << value << std::endl;
}
// Overload with a variadic number of arguments
template<class T, class ...Args>
void Print(T value, Args... args) // args here will be all parameters passed to the function from the
// second one onward.
{
Print(value);
Print(args...); // Will call recursively `Print()` with one less argument.
}
```
%% Cell type:code id: tags:
``` C++17
Print(5, "hello");
```
%% Cell type:code id: tags:
``` C++17
Print("One");
```
%% Cell type:code id: tags:
``` C++17
Print(); // Compilation error: no arguments isn't accepted!
```
%% Cell type:markdown id: tags:
To learn more about them, I recommend \cite{Meyers2015}, which provides healthy explanations about `std::forward` and `std::move` you will probably need soon if you want to use these variadic templates.
%% Cell type:markdown id: tags:
## Template template parameters (not a mistake...)
You may want to be way more specific when defining a template parameter: instead of telling it might be whatever you want, you may impose that a specific template parameter should only be a type which is itself an instantiation of a template.
Let's consider a very dumb template function which purpose is to call print the value of `size()` for a STL container. We'll see them more extensively in a [dedicated notebook](/notebooks/5-UsefulConceptsAndSTL/3-Containers.ipynb), but for now you just have to know that these containers take two template parameters:
- One that describe the type inside the container (e.g. `double` for `std::vector<double>`).
- Another optional one which specifies how the memory is allocated.
We could not bother and use directly a usual template parameter:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
template<class ContainerT>
void PrintSize1(const ContainerT& container)
{
std::cout << container.size() << std::endl;
}
```
%% Cell type:markdown id: tags:
You may use it on seemlessly on usual STL containers:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <list>
#include <deque>
{
std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 };
PrintSize1(vector);
PrintSize1(list);
PrintSize1(deque);
}
```
%% Cell type:markdown id: tags:
However, it would also work for any class that define a size parameters, regardless of its nature.
%% Cell type:code id: tags:
``` C++17
#include <string>
struct NonTemplateClass
{
std::string size() const
{
return "Might seem idiotic, but why not?";
}
};
```
%% Cell type:code id: tags:
``` C++17
template<class U, class V, class W>
struct TemplateWithThreeParameters
{
int size() const
{
return -99;
}
};
```
%% Cell type:code id: tags:
``` C++17
{
NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize1(non_template_class);
PrintSize1(template_with_three_parameters);
}
```
%% Cell type:markdown id: tags:
We see here with my rather dumb example that `PrintSize1()` also works for my own defined types, that are not following the expected prototype of a STL container (class with two template parameters).
It may seem pointless in this example, but the worst is that the method might be used to represent something entirely different from what we expect when we call `size()` upon a STL container.
A possibility to limit the risk is to use a **template template parameter** in the function definition:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
template<template <class, class> class ContainerT, class TypeT, class AllocatorT>
void PrintSize2(const ContainerT<TypeT, AllocatorT>& container)
{
std::cout << container.size() << std::endl;
}
```
%% Cell type:markdown id: tags:
By doing so, we impose that the type of the argument is an instantiation of a class with two arguments. With that, STL containers work:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <list>
#include <deque>
{
std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 };
// At call site, you don't have to specify the template arguments that are inferred.
PrintSize2(vector);
PrintSize2(list);
PrintSize2(deque);
}
```
%% Cell type:markdown id: tags:
whereas my own defined types don't:
%% Cell type:code id: tags:
``` C++17
{
NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize2(non_template_class);
PrintSize2(template_with_three_parameters);
}
```
%% Cell type:markdown id: tags:
In practice you shouldn't need to use that too often, but as the topics in this notebook it is worthy to know the possibility exists (that may help you understand an error message should you use a library using them). I had to resort to them a couple of times, especially along with policies.
If you want to learn more about them, you should really read \cite{Alexandrescu2001}).
%% Cell type:markdown id: tags:
# References
(<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu, 2001</a>) Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001.
(<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers, 2015</a>) Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment