Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 9974bd37 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Few additions in the two last template notebooks.

parent bdd9ec8c
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Metaprogramming](./4-Metaprogramming.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Metaprogramming](./4-Metaprogramming.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#Example:-same-action-upon-a-collection-of-heterogeneous-objects" data-toc-modified-id="Example:-same-action-upon-a-collection-of-heterogeneous-objects-2">Example: same action upon a collection of heterogeneous objects</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#Example:-same-action-upon-a-collection-of-heterogeneous-objects" data-toc-modified-id="Example:-same-action-upon-a-collection-of-heterogeneous-objects-2">Example: same action upon a collection of heterogeneous objects</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
We will no go very far in this direction: metaprogramming is really a very rich subset of C++ of its own, and one that is especially tricky to code. We will no go very far in this direction: metaprogramming is really a very rich subset of C++ of its own, and one that is especially tricky to code.
You can be a very skilled C++ developer and never use this; however it opens some really interesting prospects that can't be achieved easily (or at all...) without it. You can be a very skilled C++ developer and never use this; however it opens some really interesting prospects that can't be achieved easily (or at all...) without it.
I recommend the reading of \cite{Alexandrescu2001} to get the gist of it: even if it relies upon older versions of C++ (and therefore some of its hand-made constructs are now in one form or another in modern C++ or STL) it is very insightful to understand the reasoning behind metaprogrammation. I recommend the reading of \cite{Alexandrescu2001} to get the gist of it: even if it relies upon older versions of C++ (and therefore some of its hand-made constructs are now in one form or another in modern C++ or STL) it is very insightful to understand the reasoning behind metaprogrammation.
## Example: same action upon a collection of heterogeneous objects ## Example: same action upon a collection of heterogeneous objects
Let's say we want to put together in a same container multiple objects of heterogeneous type (one concrete case for which I have used that: reading an input data file with each entry is handled differently by a dedicated object). Let's say we want to put together in a same container multiple objects of heterogeneous type (one concrete case for which I have used that: reading an input data file with each entry is handled differently by a dedicated object).
In C++11, `std::tuple` was introduced for that purpose: In C++11, `std::tuple` was introduced for that purpose:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <tuple> #include <tuple>
std::tuple<int, std::string, double, float, long> tuple = std::tuple<int, std::string, double, float, long> tuple =
std::make_tuple(5, "hello", 5., 3.2f, -35l); std::make_tuple(5, "hello", 5., 3.2f, -35l);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
What if we want to apply the same treatment of all of the entries? What if we want to apply the same treatment of all of the entries?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In more usual containers, we would just write a `for` loop, but this is not an option here: to access the `I`-th element of the tuple syntax is `std::get<I>`, so `I` has to be known at compiled time... which is not the case for the mutable variable used in a typical `for` loop! In more usual containers, we would just write a `for` loop, but this is not an option here: to access the `I`-th element of the tuple syntax is `std::get<I>`, so `I` has to be known at compiled time... which is not the case for the mutable variable used in a typical `for` loop!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Let's roll with just printing each of them: Let's roll with just printing each of them:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
std::cout << std::get<0>(tuple) << std::endl; std::cout << std::get<0>(tuple) << std::endl;
std::cout << std::get<1>(tuple) << std::endl; std::cout << std::get<1>(tuple) << std::endl;
std::cout << std::get<2>(tuple) << std::endl; std::cout << std::get<2>(tuple) << std::endl;
std::cout << std::get<3>(tuple) << std::endl; std::cout << std::get<3>(tuple) << std::endl;
std::cout << std::get<4>(tuple) << std::endl; std::cout << std::get<4>(tuple) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The treatment was rather simple here, but the code was duplicated manually for each of them. **Metaprogramming** is the art of making the compiler generate by itself the whole code. The treatment was rather simple here, but the code was duplicated manually for each of them. **Metaprogramming** is the art of making the compiler generate by itself the whole code.
The syntax relies heavily on templates, and those of you familiar with functional programming will feel at ease here: The syntax relies heavily on templates, and those of you familiar with functional programming will feel at ease here:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template <std::size_t IndexT, std::size_t TupleSizeT> template <std::size_t IndexT, std::size_t TupleSizeT>
struct PrintTuple struct PrintTuple
{ {
template<class TupleT> // I'm lazy I won't put there std::tuple<int, std::string, double, float, long> again... template<class TupleT> // I'm lazy I won't put there std::tuple<int, std::string, double, float, long> again...
static void Do(const TupleT& tuple) static void Do(const TupleT& tuple)
{ {
std::cout << std::get<IndexT>(tuple) << std::endl; std::cout << std::get<IndexT>(tuple) << std::endl;
PrintTuple<IndexT + 1ul, TupleSizeT>::Do(tuple); // that's the catch: call recursively the next one! PrintTuple<IndexT + 1ul, TupleSizeT>::Do(tuple); // that's the catch: call recursively the next one!
} }
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A side reminder here: we need to use a combo of template specialization of `struct` and static method here to work around the fact template specialization of functions is not possible (see [here](2-Specialization.ipynb#Mimicking-the-partial-template-specialization-for-functions) for more details) A side reminder here: we need to use a combo of template specialization of `struct` and static method here to work around the fact template specialization of functions is not possible (see [here](2-Specialization.ipynb#Mimicking-the-partial-template-specialization-for-functions) for more details)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may see the gist of it, but there is still an issue: the recursivity goes to the infinity... (don't worry your compiler will yell before that!). So you need a specialization to stop it - you may see now why I used a class template and not a function! You may see the gist of it, but there is still an issue: the recursivity goes to the infinity... (don't worry your compiler will yell before that!). So you need a specialization to stop it - you may see now why I used a class template and not a function!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template <std::size_t TupleSizeT> template <std::size_t TupleSizeT>
struct PrintTuple<TupleSizeT, TupleSizeT> struct PrintTuple<TupleSizeT, TupleSizeT>
{ {
template<class TupleT> template<class TupleT>
static void Do(const TupleT& tuple) static void Do(const TupleT& tuple)
{ {
// Do nothing! // Do nothing!
} }
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With that, the code is properly generated: With that, the code is properly generated:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::tuple<int, std::string, double, float, long> tuple = std::tuple<int, std::string, double, float, long> tuple =
std::make_tuple(5, "hello", 5., 3.2f, -35l); std::make_tuple(5, "hello", 5., 3.2f, -35l);
PrintTuple<0, std::tuple_size<decltype(tuple)>::value>::Do(tuple); PrintTuple<0, std::tuple_size<decltype(tuple)>::value>::Do(tuple);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, the call is not yet very easy: it's cumbersome to have to explicitly reach the tuple size... But as often in C++ an extra level of indirection may lift this issue: Of course, the call is not yet very easy: it's cumbersome to have to explicitly reach the tuple size... But as often in C++ an extra level of indirection may lift this issue:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class TupleT> template<class TupleT>
void PrintTupleWrapper(const TupleT& t) void PrintTupleWrapper(const TupleT& t)
{ {
PrintTuple<0, std::tuple_size<TupleT>::value>::Do(t); PrintTuple<0, std::tuple_size<TupleT>::value>::Do(t);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And then the call may be simply: And then the call may be simply:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::tuple<int, std::string, double, float, long> tuple = std::make_tuple(5, "hello", 5., 3.2f, -35l); std::tuple<int, std::string, double, float, long> tuple = std::make_tuple(5, "hello", 5., 3.2f, -35l);
PrintTupleWrapper(tuple); PrintTupleWrapper(tuple);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In fact, my laziness earlier when I used a template argument rather than the exact tuple type pays now as this function may be used with any tuple (or more precisely with any tuple for which all elements comply with `operator<<`): In fact, my laziness earlier when I used a template argument rather than the exact tuple type pays now as this function may be used with any tuple (or more precisely with any tuple for which all elements comply with `operator<<`):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int a = 5; int a = 5;
std::tuple<std::string, int*> tuple = std::make_tuple("Hello", &a); std::tuple<std::string, int*> tuple = std::make_tuple("Hello", &a);
PrintTupleWrapper(tuple); PrintTupleWrapper(tuple);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Slight improvement with C++ 17 `if constexpr` ## Slight improvement with C++ 17 `if constexpr`
With C++ 17 compile-time check `if constexpr`, you may even do the same with much less boilerplate: With C++ 17 compile-time check `if constexpr`, you may even do the same with much less boilerplate:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template <std::size_t IndexT, class TupleT> template <std::size_t IndexT, class TupleT>
void PrintTupleIfConstexpr(const TupleT& tuple) void PrintTupleIfConstexpr(const TupleT& tuple)
{ {
constexpr auto size = std::tuple_size<TupleT>(); constexpr auto size = std::tuple_size<TupleT>();
static_assert(IndexT <= size); static_assert(IndexT <= size);
if constexpr (IndexT < size) if constexpr (IndexT < size)
{ {
std::cout << std::get<IndexT>(tuple) << std::endl; std::cout << std::get<IndexT>(tuple) << std::endl;
PrintTupleIfConstexpr<IndexT + 1, TupleT>(tuple); PrintTupleIfConstexpr<IndexT + 1, TupleT>(tuple);
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class TupleT> template<class TupleT>
void PrintTupleIfConstexprWrapper(const TupleT& tuple) void PrintTupleIfConstexprWrapper(const TupleT& tuple)
{ {
PrintTupleIfConstexpr<0ul>(tuple); PrintTupleIfConstexpr<0ul>(tuple);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::tuple<int, std::string, double, float, long> tuple = std::make_tuple(5, "hello", 5., 3.2f, -35l); std::tuple<int, std::string, double, float, long> tuple = std::make_tuple(5, "hello", 5., 3.2f, -35l);
PrintTupleIfConstexprWrapper(tuple); PrintTupleIfConstexprWrapper(tuple);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int a = 5; int a = 5;
std::tuple<std::string, int*> tuple = std::make_tuple("Hello", &a); std::tuple<std::string, int*> tuple = std::make_tuple("Hello", &a);
PrintTupleIfConstexprWrapper(tuple); PrintTupleIfConstexprWrapper(tuple);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The geist of it remains the same (it amounts to a recursive call) but the compile-time check makes us avoid entirely the use of the stopping specialization and the use of a struct with `static` method. The gist of it remains the same (it amounts to a recursive call) but the compile-time check makes us avoid entirely the use of the stopping specialization and the use of a struct with `static` method.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `std::apply` ## `std::apply`
Another option provided by C++ 17 is to use `std::apply`, which purpose is to apply upon all elements of a same tuple a same operation. Another option provided by C++ 17 is to use `std::apply`, which purpose is to apply upon all elements of a same tuple a same operation.
[Cppreference](https://en.cppreference.com/w/cpp/utility/apply) provides a snippet that solves the exact problem we tackled above in a slightly different way: they wrote a generic overload of `operator<<` for any instance of a `std::tuple` object. [Cppreference](https://en.cppreference.com/w/cpp/utility/apply) provides a snippet that solves the exact problem we tackled above in a slightly different way: they wrote a generic overload of `operator<<` for any instance of a `std::tuple` object.
I have simplified somehow their snippet as they complicated a bit the reading with unrelated niceties to handle the comma separators. I have simplified somehow their snippet as they complicated a bit the reading with unrelated niceties to handle the comma separators.
Don't bother if you do not understand all of it: Don't bother if you do not understand all of it:
- The weird `...` syntax is for variadic templates, that we will present [briefly in next notebook](../5-MoreAdvanced.ipynb#Variadic-templates). Sorry we avoid as much as possible to refer to future stuff in the training session, but current paragraph is a late addition and doesn't mesh completely well with the structure of this document. You just have to know it's a way to handle a variable number of arguments (here template arguments of `std::tuple`). - The weird `...` syntax is for variadic templates, that we will present [briefly in next notebook](../5-MoreAdvanced.ipynb#Variadic-templates). Sorry we avoid as much as possible to refer to future stuff in the training session, but current paragraph is a late addition and doesn't mesh completely well with the structure of this document. You just have to know it's a way to handle a variable number of arguments (here template arguments of `std::tuple`).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<typename... Ts> template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple) std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)
{ {
std::apply std::apply
( (
[&os](Ts const&... tupleArgs) [&os](Ts const&... tupleArgs)
{ {
((os << tupleArgs << std::endl), ...); ((os << tupleArgs << std::endl), ...);
}, theTuple }, theTuple
); );
return os; return os;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::cout << tuple << std::endl; std::cout << tuple << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Bonus: metaprogramming Fibonacci # Bonus: metaprogramming Fibonacci
In [notebook about constexpr](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb), I said implementing Fibonacci series before C++ 11 involved metaprogramming; here is an implementation (much more wordy than the `constexpr` one): In [notebook about constexpr](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb), I said implementing Fibonacci series before C++ 11 involved metaprogramming; here is an implementation (much more wordy than the `constexpr` one):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<std::size_t N> template<std::size_t N>
struct Fibonacci struct Fibonacci
{ {
static std::size_t Do() static std::size_t Do()
{ {
return Fibonacci<N-1>::Do() + Fibonacci<N-2>::Do(); return Fibonacci<N-1>::Do() + Fibonacci<N-2>::Do();
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
// Don't forget the specialization for 0 and 1! // Don't forget the specialization for 0 and 1!
template<> template<>
struct Fibonacci<0ul> struct Fibonacci<0ul>
{ {
static std::size_t Do() static std::size_t Do()
{ {
return 0ul; return 0ul;
} }
}; };
template<> template<>
struct Fibonacci<1ul> struct Fibonacci<1ul>
{ {
static std::size_t Do() static std::size_t Do()
{ {
return 1ul; return 1ul;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
std::cout << Fibonacci<5ul>::Do() << std::endl; std::cout << Fibonacci<5ul>::Do() << std::endl;
std::cout << Fibonacci<10ul>::Do() << std::endl; std::cout << Fibonacci<10ul>::Do() << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And if the syntax doesn't suit you... you could always add an extra level of indirection to remove the `::Do()` part: And if the syntax doesn't suit you... you could always add an extra level of indirection to remove the `::Do()` part:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<std::size_t N> template<std::size_t N>
std::size_t FibonacciWrapper() std::size_t FibonacciWrapper()
{ {
return Fibonacci<N>::Do(); return Fibonacci<N>::Do();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
std::cout << FibonacciWrapper<5ul>() << std::endl; std::cout << FibonacciWrapper<5ul>() << std::endl;
std::cout << FibonacciWrapper<10ul>() << std::endl; std::cout << FibonacciWrapper<10ul>() << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you can see, in some cases `constexpr` really alleviates some tedious boilerplate... As you can see, in some cases `constexpr` really alleviates some tedious boilerplate...
It should be noticed that although these computations really occur at compile time, they aren't nonetheless recognized automatically as `constexpr`: It should be noticed that although these computations really occur at compile time, they aren't nonetheless recognized automatically as `constexpr`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
constexpr auto fibo_5 = FibonacciWrapper<5ul>(); // COMPILATION ERROR! constexpr auto fibo_5 = FibonacciWrapper<5ul>(); // COMPILATION ERROR!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To fix that, you need to declare `constexpr`: To fix that, you need to declare `constexpr`:
- Each of the `Do` static method (`static constexpr std::size_t Do()`) - Each of the `Do` static method (`static constexpr std::size_t Do()`)
- The `FibonacciWrapper` function (`template<std::size_t N> constexpr std::size_t FibonacciWrapper()`) - The `FibonacciWrapper` function (`template<std::size_t N> constexpr std::size_t FibonacciWrapper()`)
So in this specific case you should really go with the much less wordy and more expressive expression with `constexpr` given in [aforementioned notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb) So in this specific case you should really go with the much less wordy and more expressive expression with `constexpr` given in [aforementioned notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # References
[<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu2001</a>] Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001. [<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu2001</a>] Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Hints to more advanced concepts with templates](./5-MoreAdvanced.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Hints to more advanced concepts with templates](./5-MoreAdvanced.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <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><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> <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: %% 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... 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) ## Curiously recurrent template pattern (CRTP)
One of my own favourite idiom (so much I didn't resist writing an [entry](../7-Appendix/Crtp.ipynb) about it in the appendix). One of my own favourite idiom (so much I didn't resist writing an [entry](../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 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: 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. * Initializing properly this identifier at construction.
* Check no other objects of the same class use it already. * Check no other objects of the same class use it already.
* Provide an accessor `GetUniqueIdentifier()`. * 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). 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: The **curiously recurrent template pattern** is a very specific inheritance:
```c++ ```c++
class MyClass : public UniqueIdentifier<MyClass> class MyClass : public UniqueIdentifier<MyClass>
``` ```
where your class inherits from a template class which parameter is... your class itself. where your class inherits from a template class which parameter is... your class itself.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Traits ## 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](./2-Specialization.ipynb) in our template presentation: 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](./2-Specialization.ipynb) in our template presentation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T> template<class T>
class HoldAValue class HoldAValue
{ {
public: public:
HoldAValue(T value); HoldAValue(T value);
T GetValue() const; T GetValue() const;
private: private:
T value_; T value_;
}; };
template<class T> template<class T>
HoldAValue<T>::HoldAValue(T value) HoldAValue<T>::HoldAValue(T value)
: value_(value) : value_(value)
{ } { }
template<class T> template<class T>
T HoldAValue<T>::GetValue() const T HoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
HoldAValue<int> hint(5); HoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl; std::cout << hint.GetValue() << std::endl;
HoldAValue<std::string> sint("Hello world!"); HoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl; std::cout << sint.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This class was not especially efficient: the accessor `GetValue()`: This class was not especially efficient: the accessor `GetValue()`:
- Requires `T` is copyable. - Requires `T` is copyable.
- Copy `T`, which is potentially a time-consuming operator. - Copy `T`, which is potentially a time-consuming operator.
We could replace by `const T& GetValue() const` to solve both those issues, 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: We could replace by `const T& GetValue() const` to solve both those issues, 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <type_traits> // for std::conditional, std::is_trivial #include <type_traits> // for std::conditional, std::is_trivial
template<class T> template<class T>
class ImprovedHoldAValue class ImprovedHoldAValue
{ {
public: public:
// Traits: information about type! // Traits: information about type!
using return_value = using return_value =
typename std::conditional<std::is_trivial<T>::value, T, const T&>::type; typename std::conditional<std::is_trivial<T>::value, T, const T&>::type;
ImprovedHoldAValue(T value); ImprovedHoldAValue(T value);
return_value GetValue() const; return_value GetValue() const;
private: private:
T value_; T value_;
}; };
template<class T> template<class T>
ImprovedHoldAValue<T>::ImprovedHoldAValue(T value) ImprovedHoldAValue<T>::ImprovedHoldAValue(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Beware the trait that acts as the return value must be scoped correctly in the definition: Beware the trait that acts as the return value must be scoped correctly in the definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
typename ImprovedHoldAValue<T>::return_value ImprovedHoldAValue<T>::GetValue() const typename ImprovedHoldAValue<T>::return_value ImprovedHoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
(unless you use the alternate syntax for function): (unless you use the alternate syntax for function):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
// Doesn't work in Xeus-cling but completely valid in a real environment // Doesn't work in Xeus-cling but completely valid in a real environment
template<class T> template<class T>
auto ImprovedHoldAValue<T>::GetValue() const -> return_value auto ImprovedHoldAValue<T>::GetValue() const -> return_value
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And the result remain the same, albeit more efficient as a copy is avoided: And the result remain the same, albeit more efficient as a copy is avoided:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
ImprovedHoldAValue<int> hint(5); ImprovedHoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl; std::cout << hint.GetValue() << std::endl;
ImprovedHoldAValue<std::string> sint("Hello world!"); ImprovedHoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl; std::cout << sint.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A more complete example with an uncopyable class and the proof the alternate syntax works is available [@Coliru](https://coliru.stacked-crooked.com/a/698eb583b5a94bc7). A more complete example with an uncopyable class and the proof the alternate syntax works is available [@Coliru](https://coliru.stacked-crooked.com/a/698eb583b5a94bc7).
In fact sometimes you may even have **traits class**: class which sole purpose is to provide type information! Such classes are often used as template parameters of other classes. In fact sometimes you may even have **traits class**: class which sole purpose is to provide type information! Such classes are often used as template parameters of other classes.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Policies ## 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. 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. 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class ColorPolicyT> template<class ColorPolicyT>
class Car : public ColorPolicyT class Car : public ColorPolicyT
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
struct Blue struct Blue
{ {
void Print() const void Print() const
{ {
std::cout << "My color is blue!" << std::endl; std::cout << "My color is blue!" << std::endl;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
// Let's assume in the future a car provides a mechanism to change its color at will: // Let's assume in the future a car provides a mechanism to change its color at will:
class Changing class Changing
{ {
public: public:
void Display() const // I do not use `Print()` intentionally to illustrate there is no constraint 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! // but in true code it would be wise to use same naming scheme!
{ {
std::cout << "Current color is " << color_ << "!" << std::endl; std::cout << "Current color is " << color_ << "!" << std::endl;
} }
void ChangeColor(const std::string& new_color) void ChangeColor(const std::string& new_color)
{ {
color_ = new_color; color_ = new_color;
} }
private: private:
std::string color_ = "white"; std::string color_ = "white";
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Car<Blue> blue_car; Car<Blue> blue_car;
blue_car.Print(); blue_car.Print();
Car<Changing> future_car; Car<Changing> future_car;
future_car.Display(); future_car.Display();
future_car.ChangeColor("black"); future_car.ChangeColor("black");
future_car.Display(); future_car.Display();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Variadic templates ## Variadic templates
If you have already written some C, you must be familiar with `printf`: If you have already written some C, you must be familiar with `printf`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <cstdio> #include <cstdio>
{ {
int i = 5; int i = 5;
double d = 3.1415; double d = 3.1415;
printf("i = %d\n", i); printf("i = %d\n", i);
printf("i = %d and d = %lf\n", i, d); printf("i = %d and d = %lf\n", i, d);
} }
``` ```
%% Cell type:markdown id: tags: %% 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 (especially regarding the type of the arguments). 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 (especially regarding the type of the arguments).
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). 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
// Overload when one value only. // Overload when one value only.
template<class T> template<class T>
void Print(T value) void Print(T value)
{ {
std::cout << value << std::endl; std::cout << value << std::endl;
} }
// Overload with a variadic number of arguments // Overload with a variadic number of arguments
template<class T, class ...Args> template<class T, class ...Args>
void Print(T value, Args... args) // args here will be all parameters passed to the function from the void Print(T value, Args... args) // args here will be all parameters passed to the function from the
// second one onward. // second one onward.
{ {
Print(value); Print(value);
Print(args...); // Will call recursively `Print()` with one less argument. Print(args...); // Will call recursively `Print()` with one less argument.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Print(5, "hello", "world", "ljksfo", 3.12); Print(5, "hello", "world", "ljksfo", 3.12);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Print("One"); Print("One");
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Print(); // Compilation error: no arguments isn't accepted! Print(); // Compilation error: no arguments isn't accepted!
``` ```
%% Cell type:markdown id: tags: %% 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. 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: %% Cell type:markdown id: tags:
## Template template parameters (not a mistake...) ## 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. 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](../5-UsefulConceptsAndSTL/3-Containers.ipynb), but for now you just have to know that these containers take two template parameters: 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](../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>`). - 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. - Another optional one which specifies how the memory is allocated.
We could not bother and use directly a usual template parameter: We could not bother and use directly a usual template parameter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintSize1(const ContainerT& container) void PrintSize1(const ContainerT& container)
{ {
std::cout << container.size() << std::endl; std::cout << container.size() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may use it on seamlessly on usual STL containers: You may use it on seamlessly on usual STL containers:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <list> #include <list>
#include <deque> #include <deque>
{ {
std::vector<double> vector { 3.54, -73.1, 1004. }; std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 }; std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 }; std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 };
PrintSize1(vector); PrintSize1(vector);
PrintSize1(list); PrintSize1(list);
PrintSize1(deque); PrintSize1(deque);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, it would also work for any class that define a `size()` parameters, regardless of its nature. However, it would also work for any class that define a `size()` parameters, regardless of its nature.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
struct NonTemplateClass struct NonTemplateClass
{ {
std::string size() const std::string size() const
{ {
return "Might seem idiotic, but why not?"; return "Might seem idiotic, but why not?";
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class U, class V, class W> template<class U, class V, class W>
struct TemplateWithThreeParameters struct TemplateWithThreeParameters
{ {
int size() const int size() const
{ {
return -99; return -99;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
NonTemplateClass non_template_class; NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters; TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize1(non_template_class); PrintSize1(non_template_class);
PrintSize1(template_with_three_parameters); PrintSize1(template_with_three_parameters);
} }
``` ```
%% Cell type:markdown id: tags: %% 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). 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. 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: A possibility to limit the risk is to use a **template template parameter** in the function definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template<template <class, class> class ContainerT, class TypeT, class AllocatorT> template<template <class, class> class ContainerT, class TypeT, class AllocatorT>
void PrintSize2(const ContainerT<TypeT, AllocatorT>& container) void PrintSize2(const ContainerT<TypeT, AllocatorT>& container)
{ {
std::cout << container.size() << std::endl; std::cout << container.size() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <list> #include <list>
#include <deque> #include <deque>
{ {
std::vector<double> vector { 3.54, -73.1, 1004. }; std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 }; std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 }; 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. // At call site, you don't have to specify the template arguments that are inferred.
PrintSize2(vector); PrintSize2(vector);
PrintSize2(list); PrintSize2(list);
PrintSize2(deque); PrintSize2(deque);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
whereas my own defined types don't: whereas my own defined types don't:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
NonTemplateClass non_template_class; NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters; TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize2(non_template_class); PrintSize2(non_template_class);
PrintSize2(template_with_three_parameters); PrintSize2(template_with_three_parameters);
} }
``` ```
%% Cell type:markdown id: tags: %% 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 policies. 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 policies.
If you want to learn more about them, you should really read \cite{Alexandrescu2001}). If you want to learn more about them, you should really read \cite{Alexandrescu2001}).
Of course, `concept` introduced in C++ 20 are a much more refined tool to check template parameter type fulfills some constraints, but template template parameter are a much older feature that worked way back to pre-C++ 11 versions of the language.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # 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-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 (<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) and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment