Mentions légales du service

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

Add an example of the usage of enum class.

parent 33635dbe
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++](/) - [Procedural programming](/notebooks/1-ProceduralProgramming/0-main.ipynb) - [Predefined types](/notebooks/1-ProceduralProgramming/3-Types.ipynb) # [Getting started in C++](/) - [Procedural programming](/notebooks/1-ProceduralProgramming/0-main.ipynb) - [Predefined types](/notebooks/1-ProceduralProgramming/3-Types.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="#Boolean" data-toc-modified-id="Boolean-1">Boolean</a></span></li><li><span><a href="#Enumerations" data-toc-modified-id="Enumerations-2">Enumerations</a></span><ul class="toc-item"><li><span><a href="#Historical-enumerations" data-toc-modified-id="Historical-enumerations-2.1">Historical enumerations</a></span></li><li><span><a href="#New-enumerations" data-toc-modified-id="New-enumerations-2.2">New enumerations</a></span></li></ul></li><li><span><a href="#Numerical-types" data-toc-modified-id="Numerical-types-3">Numerical types</a></span><ul class="toc-item"><li><ul class="toc-item"><li><span><a href="#List-of-numerical-types" data-toc-modified-id="List-of-numerical-types-3.0.1">List of numerical types</a></span></li><li><span><a href="#Numeric-limits" data-toc-modified-id="Numeric-limits-3.0.2">Numeric limits</a></span></li><li><span><a href="#Conversions-between-digital-types" data-toc-modified-id="Conversions-between-digital-types-3.0.3">Conversions between digital types</a></span></li></ul></li><li><span><a href="#Explicit-conversions-inherited-from-C" data-toc-modified-id="Explicit-conversions-inherited-from-C-3.1">Explicit conversions inherited from C</a></span></li><li><span><a href="#Explicit-conversions-by-static_cast" data-toc-modified-id="Explicit-conversions-by-static_cast-3.2">Explicit conversions by static_cast</a></span></li><li><span><a href="#Other-explicit-conversions" data-toc-modified-id="Other-explicit-conversions-3.3">Other explicit conversions</a></span></li></ul></li><li><span><a href="#Characters-and-strings" data-toc-modified-id="Characters-and-strings-4">Characters and strings</a></span><ul class="toc-item"><li><span><a href="#Historical-strings" data-toc-modified-id="Historical-strings-4.1">Historical strings</a></span></li><li><span><a href="#std::string" data-toc-modified-id="std::string-4.2">std::string</a></span></li></ul></li><li><span><a href="#Renaming-types" data-toc-modified-id="Renaming-types-5">Renaming types</a></span></li><li><span><a href="#decltype-and-auto" data-toc-modified-id="decltype-and-auto-6"><code>decltype</code> and <code>auto</code></a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Boolean" data-toc-modified-id="Boolean-1">Boolean</a></span></li><li><span><a href="#Enumerations" data-toc-modified-id="Enumerations-2">Enumerations</a></span><ul class="toc-item"><li><span><a href="#Historical-enumerations" data-toc-modified-id="Historical-enumerations-2.1">Historical enumerations</a></span></li><li><span><a href="#New-enumerations" data-toc-modified-id="New-enumerations-2.2">New enumerations</a></span></li></ul></li><li><span><a href="#Numerical-types" data-toc-modified-id="Numerical-types-3">Numerical types</a></span><ul class="toc-item"><li><ul class="toc-item"><li><span><a href="#List-of-numerical-types" data-toc-modified-id="List-of-numerical-types-3.0.1">List of numerical types</a></span></li><li><span><a href="#Numeric-limits" data-toc-modified-id="Numeric-limits-3.0.2">Numeric limits</a></span></li><li><span><a href="#Conversions-between-digital-types" data-toc-modified-id="Conversions-between-digital-types-3.0.3">Conversions between digital types</a></span></li></ul></li><li><span><a href="#Explicit-conversions-inherited-from-C" data-toc-modified-id="Explicit-conversions-inherited-from-C-3.1">Explicit conversions inherited from C</a></span></li><li><span><a href="#Explicit-conversions-by-static_cast" data-toc-modified-id="Explicit-conversions-by-static_cast-3.2">Explicit conversions by static_cast</a></span></li><li><span><a href="#Other-explicit-conversions" data-toc-modified-id="Other-explicit-conversions-3.3">Other explicit conversions</a></span></li></ul></li><li><span><a href="#Characters-and-strings" data-toc-modified-id="Characters-and-strings-4">Characters and strings</a></span><ul class="toc-item"><li><span><a href="#Historical-strings" data-toc-modified-id="Historical-strings-4.1">Historical strings</a></span></li><li><span><a href="#std::string" data-toc-modified-id="std::string-4.2">std::string</a></span></li></ul></li><li><span><a href="#Renaming-types" data-toc-modified-id="Renaming-types-5">Renaming types</a></span></li><li><span><a href="#decltype-and-auto" data-toc-modified-id="decltype-and-auto-6"><code>decltype</code> and <code>auto</code></a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Boolean ## Boolean
Variables with type `bool` may be set to true or false. Variables with type `bool` may be set to true or false.
It should be noted that this type did not originally exist, and that C++ instructions with conditions do not necessarily expect boolean values, but rather integers. It should be noted that this type did not originally exist, and that C++ instructions with conditions do not necessarily expect boolean values, but rather integers.
There is a form of equivalence between booleans and integers: any null integer is equivalent to `false`, and any other value is equivalent to `true`. There is a form of equivalence between booleans and integers: any null integer is equivalent to `false`, and any other value is equivalent to `true`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
bool a = true, b(false), c{true}; bool a = true, b(false), c{true};
bool d; // UNDEFINED !! bool d; // UNDEFINED !!
if (d) if (d)
std::cout << "This text might appear or not - it's truly undefined and may vary from " std::cout << "This text might appear or not - it's truly undefined and may vary from "
"one run/compiler/architecture/etc... to another!" << std::endl; "one run/compiler/architecture/etc... to another!" << std::endl;
int n = 5; int n = 5;
if (n) if (n)
std::cout << "Boolean value of " << n << " is true." << std::endl; std::cout << "Boolean value of " << n << " is true." << std::endl;
n = 0; n = 0;
if (!n) // ! is the not operator: the condition is true if n is false. if (!n) // ! is the not operator: the condition is true if n is false.
std::cout << "Boolean value of " << n << " is false." << std::endl; std::cout << "Boolean value of " << n << " is false." << std::endl;
n = -2; n = -2;
if (n) if (n)
std::cout << "Boolean value of " << n << " is true." << std::endl; std::cout << "Boolean value of " << n << " is true." << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Enumerations ## Enumerations
### Historical enumerations ### Historical enumerations
The historical enumerations `enum` of C++ allow to define constants that are treated as integers, and that can be initialized from integers. By default the first value is 0 and the `enum` is incremented for each value, but it is possible to bypass these default values and provide the desired numerical value yourself. The historical enumerations `enum` of C++ allow to define constants that are treated as integers, and that can be initialized from integers. By default the first value is 0 and the `enum` is incremented for each value, but it is possible to bypass these default values and provide the desired numerical value yourself.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
enum color { red, green, blue } ; enum color { red, green, blue } ;
std::cout << red << " " << green << " " << blue << " (expected: 0, 1, 2)" << std::endl; std::cout << red << " " << green << " " << blue << " (expected: 0, 1, 2)" << std::endl;
enum shape { circle=10, square, triangle=20 }; enum shape { circle=10, square, triangle=20 };
std::cout << circle << " " << square << " " << triangle << " (expected: 10, 11, 20)"<< std::endl; // 10 11 20 std::cout << circle << " " << square << " " << triangle << " (expected: 10, 11, 20)"<< std::endl; // 10 11 20
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
These `enum` are placeholders for integers and might be used as such: These `enum` are placeholders for integers and might be used as such:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
enum color { red, green, blue } ; enum color { red, green, blue } ;
int a { 5 }; int a { 5 };
color c = green; color c = green;
int b = a + c; int b = a + c;
std::cout << "b = " << b << " (expected: 6)" << std::endl; std::cout << "b = " << b << " (expected: 6)" << std::endl;
enum shape { circle=10, square, triangle=20 }; enum shape { circle=10, square, triangle=20 };
shape s = triangle; shape s = triangle;
int d = s + c; int d = s + c;
std::cout << "d = " << d << " (expected: 21... but we've just added a shape to a color without ado!)" << std::endl; std::cout << "d = " << d << " (expected: 21... but we've just added a shape to a color without ado!)" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A shortcoming of historical `enum ` is that the same word can't be used in two different `enum`: A shortcoming of historical `enum ` is that the same word can't be used in two different `enum`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
enum is_positive { yes, no }; enum is_positive { yes, no };
enum is_colored { yes, no }; // COMPILATION ERROR! enum is_colored { yes, no }; // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### New enumerations ### New enumerations
To overcome the two limitations we have just mentioned, C++11 makes it possible to declare new `enum class` enumerations, each constituting a separate type, not implicitly convertible into an integer. This type protects against previous errors at the cost of a little more writing work. To overcome the two limitations we have just mentioned, C++11 makes it possible to declare new `enum class` enumerations, each constituting a separate type, not implicitly convertible into an integer. This type protects against previous errors at the cost of a little more writing work.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
enum class is_positive { yes, no }; enum class is_positive { yes, no };
enum class is_colored { yes, no }; // OK enum class is_colored { yes, no }; // OK
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
yes; // COMPILATION ERROR: `enum class ` value must be prefixed! (see below) yes; // COMPILATION ERROR: `enum class ` value must be prefixed! (see below)
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
is_positive p = is_positive::yes; // OK is_positive p = is_positive::yes; // OK
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int is_positive_p = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer int is_positive_p = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
is_positive::yes + is_colored::no; // COMPILATION ERROR: addition of two unrelated types is_positive::yes + is_colored::no; // COMPILATION ERROR: addition of two unrelated types
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
enum class color { red, green, blue } ; enum class color { red, green, blue } ;
color c = color::green; color c = color::green;
bool is_more_than_red = (c > color::red); // Both belong to the same type and therefore might be compared bool is_more_than_red = (c > color::red); // Both belong to the same type and therefore might be compared
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
These enum types are really handy to make code more expressive, especially in function calls:
````
f(print::yes, perform_checks::no);
````
is much more expressive (and less error-prone) than:
````
f(true, false);
````
for which you will probably need to go check the prototype to figure out what each argument stands for.
%% Cell type:markdown id: tags:
## Numerical types ## Numerical types
#### List of numerical types #### List of numerical types
The FORTRAN correspondences below are given as examples. The The FORTRAN correspondences below are given as examples. The
size of the C++ digital types can vary depending on the processor used. The size of the C++ digital types can vary depending on the processor used. The
standard C++ only imposes `short <= int <= long` and `float <= double <= long double`. This makes these predefined types unportable. Like many things standard C++ only imposes `short <= int <= long` and `float <= double <= long double`. This makes these predefined types unportable. Like many things
in C, and therefore in C++, performance is given priority over any other consideration. in C, and therefore in C++, performance is given priority over any other consideration.
The default integer and real types, `int` and `double`, are assumed The default integer and real types, `int` and `double`, are assumed
to match the size of the processor registers and be the fastest (for more details see [the article on cppreference](http://en.cppreference.com/w/cpp/language/types)) to match the size of the processor registers and be the fastest (for more details see [the article on cppreference](http://en.cppreference.com/w/cpp/language/types))
| C++ | Fortran | Observations | 0 notation | | C++ | Fortran | Observations | 0 notation |
|:------------- |:---------:|:-------------------:|:----------:| |:------------- |:---------:|:-------------------:|:----------:|
| `short` | INTEGER*2 | At least on 16 bits | None | | `short` | INTEGER*2 | At least on 16 bits | None |
| `int` | INTEGER*4 | At least on 16 bits | 0 | | `int` | INTEGER*4 | At least on 16 bits | 0 |
| `long` | INTEGER*8 | At least on 32 bits | 0l | | `long` | INTEGER*8 | At least on 32 bits | 0l |
| `long long` | INTEGER*16| At least on 64 bits | 0ll | | `long long` | INTEGER*16| At least on 64 bits | 0ll |
| `float` | REAL*4 | - | 0.f | | `float` | REAL*4 | - | 0.f |
| `double` | REAL*8 | - | 0. | | `double` | REAL*8 | - | 0. |
| `long double` | REAL*16 | - | 0.l | | `long double` | REAL*16 | - | 0.l |
All integer types (`short`, `int` and `long`) also have an unsigned variant, for example All integer types (`short`, `int` and `long`) also have an unsigned variant, for example
`unsigned int`, which only takes positive values. `unsigned int`, which only takes positive values.
It should also be noted that the type `char` is the equivalent of one byte, It should also be noted that the type `char` is the equivalent of one byte,
and depending on the context will be interpreted as a number or as a and depending on the context will be interpreted as a number or as a
character. character.
If you need an integer type of a defined size, regardless of the type of processor or platform used, you should use those already defined in `<cstdint>` for C++11 (for more details click [here](http://en.cppreference.com/w/cpp/types/integer)). If you need an integer type of a defined size, regardless of the type of processor or platform used, you should use those already defined in `<cstdint>` for C++11 (for more details click [here](http://en.cppreference.com/w/cpp/types/integer)).
The _0 notation column_ is the way to notice explicitly the type in an expression; of course any value might be used instead of 0. A `u` might be used to signal the unsigned status for integer types; for instance `3ul` means 3 as an _unsigned long_. `auto` notation below will illustrate a case in which such a notation is useful. The _0 notation column_ is the way to notice explicitly the type in an expression; of course any value might be used instead of 0. A `u` might be used to signal the unsigned status for integer types; for instance `3ul` means 3 as an _unsigned long_. `auto` notation below will illustrate a case in which such a notation is useful.
#### Numeric limits #### Numeric limits
Always keep in mind the types of the computer don't match the abstract concept you may use in mathematics... The types stored especially don't go from minus infinity to infinity: Always keep in mind the types of the computer don't match the abstract concept you may use in mathematics... The types stored especially don't go from minus infinity to infinity:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <limits> // for std::numeric_limits #include <limits> // for std::numeric_limits
{ {
std::cout << "int [min, max] = [" << std::numeric_limits<int>::lowest() << ", " std::cout << "int [min, max] = [" << std::numeric_limits<int>::lowest() << ", "
<< std::numeric_limits<int>::max() << "]" << std::endl; << std::numeric_limits<int>::max() << "]" << std::endl;
std::cout << "unsigned int [min, max] = [" << std::numeric_limits<unsigned int>::lowest() << ", " std::cout << "unsigned int [min, max] = [" << std::numeric_limits<unsigned int>::lowest() << ", "
<< std::numeric_limits<unsigned int>::max() << "]" << std::endl; << std::numeric_limits<unsigned int>::max() << "]" << std::endl;
std::cout << "short [min, max] = [" << std::numeric_limits<short>::lowest() << ", " std::cout << "short [min, max] = [" << std::numeric_limits<short>::lowest() << ", "
<< std::numeric_limits<short>::max() << "]" << std::endl; << std::numeric_limits<short>::max() << "]" << std::endl;
std::cout << "long [min, max] = [" << std::numeric_limits<long>::lowest() << ", " std::cout << "long [min, max] = [" << std::numeric_limits<long>::lowest() << ", "
<< std::numeric_limits<long>::max() << "]" << std::endl; << std::numeric_limits<long>::max() << "]" << std::endl;
std::cout << "float [min, max] = [" << std::numeric_limits<float>::lowest() << ", " std::cout << "float [min, max] = [" << std::numeric_limits<float>::lowest() << ", "
<< std::numeric_limits<float>::max() << "]" << std::endl; << std::numeric_limits<float>::max() << "]" << std::endl;
std::cout << "double [min, max] = [" << std::numeric_limits<double>::lowest() << ", " std::cout << "double [min, max] = [" << std::numeric_limits<double>::lowest() << ", "
<< std::numeric_limits<double>::max() << "]" << std::endl; << std::numeric_limits<double>::max() << "]" << std::endl;
std::cout << "long double [min, max] = [" << std::numeric_limits<long double>::lowest() << ", " std::cout << "long double [min, max] = [" << std::numeric_limits<long double>::lowest() << ", "
<< std::numeric_limits<long double>::max() << "]" << std::endl; << std::numeric_limits<long double>::max() << "]" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If an initial value is not in the range, the compiler will yell: If an initial value is not in the range, the compiler will yell:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
short s = -33010; // triggers a warning: outside the range short s = -33010; // triggers a warning: outside the range
std::cout << s << std::endl; std::cout << s << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, if you go beyond the numeric limit during a computation you're on your own: However, if you go beyond the numeric limit during a computation you're on your own:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <limits> // for std::numeric_limits #include <limits> // for std::numeric_limits
{ {
unsigned int max = std::numeric_limits<unsigned int>::max(); unsigned int max = std::numeric_limits<unsigned int>::max();
std::cout << "Max = " << max << std::endl; std::cout << "Max = " << max << std::endl;
std::cout << "Max + 1 = " << max + 1 << "!" << std::endl; std::cout << "Max + 1 = " << max + 1 << "!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
When you reach the end of a type, a modulo is actually applied to make put it back into the range! When you reach the end of a type, a modulo is actually applied to make put it back into the range!
Don't worry, for most computations you shouldn't run into this kind of trouble, but if you are dealing with important values it is important to keep in mind this kind of issues. Don't worry, for most computations you shouldn't run into this kind of trouble, but if you are dealing with important values it is important to keep in mind this kind of issues.
The most obvious way to avoid this is to choose appropriate types: if your integer might be huge a `long` is more appropriate than an `int`. The most obvious way to avoid this is to choose appropriate types: if your integer might be huge a `long` is more appropriate than an `int`.
Other languages such as Python gets a underlying integer model that is resilient to this kind of issue but there is a cost behind; types such as those used in C++ are tailored to favor optimization on your hardware. Other languages such as Python gets a underlying integer model that is resilient to this kind of issue but there is a cost behind; types such as those used in C++ are tailored to favor optimization on your hardware.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Conversions between digital types #### Conversions between digital types
In C++11, there is a difference in compiler behavior between initialization and assignment. Accuracy losses are allowed during an assignment, but not during an initialization between braces: In C++11, there is a difference in compiler behavior between initialization and assignment. Accuracy losses are allowed during an assignment, but not during an initialization between braces:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
float f = 1.12345678901234567890; float f = 1.12345678901234567890;
double d = 2.12345678901234567890; double d = 2.12345678901234567890;
float f_d(d); float f_d(d);
float f_dd = d; float f_dd = d;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
are ok while the operation below doesn't compile: are ok while the operation below doesn't compile:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double d = 2.12345678901234567890; double d = 2.12345678901234567890;
float f_d{d}; float f_d{d};
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Accuracy losses are detected during conversion: Accuracy losses are detected during conversion:
* from a floating point type (`long double`, `double` and `float`) into an integer type. * from a floating point type (`long double`, `double` and `float`) into an integer type.
* from a `long double` into a `double` or a `float`, unless the source is constant and its value fits into the type of the destination. * from a `long double` into a `double` or a `float`, unless the source is constant and its value fits into the type of the destination.
* from a `double` into a `float`, unless the source is constant and its value fits in the type of the destination. * from a `double` into a `float`, unless the source is constant and its value fits in the type of the destination.
* from an integer type to an enumerated or floating point type, unless the source is constant and its value fits into the type of the destination. * from an integer type to an enumerated or floating point type, unless the source is constant and its value fits into the type of the destination.
* from an integer type to an enumerated type or another integer type, unless the source is constant and its value fits into the type of the destination. * from an integer type to an enumerated type or another integer type, unless the source is constant and its value fits into the type of the destination.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Explicit conversions inherited from C ### Explicit conversions inherited from C
In the case of an explicit conversion, the programmer explicitly says which conversion to use. In the case of an explicit conversion, the programmer explicitly says which conversion to use.
C++ inherits the forcing mechanism of the C type: C++ inherits the forcing mechanism of the C type:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
unsigned short i = 42000 ; unsigned short i = 42000 ;
short j = short(i) ; short j = short(i) ;
unsigned short k = (unsigned short)(j) ; unsigned short k = (unsigned short)(j) ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is **not recommended** to use this type of conversion: even if it is clearly faster to type, it is less accurate and does not stand out clearly when reading a code; it is preferable to use the other conversion modes mentioned below. It is **not recommended** to use this type of conversion: even if it is clearly faster to type, it is less accurate and does not stand out clearly when reading a code; it is preferable to use the other conversion modes mentioned below.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Explicit conversions by static_cast ### Explicit conversions by static_cast
C++ has also redefined a family of type forcing, C++ has also redefined a family of type forcing,
more verbose but more precise. The most common type of explicit conversion is the `static_cast`: more verbose but more precise. The most common type of explicit conversion is the `static_cast`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
unsigned short i = 42000; unsigned short i = 42000;
short j = static_cast<short>(i); short j = static_cast<short>(i);
unsigned short k = static_cast<unsigned short>(j); unsigned short k = static_cast<unsigned short>(j);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Another advantage of this more verbosy syntax is that you may find it more easily in your code with your editor search functionality. Another advantage of this more verbosy syntax is that you may find it more easily in your code with your editor search functionality.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Other explicit conversions ### Other explicit conversions
There are 3 other types of C++ conversions: There are 3 other types of C++ conversions:
* `const_cast`, to add or remove constness to a reference or a pointer (obviously to be used with great caution!) * `const_cast`, to add or remove constness to a reference or a pointer (obviously to be used with great caution!)
* `dynamic_cast`, which will be introduced when we'll deal with polymorphism. * `dynamic_cast`, which will be introduced when we'll deal with polymorphism.
* `reinterpret_cast`, which is a very brutal cast which changes the type into any other type, regardless of the compatibility of the two types considered. It is a dangerous one that should be considered only in very last resort (usually when interacting with a C library). * `reinterpret_cast`, which is a very brutal cast which changes the type into any other type, regardless of the compatibility of the two types considered. It is a dangerous one that should be considered only in very last resort (usually when interacting with a C library).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Characters and strings ## Characters and strings
### Historical strings ### Historical strings
In C, a character string is literally an array of `char` variables, the last character of which is by convention the symbol `\0`. In C, a character string is literally an array of `char` variables, the last character of which is by convention the symbol `\0`.
The `strlen` function returns the length of a string, which is the number of characters between the very first character and the first occurrence of `\0`. The `strlen` function returns the length of a string, which is the number of characters between the very first character and the first occurrence of `\0`.
The `strcpy` function copies a character string to a new memory location; care must be taken to ensure that the destination is large enough to avoid any undefined behavior. The `strcpy` function copies a character string to a new memory location; care must be taken to ensure that the destination is large enough to avoid any undefined behavior.
The `strncpy` function allows you to copy only the first <b>n</b> first characters, where <b>n</b> is the third parameter of the function. Same remark about the need to foresee a large enough destination. The `strncpy` function allows you to copy only the first <b>n</b> first characters, where <b>n</b> is the third parameter of the function. Same remark about the need to foresee a large enough destination.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <cstring> // For strlen, strcpy, strncpy #include <cstring> // For strlen, strcpy, strncpy
{ {
char bonjour[] = {'b','o','n','j','o','u','r','\0'}; char bonjour[] = {'b','o','n','j','o','u','r','\0'};
char coucou[] = "coucou"; char coucou[] = "coucou";
char* salut = "salut"; // To avoid in C++ (see warning) char* salut = "salut"; // To avoid in C++ (see warning)
char copy[10] = {}; // = {'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'}; char copy[10] = {}; // = {'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
strcpy(copy, bonjour); strcpy(copy, bonjour);
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
strncpy(copy, coucou, strlen(coucou)); strncpy(copy, coucou, strlen(coucou));
copy[strlen(coucou)] = '\0'; // Don't forget to terminate the string! copy[strlen(coucou)] = '\0'; // Don't forget to terminate the string!
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are several other functions related to historical strings; for more information, do not hesitate to consult [this reference page](http://www.cplusplus.com/reference/cstring/). There are several other functions related to historical strings; for more information, do not hesitate to consult [this reference page](http://www.cplusplus.com/reference/cstring/).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### std::string ### std::string
In modern C++, rather than bothering with character tables In modern C++, rather than bothering with character tables
which come from the C language, it's easier to use the type `std::string`, provided which come from the C language, it's easier to use the type `std::string`, provided
through the standard language library: through the standard language library:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <cstring> // For strlen #include <cstring> // For strlen
#include <string> // For std::string #include <string> // For std::string
{ {
const char* bonjour_str = "bonjour"; const char* bonjour_str = "bonjour";
std::string bonjour = bonjour_str; std::string bonjour = bonjour_str;
std::string salut("salut"); std::string salut("salut");
std::string coucou {"coucou"}; std::string coucou {"coucou"};
std::string copy {}; std::string copy {};
copy = bonjour; copy = bonjour;
std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl;
copy = coucou; // please notice affectation is much more straightforward copy = coucou; // please notice affectation is much more straightforward
std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl;
const char* copy_str = bonjour.data(); // Returns a classic C-string (from C++11 onward) const char* copy_str = bonjour.data(); // Returns a classic C-string (from C++11 onward)
const char* old_copy_str = &bonjour[0]; // Same before C++11... const char* old_copy_str = &bonjour[0]; // Same before C++11...
std::cout << "String '" << copy_str << "' is " << strlen(copy_str) << " characters long." << std::endl; std::cout << "String '" << copy_str << "' is " << strlen(copy_str) << " characters long." << std::endl;
std::string dynamic {"dynamic std::string"}; std::string dynamic {"dynamic std::string"};
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl; std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
dynamic = "std::string is dynamical and flexible"; dynamic = "std::string is dynamical and flexible";
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl; std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
} }
``` ```
%% Output %% Output
String 'bonjour' is 7 characters long. String 'bonjour' is 7 characters long.
String 'coucou' is 6 characters long. String 'coucou' is 6 characters long.
String 'bonjour' is 7 characters long. String 'bonjour' is 7 characters long.
String 'dynamic std::string' is 19 characters long. String 'dynamic std::string' is 19 characters long.
String 'std::string is dynamical and flexible' is 37 characters long. String 'std::string is dynamical and flexible' is 37 characters long.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some operations; it is however out of the scope of this lecture. Please notice C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some operations; it is however out of the scope of this lecture.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Renaming types ## Renaming types
Sometimes it may be handy to rename a type, for instance if you want to be able to change easily throughout the code the numeric precision to use. Historical syntax (up to C++ 11 and still valid) was `typedef`: Sometimes it may be handy to rename a type, for instance if you want to be able to change easily throughout the code the numeric precision to use. Historical syntax (up to C++ 11 and still valid) was `typedef`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <iomanip> // For std::setprecision #include <iomanip> // For std::setprecision
{ {
typedef double real; // notice the ordering: new typename comes after its value typedef double real; // notice the ordering: new typename comes after its value
real radius {1.}; real radius {1.};
real area = 3.1415926535897932385 * radius * radius; real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl; std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
} }
``` ```
%% Output %% Output
Area = 3.14159265358979 Area = 3.14159265358979
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In more modern C++ (C++11 and above), another syntax relying on `using` keyword was introduced; it is advised to use it as this syntax is more powerful in some contexts (see later with templates...): In more modern C++ (C++11 and above), another syntax relying on `using` keyword was introduced; it is advised to use it as this syntax is more powerful in some contexts (see later with templates...):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <iomanip> // For std::setprecision #include <iomanip> // For std::setprecision
{ {
using real = float; // notice the ordering: more in line with was we're accustomed to when using real = float; // notice the ordering: more in line with was we're accustomed to when
// initialising variables. // initialising variables.
real radius {1.}; real radius {1.};
real area = 3.1415926535897932385 * radius * radius; real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl; std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
} }
``` ```
%% Output %% Output
Area = 3.14159274101257 Area = 3.14159274101257
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `decltype` and `auto` ## `decltype` and `auto`
C++ 11 introduced new keywords that are very handy to deal with types: C++ 11 introduced new keywords that are very handy to deal with types:
* `decltype` which is able to determine at compile time the underlying type of a variable. * `decltype` which is able to determine at compile time the underlying type of a variable.
* `auto` which determines automatically the type of an expression. * `auto` which determines automatically the type of an expression.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
auto i = 5; // i is here an int. auto i = 5; // i is here an int.
auto j = 5u; // j is an unsigned int auto j = 5u; // j is an unsigned int
decltype(j) k; // decltype(j) is interpreted by the compiler as an unsigned int. decltype(j) k; // decltype(j) is interpreted by the compiler as an unsigned int.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On such trivial examples it might not seem much, but in practice it might prove incredibly useful. Consider for instance the following C++03 code (the details don't matter: we'll deal with `std::vector` in a [later notebook](/notebooks/5-UsefulConceptsAndSTL/3-Containers.ipynb)): On such trivial examples it might not seem much, but in practice it might prove incredibly useful. Consider for instance the following C++03 code (the details don't matter: we'll deal with `std::vector` in a [later notebook](/notebooks/5-UsefulConceptsAndSTL/3-Containers.ipynb)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation... std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation...
// In C++ 03 you would have to push_back one by one each of the element! // In C++ 03 you would have to push_back one by one each of the element!
for (std::vector<unsigned int>::const_iterator it = primes.cbegin(); for (std::vector<unsigned int>::const_iterator it = primes.cbegin();
it != primes.cend(); it != primes.cend();
++it) ++it)
{ {
std::cout << *it << " is prime." << std::endl; std::cout << *it << " is prime." << std::endl;
} }
} }
``` ```
%% Output %% Output
2 is prime. 2 is prime.
3 is prime. 3 is prime.
5 is prime. 5 is prime.
7 is prime. 7 is prime.
11 is prime. 11 is prime.
13 is prime. 13 is prime.
17 is prime. 17 is prime.
19 is prime. 19 is prime.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It's very verbosy; we could of course use alias: It's very verbosy; we could of course use alias:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation... std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation...
using iterator = std::vector<unsigned int>::const_iterator; using iterator = std::vector<unsigned int>::const_iterator;
for (iterator it = primes.cbegin(); for (iterator it = primes.cbegin();
it != primes.cend(); it != primes.cend();
++it) ++it)
{ {
std::cout << *it << " is prime." << std::endl; std::cout << *it << " is prime." << std::endl;
} }
} }
``` ```
%% Output %% Output
2 is prime. 2 is prime.
3 is prime. 3 is prime.
5 is prime. 5 is prime.
7 is prime. 7 is prime.
11 is prime. 11 is prime.
13 is prime. 13 is prime.
17 is prime. 17 is prime.
19 is prime. 19 is prime.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But with `decltype` we may write instead: But with `decltype` we may write instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation... std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation...
for (decltype(primes.cbegin()) it = primes.cbegin(); for (decltype(primes.cbegin()) it = primes.cbegin();
it != primes.cend(); it != primes.cend();
++it) ++it)
{ {
std::cout << *it << " is prime." << std::endl; std::cout << *it << " is prime." << std::endl;
} }
} }
``` ```
%% Output %% Output
2 is prime. 2 is prime.
3 is prime. 3 is prime.
5 is prime. 5 is prime.
7 is prime. 7 is prime.
11 is prime. 11 is prime.
13 is prime. 13 is prime.
17 is prime. 17 is prime.
19 is prime. 19 is prime.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
or even better: or even better:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation... std::vector<unsigned int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; // I'm cheating: it's C++ 11 notation...
for (auto it = primes.cbegin(); for (auto it = primes.cbegin();
it != primes.cend(); it != primes.cend();
++it) ++it)
{ {
std::cout << *it << " is prime." << std::endl; std::cout << *it << " is prime." << std::endl;
} }
} }
``` ```
%% Output %% Output
2 is prime. 2 is prime.
3 is prime. 3 is prime.
5 is prime. 5 is prime.
7 is prime. 7 is prime.
11 is prime. 11 is prime.
13 is prime. 13 is prime.
17 is prime. 17 is prime.
19 is prime. 19 is prime.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
That is not to say `decltype` is always inferior to `auto`: there are some cases in which decltype is invaluable (especially in metaprogramming - but it's out of the scope of this lecture!). That is not to say `decltype` is always inferior to `auto`: there are some cases in which decltype is invaluable (especially in metaprogramming - but it's out of the scope of this lecture!).
C++ 14 introduced a new one (poorly) called `decltype(auto)` which usefulness will be explained below: C++ 14 introduced a new one (poorly) called `decltype(auto)` which usefulness will be explained below:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
{ {
int i = 5; int i = 5;
int& j = i; int& j = i;
auto k = j; auto k = j;
if (std::is_same<decltype(j), decltype(k)>()) if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl; std::cout << "j and k are of the same type." << std::endl;
else else
std::cout << "j and k are of different type." << std::endl; std::cout << "j and k are of different type." << std::endl;
if (std::is_same<decltype(i), decltype(k)>()) if (std::is_same<decltype(i), decltype(k)>())
std::cout << "i and k are of the same type." << std::endl; std::cout << "i and k are of the same type." << std::endl;
else else
std::cout << "i and k are of different type." << std::endl; std::cout << "i and k are of different type." << std::endl;
} }
``` ```
%% Output %% Output
j and k are of different type. j and k are of different type.
i and k are of the same type. i and k are of the same type.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Despite the `auto k = j`, j and k don't share the same type! The reason for this is that `auto` loses information about pointers, reference or constness in the process... Despite the `auto k = j`, j and k don't share the same type! The reason for this is that `auto` loses information about pointers, reference or constness in the process...
A way to circumvent this is `auto& k = j`, but it's not very pretty... A way to circumvent this is `auto& k = j`, but it's not very pretty...
`decltype(auto)` was introduced to fill this hole: it retains these informations: `decltype(auto)` was introduced to fill this hole: it retains these informations:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
{ {
int i = 5; int i = 5;
int& j = i; int& j = i;
decltype(auto) k = j; decltype(auto) k = j;
if (std::is_same<decltype(j), decltype(k)>()) if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl; std::cout << "j and k are of the same type." << std::endl;
else else
std::cout << "j and k are of different type." << std::endl; std::cout << "j and k are of different type." << std::endl;
} }
``` ```
%% Output %% Output
j and k are of the same type. j and k are of the same type.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted 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 and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Curiously recurrent template pattern](/notebooks/7-Appendix/Crtp.ipynb) # [Getting started in C++](/) - [Curiously recurrent template pattern](/notebooks/7-Appendix/Crtp.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="#Attempt-without-CRTP" data-toc-modified-id="Attempt-without-CRTP-2">Attempt without CRTP</a></span><ul class="toc-item"><li><span><a href="#First-attempt:-public-inheritance" data-toc-modified-id="First-attempt:-public-inheritance-2.1">First attempt: public inheritance</a></span></li><li><span><a href="#Second-attempt:-private-inheritance" data-toc-modified-id="Second-attempt:-private-inheritance-2.2">Second attempt: private inheritance</a></span></li><li><span><a href="#Third-attempt:-composition" data-toc-modified-id="Third-attempt:-composition-2.3">Third attempt: composition</a></span></li></ul></li><li><span><a href="#CRTP" data-toc-modified-id="CRTP-3">CRTP</a></span><ul class="toc-item"><li><span><a href="#Refering-to-the-base-class" data-toc-modified-id="Refering-to-the-base-class-3.1">Refering to the base class</a></span></li><li><span><a href="#Never-call-a-CRTP-method-in-base-constructor!" data-toc-modified-id="Never-call-a-CRTP-method-in-base-constructor!-3.2">Never call a CRTP method in base constructor!</a></span></li></ul></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="#Attempt-without-CRTP" data-toc-modified-id="Attempt-without-CRTP-2">Attempt without CRTP</a></span><ul class="toc-item"><li><span><a href="#First-attempt:-public-inheritance" data-toc-modified-id="First-attempt:-public-inheritance-2.1">First attempt: public inheritance</a></span></li><li><span><a href="#Second-attempt:-private-inheritance" data-toc-modified-id="Second-attempt:-private-inheritance-2.2">Second attempt: private inheritance</a></span></li><li><span><a href="#Third-attempt:-composition" data-toc-modified-id="Third-attempt:-composition-2.3">Third attempt: composition</a></span></li></ul></li><li><span><a href="#CRTP" data-toc-modified-id="CRTP-3">CRTP</a></span><ul class="toc-item"><li><span><a href="#Refering-to-the-base-class" data-toc-modified-id="Refering-to-the-base-class-3.1">Refering to the base class</a></span></li><li><span><a href="#Never-call-a-CRTP-method-in-base-constructor!" data-toc-modified-id="Never-call-a-CRTP-method-in-base-constructor!-3.2">Never call a CRTP method in base constructor!</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
The **curiously recurrent template pattern** (often shortened as **CRTP**) is a idiom that might put you off guard the first time you meet it: it's not exactly obvious it should be allowed to compile... The **curiously recurrent template pattern** (often shortened as **CRTP**) is a idiom that might put you off guard the first time you meet it: it's not exactly obvious it should be allowed to compile...
The syntax is: The syntax is:
```` ````
class Derived : public Base<Derived> class Derived : public Base<Derived>
```` ````
i.e. the class derives from a template specialization of a base class... which template argument is the class itself! i.e. the class derives from a template specialization of a base class... which template argument is the class itself!
The purpose of this idiom is to provide a specific behaviour to several classes that might be unrelated otherwise. The purpose of this idiom is to provide a specific behaviour to several classes that might be unrelated otherwise.
Throughout this tutorial, we will consider the functionality of giving to an object a unique identifier: each time a new object is created, we want to provide to it a unique index and the accessor to get it. We'll try first what we already have in store and see why the CRTP is very handy to get. Throughout this tutorial, we will consider the functionality of giving to an object a unique identifier: each time a new object is created, we want to provide to it a unique index and the accessor to get it. We'll try first what we already have in store and see why the CRTP is very handy to get.
Of course, some of the limitations we shall see stem from the use of a static method; however this is an example among others of cases that aren't properly supported by the basic inheritance or composition. Of course, some of the limitations we shall see stem from the use of a static method; however this is an example among others of cases that aren't properly supported by the basic inheritance or composition.
## Attempt without CRTP ## Attempt without CRTP
### First attempt: public inheritance ### First attempt: public inheritance
The first idea should be for most of you to use inheritance, probably in its public form. The first idea should be for most of you to use inheritance, probably in its public form.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
using uint = unsigned int; using uint = unsigned int;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class UniqueId class UniqueId
{ {
public: public:
UniqueId(); UniqueId();
// Allowing copy would defeat the purpose of such a class... // Allowing copy would defeat the purpose of such a class...
UniqueId(const UniqueId&) = delete; UniqueId(const UniqueId&) = delete;
UniqueId& operator=(const UniqueId&) = delete; UniqueId& operator=(const UniqueId&) = delete;
uint GetUniqueId() const; // noexcept would be better, but cling disagree... uint GetUniqueId() const; // noexcept would be better, but cling disagree...
private: private:
const uint unique_id_; const uint unique_id_;
static uint Generate(); static uint Generate();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
uint UniqueId::Generate() uint UniqueId::Generate()
{ {
static auto ret = 0u; static auto ret = 0u;
return ret++; return ret++;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
UniqueId::UniqueId() UniqueId::UniqueId()
: unique_id_(Generate()) : unique_id_(Generate())
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
uint UniqueId::GetUniqueId() const uint UniqueId::GetUniqueId() const
{ {
return unique_id_; return unique_id_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Carrot : public UniqueId struct Carrot : public UniqueId
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Cabbage : public UniqueId struct Cabbage : public UniqueId
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Carrot carrot[2]; Carrot carrot[2];
Cabbage cabbage[2]; Cabbage cabbage[2];
for (auto i = 0ul; i < 2; ++i) for (auto i = 0ul; i < 2; ++i)
{ {
std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl; std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl;
std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl; std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl;
} }
} }
``` ```
%% Output
Carrot 0
Cabbage 2
Carrot 1
Cabbage 3
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
That is not exactly what we had in mind: the idea of providing the same functionality for `Carrot` and `Cabbage` is appealing, but it would have been better not to share the same index (and we can imagine more complex CRTP for which we absolutely do not want to intertwine the derived classes). That is not exactly what we had in mind: the idea of providing the same functionality for `Carrot` and `Cabbage` is appealing, but it would have been better not to share the same index (and we can imagine more complex CRTP for which we absolutely do not want to intertwine the derived classes).
Besides that, there is another drawback: doing so enables a mischiever developer to store in a same container otherwise unrelated classes as pointers: Besides that, there is another drawback: doing so enables a mischiever developer to store in a same container otherwise unrelated classes as pointers:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
Carrot* carrot = new Carrot; Carrot* carrot = new Carrot;
Cabbage* cabbage = new Cabbage; Cabbage* cabbage = new Cabbage;
std::vector<UniqueId*> list { carrot, cabbage }; std::vector<UniqueId*> list { carrot, cabbage };
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, at some point you can't protect a developer against his/her own stupidity, but ideally it's better if you can protect it as much a possible, and closing this possibility would be nice. Of course, at some point you can't protect a developer against his/her own stupidity, but ideally it's better if you can protect it as much a possible, and closing this possibility would be nice.
In case you're wondering, the list above would not be terribly useful: if you want to use the list for something other that the unique id functionality, you would have to use **dynamic_cast** - which I do not recommend) In case you're wondering, the list above would not be terribly useful: if you want to use the list for something other that the unique id functionality, you would have to use **dynamic_cast** - which I do not recommend)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Second attempt: private inheritance ### Second attempt: private inheritance
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Carrot2 : private UniqueId struct Carrot2 : private UniqueId
{ {
using UniqueId::GetUniqueId; using UniqueId::GetUniqueId;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Cabbage2 : private UniqueId struct Cabbage2 : private UniqueId
{ {
using UniqueId::GetUniqueId; using UniqueId::GetUniqueId;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Carrot2 carrot[2]; Carrot2 carrot[2];
Cabbage2 cabbage[2]; Cabbage2 cabbage[2];
for (auto i = 0ul; i < 2; ++i) for (auto i = 0ul; i < 2; ++i)
{ {
std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl; std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl;
std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl; std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl;
} }
} }
``` ```
%% Output
Carrot 6
Cabbage 8
Carrot 7
Cabbage 9
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We haven't gained much with private inheritance, and even lost a bit: We haven't gained much with private inheritance, and even lost a bit:
* Underlying numeration is still the same for unrelated classes. * Underlying numeration is still the same for unrelated classes.
* We now additionally need to explicitly allow in derived classes the base class method with `using` statement. This may not seem much, but in a complex CRTP with several methods it adds boilerplate to provide in each derived class. * We now additionally need to explicitly allow in derived classes the base class method with `using` statement. This may not seem much, but in a complex CRTP with several methods it adds boilerplate to provide in each derived class.
* It is up to the user to proceed to private inheritance: he may use public one as easily as private one. * It is up to the user to proceed to private inheritance: he may use public one as easily as private one.
The only substantive gain is that we block the possibility to list together unrelated objects: The only substantive gain is that we block the possibility to list together unrelated objects:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
Carrot2* carrot = new Carrot2; Carrot2* carrot = new Carrot2;
Cabbage2* cabbage = new Cabbage2; Cabbage2* cabbage = new Cabbage2;
std::vector<UniqueId*> list { carrot, cabbage }; std::vector<UniqueId*> list { carrot, cabbage };
} }
``` ```
%% Output
input_line_22:6:35: error: cannot cast 'Carrot2' to its private base class 'UniqueId'
std::vector<UniqueId*> list { carrot, cabbage };
 ^
input_line_18:1:18: note: declared private here
struct Carrot2 : private UniqueId
 ^~~~~~~~~~~~~~~~
input_line_22:6:43: error: cannot cast 'Cabbage2' to its private base class 'UniqueId'
std::vector<UniqueId*> list { carrot, cabbage };
 ^
input_line_19:1:19: note: declared private here
struct Cabbage2 : private UniqueId
 ^~~~~~~~~~~~~~~~

Interpreter Error:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Third attempt: composition ### Third attempt: composition
Composition actually displays most of the same weaknesses as the private inheritance, and is also more wordy to use: Composition actually displays most of the same weaknesses as the private inheritance, and is also more wordy to use:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
class Carrot3 class Carrot3
{ {
public: public:
Carrot3(); Carrot3();
uint GetUniqueId() const; uint GetUniqueId() const;
private: private:
const std::unique_ptr<UniqueId> unique_id_ = nullptr; const std::unique_ptr<UniqueId> unique_id_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Carrot3::Carrot3() Carrot3::Carrot3()
: unique_id_(std::make_unique<UniqueId>()) : unique_id_(std::make_unique<UniqueId>())
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
uint Carrot3::GetUniqueId() const uint Carrot3::GetUniqueId() const
{ {
assert(!(!unique_id_)); assert(!(!unique_id_));
return unique_id_->GetUniqueId(); return unique_id_->GetUniqueId();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Cabbage3 class Cabbage3
{ {
public: public:
Cabbage3(); Cabbage3();
uint GetUniqueId() const; uint GetUniqueId() const;
private: private:
const std::unique_ptr<UniqueId> unique_id_ = nullptr; const std::unique_ptr<UniqueId> unique_id_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Cabbage3::Cabbage3() Cabbage3::Cabbage3()
: unique_id_(std::make_unique<UniqueId>()) : unique_id_(std::make_unique<UniqueId>())
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
uint Cabbage3::GetUniqueId() const uint Cabbage3::GetUniqueId() const
{ {
assert(!(!unique_id_)); assert(!(!unique_id_));
return unique_id_->GetUniqueId(); return unique_id_->GetUniqueId();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Carrot3 carrot[2]; Carrot3 carrot[2];
Cabbage3 cabbage[2]; Cabbage3 cabbage[2];
for (auto i = 0ul; i < 2; ++i) for (auto i = 0ul; i < 2; ++i)
{ {
std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl; std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl;
std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl; std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl;
} }
} }
``` ```
%% Output
Carrot 10
Cabbage 12
Carrot 11
Cabbage 13
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So it's mostly more of the same; it is pretty wordy but at least unrelated objects can't be put in the same container. So it's mostly more of the same; it is pretty wordy but at least unrelated objects can't be put in the same container.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## CRTP ## CRTP
Now let's write a CRTP: Now let's write a CRTP:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class DerivedT> template<class DerivedT>
class CrtpUniqueId class CrtpUniqueId
{ {
public: public:
CrtpUniqueId(); CrtpUniqueId();
// Allowing copy would defeat the purpose of such a class... // Allowing copy would defeat the purpose of such a class...
CrtpUniqueId(const CrtpUniqueId&) = delete; CrtpUniqueId(const CrtpUniqueId&) = delete;
CrtpUniqueId& operator=(const CrtpUniqueId&) = delete; CrtpUniqueId& operator=(const CrtpUniqueId&) = delete;
uint GetUniqueId() const; // noexcept would be better, but cling disagree... uint GetUniqueId() const; // noexcept would be better, but cling disagree...
private: private:
const uint unique_id_; const uint unique_id_;
static uint Generate(); static uint Generate();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class DerivedT> template<class DerivedT>
uint CrtpUniqueId<DerivedT>::Generate() uint CrtpUniqueId<DerivedT>::Generate()
{ {
static auto ret = 0u; static auto ret = 0u;
return ret++; return ret++;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class DerivedT> template<class DerivedT>
CrtpUniqueId<DerivedT>::CrtpUniqueId() CrtpUniqueId<DerivedT>::CrtpUniqueId()
: unique_id_(Generate()) : unique_id_(Generate())
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class DerivedT> template<class DerivedT>
uint CrtpUniqueId<DerivedT>::GetUniqueId() const uint CrtpUniqueId<DerivedT>::GetUniqueId() const
{ {
return unique_id_; return unique_id_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And now define the derived class with this curious syntax: And now define the derived class with this curious syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Carrot4 : public CrtpUniqueId<Carrot4> struct Carrot4 : public CrtpUniqueId<Carrot4>
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct Cabbage4 : public CrtpUniqueId<Cabbage4> struct Cabbage4 : public CrtpUniqueId<Cabbage4>
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Carrot4 carrot[2]; Carrot4 carrot[2];
Cabbage4 cabbage[2]; Cabbage4 cabbage[2];
for (auto i = 0ul; i < 2; ++i) for (auto i = 0ul; i < 2; ++i)
{ {
std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl; std::cout << "Carrot " << carrot[i].GetUniqueId() << std::endl;
std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl; std::cout << "Cabbage " << cabbage[i].GetUniqueId() << std::endl;
} }
} }
``` ```
%% Output
Carrot 0
Cabbage 0
Carrot 1
Cabbage 1
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We see with this syntax that: We see with this syntax that:
* Each class properly use its own internal numbering. * Each class properly use its own internal numbering.
* There are no relationship at all between derived classes: `CrtpUniqueId<Carrot4>` is an entirely different object than `CrtpUniqueId<Cabbage4>`. * There are no relationship at all between derived classes: `CrtpUniqueId<Carrot4>` is an entirely different object than `CrtpUniqueId<Cabbage4>`.
* There are no boilerplate in derived classes implementation: nothing to add besides the CRTP syntax. * There are no boilerplate in derived classes implementation: nothing to add besides the CRTP syntax.
Moreover, this occurs at compilation and there are no runtime cost whatsoever. Moreover, this occurs at compilation and there are no runtime cost whatsoever.
The inheritance might be public, protected or private, depending on your needs. The inheritance might be public, protected or private, depending on your needs.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Refering to the base class ### Refering to the base class
Sometimes however, you need to access something from the derived class in the CRTP, and I must admit in this case the syntax could have been more sugary... Sometimes however, you need to access something from the derived class in the CRTP, and I must admit in this case the syntax could have been more sugary...
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
template<class DerivedT> template<class DerivedT>
struct CrtpPrint struct CrtpPrint
{ {
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
template<class DerivedT> template<class DerivedT>
void CrtpPrint<DerivedT>::Print() const void CrtpPrint<DerivedT>::Print() const
{ {
std::cout << "The name of the class is " << static_cast<const DerivedT&>(*this).ClassName() << std::endl; std::cout << "The name of the class is " << static_cast<const DerivedT&>(*this).ClassName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
struct Tomato : public CrtpPrint<Tomato> struct Tomato : public CrtpPrint<Tomato>
{ {
static std::string ClassName(); static std::string ClassName();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string Tomato::ClassName() std::string Tomato::ClassName()
{ {
return std::string("Tomato"); return std::string("Tomato");
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Tomato tomato; Tomato tomato;
tomato.Print(); tomato.Print();
} }
``` ```
%% Output
The name of the class is Tomato
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As we saw, the way to access something in the base class is to use: As we saw, the way to access something in the base class is to use:
```` ````
static_cast<DerivedT&>(*this) static_cast<DerivedT&>(*this)
```` ````
In the case above, I added a `const` as we wanted to access a constant method of the base class. In the case above, I added a `const` as we wanted to access a constant method of the base class.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Never call a CRTP method in base constructor! ### Never call a CRTP method in base constructor!
CRTP is sometimes dubbed **static polymorphism** due to this functionality. This name is helpful in the sense a warning true for usual dynamic polymorphism is also true here: **never** call a derived method in the base constructor: it would lead to undefined behaviour. CRTP is sometimes dubbed **static polymorphism** due to this functionality. This name is helpful in the sense a warning true for usual dynamic polymorphism is also true here: **never** call a derived method in the base constructor: it would lead to undefined behaviour.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted 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 and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been redacted 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