Mentions légales du service

Skip to content
Snippets Groups Projects

Re-reading and modifications in template section

12 files
+ 952
1053
Compare changes
  • Side-by-side
  • Inline
Files
12
+ 2
2
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Introduction](./1-Intro.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="#Motivation" data-toc-modified-id="Motivation-1">Motivation</a></span></li><li><span><a href="#Function-templates-(or-methods)" data-toc-modified-id="Function-templates-(or-methods)-2">Function templates (or methods)</a></span><ul class="toc-item"><li><span><a href="#static_assert" data-toc-modified-id="static_assert-2.1"><code>static_assert</code></a></span></li></ul></li><li><span><a href="#Class-templates" data-toc-modified-id="Class-templates-3">Class templates</a></span><ul class="toc-item"><li><span><a href="#Template-method-of-a-template-class" data-toc-modified-id="Template-method-of-a-template-class-3.1">Template method of a template class</a></span></li><li><span><a href="#Friendship-syntax" data-toc-modified-id="Friendship-syntax-3.2">Friendship syntax</a></span></li></ul></li><li><span><a href="#Type-or-non-type-template-parameter" data-toc-modified-id="Type-or-non-type-template-parameter-4">Type or non-type template parameter</a></span></li><li><span><a href="#Few-precisions-about-templates" data-toc-modified-id="Few-precisions-about-templates-5">Few precisions about templates</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Motivation
The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL provides it already...)
The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL [provides it already](https://en.cppreference.com/w/cpp/algorithm/min)...)
%% Cell type:code id: tags:
``` C++17
int Min(int lhs, int rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
double Min(double lhs, double rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
float Min(float lhs, float rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << Min(5, -8) << std::endl;
std::cout << Min(5., -8.) << std::endl;
std::cout << Min(5.f, -8.f) << std::endl;
}
```
%% Cell type:markdown id: tags:
Already tired? Yet we have still to define the same for unsigned versions, other types such as `short` ou `long`... And it's not very [**DRY** (Don't Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself): you may have noticed the implementation is exactly the same!
## Function templates (or methods)
A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**:
%% Cell type:code id: tags:
``` C++17
template<class T>
T MinWithTemplate(T lhs, T rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << MinWithTemplate<int>(5, -8) << std::endl;
std::cout << MinWithTemplate(5, -8) << std::endl;
std::cout << MinWithTemplate(5., -8.) << std::endl;
std::cout << MinWithTemplate(5.f, -8.f) << std::endl;
}
```
%% Cell type:markdown id: tags:
As you can see:
* The type is replaced by a parameter (here called `T`)
* In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one of the parameters. In other words, for the following case it won't work:
* In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one or several of the parameters. In other words, for the following case it won't work:
%% Cell type:code id: tags:
``` C++17
template<class T>
T Convert(int value)
{
return static_cast<T>(value);
}
```
%% Cell type:code id: tags:
``` C++17
{
double x = Convert(5); // Error: can't figure out which type `T` to use!
}
```
%% Cell type:code id: tags:
``` C++17
{
float x = Convert<double>(5); // Ok
}
```
%% Cell type:markdown id: tags:
You may have noticed there are no constraints on `T` whatsoever. If you invoke a template and the implementation doesn't make sense for the chosen type the compiler will yell (no doubt you have already seen compilers are extremely talented to do so and it is even truer regarding templates...)
%% Cell type:code id: tags:
``` C++17
#include <string>
{
Convert<std::string>(5); // Doesn't make sense so compiler will yell!
}
```
%% Cell type:markdown id: tags:
### `static_assert`
Of course, it would be nicer to get a clearer error message when an impossible type is provided... C++ 20 will introduce the [concept](https://en.cppreference.com/w/cpp/language/constraints) to constraint properly which type is acceptable, but C++ 11 already introduced a way slightly better than C++ 03 with `static_assert`:
%% Cell type:code id: tags:
``` C++17
#include <type_traits> // for std::is_arithmetic
template<class T>
T Convert2(int value)
{
static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!");
return static_cast<T>(value);
}
```
%% Cell type:code id: tags:
``` C++17
#include <string>
{
Convert2<std::string>(5); // Doesn't make sense so compiler will yell!
// But first line is much clearer than previously...
}
```
%% Cell type:markdown id: tags:
`static_assert` evolved in C++ 17:
* In C++ 11, it takes two arguments: the test and a string which features an explanation on the topic at hand.
* In C++ 17 and above, you might just give the test; it is actually handy when the test is already crystal clear:
```c++
static_assert(std::is_same<T, int>(), "Check T is an integer");
```
is a tad overkill!
That being said, if the test is not that trivial you should really use the possibility to add an explanation.
## Class templates
We have seen templates in the case of functions, but classes can be templated as well:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue
{
public:
HoldAValue(T value);
T GetValue() const;
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
template<class T>
T HoldAValue<T>::GetValue() const
{
return value_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
{
HoldAValue integer(5);
std::cout << "Integer hold: " << integer.GetValue() << std::endl;
HoldAValue<std::string> string("Hello world!"); // If type not specified explicitly it would have been char*...
std::cout << "String hold: " << string.GetValue() << std::endl;
}
```
%% Cell type:markdown id: tags:
The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters.
### Template method of a template class
Notice a template class may provide template methods:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue2
{
public:
HoldAValue2(T value);
T GetValue() const;
template<class U>
U Convert() const;
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue2<T>::HoldAValue2(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
template<class T>
T HoldAValue2<T>::GetValue() const
{
return value_;
}
```
%% Cell type:markdown id: tags:
In this case there are two `template` keyword for the definition: one for the class and the other for the method:
%% Cell type:code id: tags:
``` C++17
template<class T> // template type for the class first
template<class U> // then template type for the method
U HoldAValue2<T>::Convert() const
{
return static_cast<U>(value_);
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue2 hold(9);
hold.Convert<double>();
}
```
%% Cell type:markdown id: tags:
### Friendship syntax
There is a weird and specific syntax that is expected if you want to declare a friendship to a function. Quite naturally you would probably write something like:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue3
{
public:
HoldAValue3(T value);
friend void Print(const HoldAValue3<T>& obj);
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue3<T>::HoldAValue3(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
template<class T>
void Print(const HoldAValue3<T>& obj)
{
// Friendship required to access private data.
// I wouldn't recommend friendship where an accessor would do the same task easily!
std::cout << "Underlying value is " << obj.value_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue3<int> hold(5);
Print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!
}
```
%% Cell type:markdown id: tags:
To make the friendship work, you have to use in the friendship declaration another label for the template parameter:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue4
{
public:
HoldAValue4(T value);
// 'Repeating' the list of template arguments and not using the ones from the class will fix the issue...
// T wouldn't have work here; the label MUST differ.
template<class U>
friend void Print(const HoldAValue4<U>& obj);
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue4<T>::HoldAValue4(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions!
template<class T>
void Print(const HoldAValue4<T>& obj)
{
std::cout << "Underlying value is " << obj.value_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue4<int> hold(5);
Print(hold); // Ok!
}
```
%% Cell type:markdown id: tags:
This way of declaring friendship works but is not entirely fullproof: `Print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.archive.org/web/20211003194958/https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it.
%% Cell type:markdown id: tags:
## Type or non-type template parameter
The examples so far are using **type** parameters: the `T` in the example stands for a type and is deemed to be substituted by a type. Templates may also use **non type** parameters, which are in most cases `enum` or `integral constant` types (beware: floating point types parameters or `std::string` **can't** be used as template parameters!)
Both can be mixed in a given template declaration:
%% Cell type:code id: tags:
``` C++17
template<class TypeT, std::size_t Nelts>
class MyArray
{
public:
explicit MyArray(TypeT initial_value);
private:
TypeT content_[Nelts];
};
```
%% Cell type:code id: tags:
``` C++17
template<class TypeT, std::size_t Nelts>
MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)
{
for (std::size_t i = 0; i < Nelts; ++i)
content_[i] = initial_value;
}
```
%% Cell type:code id: tags:
``` C++17
{
MyArray<int, 5ul> array1(2);
MyArray<double, 2ul> array2(3.3);
}
```
%% Cell type:markdown id: tags:
However, you can't provide a type parameter where a non-type is expected (and vice-versa):
%% Cell type:code id: tags:
``` C++17
{
MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR!
}
```
%% Cell type:code id: tags:
``` C++17
{
MyArray<int, int> array1(2); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
## Few precisions about templates
* Template parameters must be known or computed at **compile time**. You can't therefore instantiate a template from a value that was entered by the user of the program or computed at runtime.
* In the template syntax, `class` might be replaced by `typename`:
%% Cell type:code id: tags:
``` C++17
template<typename T>
T Convert3(int value)
{
return static_cast<T>(value);
}
```
%% Cell type:markdown id: tags:
There are exactly zero differences between both keywords; some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions!
* The definition of the templates must be provided in header files, not in compiled files. The reason is that the compiler can't know all the types for which the template might be used: you may use `std::min` for your own defined types (provided they define a `<` operator...) and obviously STL writers can't foresee which type you will come up with!
* The compiler will generate the code for each type given to the template; for instance if `Convert<double>`, `Convert<unsigned int>` and `Convert<short>` are used in your code, the content of this template function will be instantiated thrice! This can lead to **code bloat**: lots of assembler code is generated, and compilation time may increase significatively.
* So templates are a _very_ nice tool, but make sure to use them only when they're relevant, and you may employ techniques to limit the amount of code actually defined within the template definitions - the more straightforward being defining in a non template function the parts of the implementation that do not depend on the template parameter type. This way, this part of the code is not duplicated for each different type.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
Loading