Mentions légales du service

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

#8 Replace an by a to ease understanding by readers (one issue at a time...).

parent 806636e1
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) - [Introduction](./1-Intro.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Introduction](./1-Intro.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="#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> <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: %% Cell type:markdown id: tags:
## Motivation ## 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...)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int min(int lhs, int rhs) int min(int lhs, int rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
double min(double lhs, double rhs) double min(double lhs, double rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
float min(float lhs, float rhs) float min(float lhs, float rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
std::cout << min(5, -8) << std::endl; std::cout << min(5, -8) << std::endl;
std::cout << min(5., -8.) << std::endl; std::cout << min(5., -8.) << std::endl;
std::cout << min(5.f, -8.f) << std::endl; std::cout << min(5.f, -8.f) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% 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! 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) ## Function templates (or methods)
A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**: A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
T min2(T lhs, T rhs) T min2(T lhs, T rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
std::cout << min2<int>(5, -8) << std::endl; std::cout << min2<int>(5, -8) << std::endl;
std::cout << min2(5, -8) << std::endl; std::cout << min2(5, -8) << std::endl;
std::cout << min2(5., -8.) << std::endl; std::cout << min2(5., -8.) << std::endl;
std::cout << min2(5.f, -8.f) << std::endl; std::cout << min2(5.f, -8.f) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you can see: As you can see:
* The type is replaced by a parameter (here called `T`) * 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 of the parameters. In other words, for the following case it won't work:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
T Convert(int value) T Convert(int value)
{ {
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double x = Convert(5); // Error: can't figure out which type `T` to use! double x = Convert(5); // Error: can't figure out which type `T` to use!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
float x = Convert<double>(5); // Ok float x = Convert<double>(5); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may have see 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...) You may have see 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
{ {
Convert<std::string>(5); // Doesn't make sense so compiler will yell! Convert<std::string>(5); // Doesn't make sense so compiler will yell!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `static_assert` ### `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`: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <type_traits> // for std::is_arithmetic #include <type_traits> // for std::is_arithmetic
template<class T> template<class T>
T Convert2(int value) T Convert2(int value)
{ {
static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!"); static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!");
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
{ {
Convert2<std::string>(5); // Doesn't make sense so compiler will yell! Convert2<std::string>(5); // Doesn't make sense so compiler will yell!
// But first line is much clearer than previously... // But first line is much clearer than previously...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`static_assert` evolved in C++ 17: `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++ 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: * In C++ 17 and above, you might just give the test; it is actually handy when the test is already crystal clear:
````static_assert(std::is_same<T, int>(), "Check T is an integer");```` is a tad overkill! ````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. That being said, if the test is not that trivial you should really use the possibility to add an explanation.
## Class templates ## Class templates
We have seen templates in the case of functions, but classes can be templated as well: We have seen templates in the case of functions, but classes can be templated as well:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
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_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
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
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
HoldAValue integer(5); HoldAValue integer(5);
std::cout << "Integer hold: " << integer.GetValue() << std::endl; std::cout << "Integer hold: " << integer.GetValue() << std::endl;
HoldAValue<std::string> string("Hello world!"); // If type not specified explicitly it would have been char*... HoldAValue<std::string> string("Hello world!"); // If type not specified explicitly it would have been char*...
std::cout << "String hold: " << string.GetValue() << std::endl; std::cout << "String hold: " << string.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The template must be reminded in the definition as well; please notice before the `::` the brackets with the parameters *without their type*. The template must be reminded in the definition as well; please notice before the `::` the brackets with the parameters *without their type*.
### Template method of a template class ### Template method of a template class
Notice a template class may provide template methods: Notice a template class may provide template methods:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
class HoldAValue2 class HoldAValue2
{ {
public: public:
HoldAValue2(T value); HoldAValue2(T value);
T GetValue() const; T GetValue() const;
template<class U> template<class U>
U Convert() const; U Convert() const;
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
HoldAValue2<T>::HoldAValue2(T value) HoldAValue2<T>::HoldAValue2(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
T HoldAValue2<T>::GetValue() const T HoldAValue2<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> // template type for the class first template<class T> // template type for the class first
template<class U> // then template type for the method template<class U> // then template type for the method
U HoldAValue2<T>::Convert() const U HoldAValue2<T>::Convert() const
{ {
return static_cast<U>(value_); return static_cast<U>(value_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
HoldAValue2 hold(9); HoldAValue2 hold(9);
hold.Convert<double>(); hold.Convert<double>();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Friendship syntax ### 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
class HoldAValue3 class HoldAValue3
{ {
public: public:
HoldAValue3(T value); HoldAValue3(T value);
friend void print(const HoldAValue3<T>& obj); friend void print(const HoldAValue3<T>& obj);
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
HoldAValue3<T>::HoldAValue3(T value) HoldAValue3<T>::HoldAValue3(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template<class T> template<class T>
void print(const HoldAValue3<T>& obj) void print(const HoldAValue3<T>& obj)
{ {
// Friendship required to access private data. // Friendship required to access private data.
// I wouldn't recommend friendship where an accessor would do the same task easily! // I wouldn't recommend friendship where an accessor would do the same task easily!
std::cout << "Underlying value is " << obj.value_ << std::endl; std::cout << "Underlying value is " << obj.value_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
HoldAValue3<int> hold(5); HoldAValue3<int> hold(5);
print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling! print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To make the friendship work, you have to use in the friendship declaration another label for the template parameter: To make the friendship work, you have to use in the friendship declaration another label for the template parameter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
class HoldAValue4 class HoldAValue4
{ {
public: public:
HoldAValue4(T value); HoldAValue4(T value);
// 'Repeating' the list of template arguments and not using the ones from the class will fix the issue... // '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. // T wouldn't have work here; the label MUST differ.
template<class U> template<class U>
friend void print(const HoldAValue4<U>& obj); friend void print(const HoldAValue4<U>& obj);
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class T> template<class T>
HoldAValue4<T>::HoldAValue4(T value) HoldAValue4<T>::HoldAValue4(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions! // 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> template<class T>
void print(const HoldAValue4<T>& obj) void print(const HoldAValue4<T>& obj)
{ {
std::cout << "Underlying value is " << obj.value_ << std::endl; std::cout << "Underlying value is " << obj.value_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
HoldAValue4<int> hold(5); HoldAValue4<int> hold(5);
print(hold); // Ok! print(hold); // Ok!
} }
``` ```
%% Cell type:markdown id: tags: %% 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.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it. 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.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Type or non-type template parameter ## 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!) 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: Both can be mixed in a given template declaration:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
class MyArray class MyArray
{ {
public: public:
explicit MyArray(TypeT initial_value); explicit MyArray(TypeT initial_value);
private: private:
TypeT content_[Nelts]; TypeT content_[Nelts];
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
MyArray<TypeT, Nelts>::MyArray(TypeT initial_value) MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)
{ {
for (auto i = 0ul; i < Nelts; ++i) for (std::size_t i = 0; i < Nelts; ++i)
content_[i] = initial_value; content_[i] = initial_value;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
MyArray<int, 5ul> array1(2); MyArray<int, 5ul> array1(2);
MyArray<double, 2ul> array2(3.3); MyArray<double, 2ul> array2(3.3);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, you can't provide a type parameter where a non-type is expected (and vice-versa): However, you can't provide a type parameter where a non-type is expected (and vice-versa):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR! MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
MyArray<int, int> array1(2); // COMPILATION ERROR! MyArray<int, int> array1(2); // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Few precisions about templates ## 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. * 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`: * In the template syntax, `class` might be replaced by `typename`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<typename T> template<typename T>
T Convert3(int value) T Convert3(int value)
{ {
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:markdown id: tags: %% 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! 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 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. * 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. * 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: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_ © _CNRS 2016_ - _Inria 2018-2021_
_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/)_ _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)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment