Mentions légales du service

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • v19.05
  • v20.03
  • v21.05
  • v22.10
5 results

Target

Select target project
  • formations/cpp/gettingstartedwithmoderncpp
  • sbenamor/gettingstartedwithmoderncpp
  • steff/gettingstartedwithmoderncpp
  • sgilles/gettingstartedwithmoderncpp
  • vrouvrea/gettingstartedwithmoderncpp
  • fvergnet/gettingstartedwithmoderncpp
  • jediaz/gettingstartedwithmoderncpp
  • mmalanda/gettingstartedwithmoderncpp
  • bnguyenv/gettingstartedwithmoderncpp
9 results
Select Git revision
  • 112_binder
  • 112_binder_support
  • 112_mr
  • 113_jupytext
  • 113_jupytext_dot_binder
  • 113_jupytext_postbuild
  • 113_jupytext_postbuild_pyproject
  • 124_decl_def
  • 124_test_compilation_ok
  • 126_zotero
  • 129_rephrasing
  • 134_virtual_calls
  • algorithm_rereading
  • ci_nbstripout
  • cppyy
  • develop
  • hands_on
  • jupytext
  • jupytext_in_postbuild
  • jupytext_pyproject_mac
  • master
  • miscellaneous_small_fixes
  • object_rereading
  • procedural_rereading
  • rereading_operators
  • rereading_template_notebooks
  • sebastien_note_2024_day2
  • rc24.03.4
  • rc24.03.5
  • rc24.03.6
  • rc24.03.7
31 results
Show changes
Commits on Source (44)
Showing
with 414 additions and 149 deletions
...@@ -43,11 +43,6 @@ check_notebooks_empty_outputs: ...@@ -43,11 +43,6 @@ check_notebooks_empty_outputs:
- changes: - changes:
- docker/Dockerfile.${NAME} - docker/Dockerfile.${NAME}
build_xeus-cling:
extends: .build_docker_image
variables:
NAME: "xeus-cling"
build_fedora: build_fedora:
extends: .build_docker_image extends: .build_docker_image
variables: variables:
...@@ -58,6 +53,7 @@ build_fedora_with_boost: ...@@ -58,6 +53,7 @@ build_fedora_with_boost:
variables: variables:
NAME: "fedora_with_boost" NAME: "fedora_with_boost"
compile_hands_on_solutions: compile_hands_on_solutions:
image: $CI_REGISTRY/$CI_PROJECT_PATH/fedora_for_hands_on image: $CI_REGISTRY/$CI_PROJECT_PATH/fedora_for_hands_on
stage: check stage: check
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Getting started with the tutorial](./getting_started_with_tutorial.ipynb) # [Getting started in C++](./) - [Getting started with the tutorial](./getting_started_with_tutorial.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Jupyter notebook ## Jupyter notebook
### About the choice of a Jupyter notebook ### About the choice of a Jupyter notebook
We made the choice to use a Jupyter notebook for conveniency: We made the choice to use a Jupyter notebook for conveniency:
- We can delve directly into the syntax of the language itself, without muddying the water with peripherical stuff such as explaining from the beginning how a build system works. - We can delve directly into the syntax of the language itself, without muddying the water with peripherical stuff such as explaining from the beginning how a build system works.
(don't worry we'll cover real environments [in part 6](./6-InRealEnvironment/0-main.ipynb) (don't worry we'll cover real environments [in part 6](./6-InRealEnvironment/0-main.ipynb)
- Notebooks are a cool tool to meddle explanations and actual code that is directly executable. - Notebooks are a cool tool to meddle explanations and actual code that is directly executable.
However, Jupyter notebooks were clearly not initially designed with C++ in mind (Jupyter stands for **Ju**Lia **Py**thon and **R** - these three langages are either interpreted or just-in-time, whereas C++ is a compiled langage). However, Jupyter notebooks were clearly not initially designed with C++ in mind (Jupyter stands for **Ju**Lia **Py**thon and **R** - these three langages are either interpreted or just-in-time, whereas C++ is a compiled langage).
This didn't stop people from trying, leveraging the [cling](https://root.cern/cling) project from CERN which aims to provide an interpreter to the C++ langage. This didn't stop people from trying, leveraging the [cling](https://root.cern/cling) project from CERN which aims to provide an interpreter to the C++ langage.
Up to summer 2024, we were using [Xeus-cling](https://xeus-cling.readthedocs.io/en/latest) to run this tutorial; however this project was stagnating and in particular doesn't seem keen to adopt C++ 20, which is (partially) supported by the cling interpreter. Up to summer 2024, we were using [Xeus-cling](https://xeus-cling.readthedocs.io/en/latest) to run this tutorial; however this project was stagnating and in particular doesn't seem keen to adopt C++ 20, which is (partially) supported by the cling interpreter.
We have therefore switched to a [homemade Jupyter kernel](https://gitlab.inria.fr/sed-saclay/cppyy_kernel) which uses up the fantastic [cppyy](https://cppyy.readthedocs.io) project, which enables running C++ code from a Python environment. We have therefore switched to a [homemade Jupyter kernel](https://gitlab.inria.fr/sed-saclay/cppyy_kernel) which uses up the fantastic [cppyy](https://cppyy.readthedocs.io) project, which enables running C++ code from a Python environment.
That being said, it is important to keep in mind that this notebook's fancy interpreter is absolutely not a typical C++ environment. That being said, it is important to keep in mind that this notebook's fancy interpreter is absolutely not a typical C++ environment.
### When cling / the notebook is not enough... ### When cling / the notebook is not enough...
Even if notebooks are really useful, there are some C++ operations that are not fully supported, be it due to cling limitations (either intrinsic or just because some new C++ features aren't yet covered) or to the way we implemented our kernel around cppyy. Even if notebooks are really useful, there are some C++ operations that are not fully supported, be it due to cling limitations (either intrinsic or just because some new C++ features aren't yet covered) or to the way we implemented our kernel around cppyy.
We try our best to make the most content available directly, and we designed our kernel accordingly. We used so-called *magics* to do so, so don't be surprised if some cells starts with a line such as: We try our best to make the most content available directly, and we designed our kernel accordingly. We used so-called *magics* to do so, so don't be surprised if some cells starts with a line such as:
```jupyter ```jupyter
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
``` ```
Such lines are dedicated to the running of the tutorial and have nothing to do with C++ itself; you may entirely ignore it (unless of course you intend to use our cppyy kernel for your own purposes). Such lines are dedicated to the running of the tutorial and have nothing to do with C++ itself; you may entirely ignore it (unless of course you intend to use our cppyy kernel for your own purposes).
The most common such magics are: The most common such magics are:
- `%%cpptoolbox cppyy/cppdef`: cppyy in fact defines two different functions `cppexec` and `cppdef`. The former is for code deemed to be executed, the latter for code that defines classes, functions and so on. In our Jupyter kernel we use by default `cppexec`, which works just fine for most operations. However some really need to call under the hood `cppdef`, and that's the reason for this magics. - `%%cppmagics cppyy/cppdef`: cppyy in fact defines two different functions `cppexec` and `cppdef`. The former is for code deemed to be executed, the latter for code that defines classes, functions and so on. In our Jupyter kernel we use by default `cppexec`, which works just fine for most operations. However some really need to call under the hood `cppdef`, and that's the reason for this magics.
- `%%cpptoolbox clang`: this magics tells that the entire content of the cell is to be written into a file that is to be compiled by `clang++` compiler; the executable hence produced is then run directly and its output is printed in the notebook. Contrary to the usual behaviour, the content of the cell is sandboxed and self-contained. - `%%cppmagics clang`: this magics tells that the entire content of the cell is to be written into a file that is to be compiled by `clang++` compiler; the executable hence produced is then run directly and its output is printed in the notebook. Contrary to the usual behaviour, the content of the cell is sandboxed and self-contained.
The list of all magics may be displayed with following cell: The list of all magics may be displayed with following cell:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox %%cppmagics
; ;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Few guidelines about Jupyter ### Few guidelines about Jupyter
You might not be familiar with Jupyter notebooks, so here are few tips to run it smoothly (the _Help_ menu will help you find more if you need it). You might not be familiar with Jupyter notebooks, so here are few tips to run it smoothly (the _Help_ menu will help you find more if you need it).
In a Jupyter notebook the content is divided into _cells_, in our case we are using two kind of cells: In a Jupyter notebook the content is divided into _cells_, in our case we are using two kind of cells:
* Markdown cells, such as the ones into these very words are written. * Markdown cells, such as the ones into these very words are written.
* Code cells, which are running code. In these notebooks the chosen kernel is C++17, so the code is C++17 which is interpreted by cling. * Code cells, which are running code. In these notebooks the chosen kernel is C++17, so the code is C++17 which is interpreted by cling.
There are two modes: There are two modes:
* Edit mode, in which you might change the content of a cell. In this mode the left part of the cell is in green. * Edit mode, in which you might change the content of a cell. In this mode the left part of the cell is in green.
* Command mode, in which you might take actions such as changing the type of a cell, create or delete a new one, etc... * Command mode, in which you might take actions such as changing the type of a cell, create or delete a new one, etc...
To enter in edit mode, simply type on 'Enter'. To enter in edit mode, simply type on 'Enter'.
To enter in command mode, type 'Esc'. To enter in command mode, type 'Esc'.
To execute a cell, type 'Shift + Enter'. For a markdown cell it will edit nicely the content by interpreting the markdown, and for a code cell it will run the code. To execute a cell, type 'Shift + Enter'. For a markdown cell it will edit nicely the content by interpreting the markdown, and for a code cell it will run the code.
In command mode, several handy shortcuts are there; I would recommend especially: In command mode, several handy shortcuts are there; I would recommend especially:
* `a` (add a cell above) * `a` (add a cell above)
* `b` (add a cell below) * `b` (add a cell below)
* `x` (cut a cell) * `x` (cut a cell)
* `M` (change cell mode to Markdown) * `M` (change cell mode to Markdown)
The complete list is available in _Help_ > _Keyboard_ shortcut. The complete list is available in _Help_ > _Keyboard_ shortcut.
If for some reason the code in the notebook seems stuck, you might try to restart the kernel with one of the restart option in the _Kernel_ menu. If for some reason the code in the notebook seems stuck, you might try to restart the kernel with one of the restart option in the _Kernel_ menu.
#### Restarting the kernel #### Restarting the kernel
Sometimes something that should work doesn't... In this case try restarting the kernel: it might fix your issue! Sometimes something that should work doesn't... In this case try restarting the kernel: it might fix your issue!
#### Table of contents #### Table of contents
The table of content for a given notebook is available as a side panel if you go to _View_ > _Table of contents_ or if you click on the third item on the leftmost panel. The table of content for a given notebook is available as a side panel if you go to _View_ > _Table of contents_ or if you click on the third item on the leftmost panel.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Very basic C++ syntax (in notebook and in general) ## Very basic C++ syntax (in notebook and in general)
### Semicolons ### Semicolons
In C++ most instructions end by a semicolon `;`. If you forget it, the underlying compiler doesn't understand the syntax. In C++ most instructions end by a semicolon `;`. If you forget it, the underlying compiler doesn't understand the syntax.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int foo = 5 // COMPILATION ERROR! int foo = 5 // COMPILATION ERROR!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int foo = 5; // OK int foo = 5; // OK
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Spaces, end lines and tabulations act as word separators; utterly unreadable code as the one below is perfectly fine from the compiler standpoint: Spaces, end lines and tabulations act as word separators; utterly unreadable code as the one below is perfectly fine from the compiler standpoint:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
# include <string> # include <string>
{ {
int number ; number = 1 int number ; number = 1
; std::string name; ; std::string name;
name= name=
"truc" ; "truc" ;
number = 2 number = 2
; ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Input / output ### Input / output
Inputs and outputs aren't directly a part of the language itself, but are in the standard library (often abbreviated as STL for *Standard Template Library* even if some purist may yell and explain it's not 100 % the same thing...). You therefore need to __include__ a file named `iostream`; doing so will enable the use of the input / output facilities. Inputs and outputs aren't directly a part of the language itself, but are in the standard library (often abbreviated as STL for *Standard Template Library* even if some purist may yell and explain it's not 100 % the same thing...). You therefore need to __include__ a file named `iostream`; doing so will enable the use of the input / output facilities.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
std::cout << "Hello world!" << std::endl; // Should fail (unless you run a cell that includes iostream before) std::cout << "Hello world!" << std::endl; // Should fail (unless you run a cell that includes iostream before)
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << "Hello world!" << std::endl; // Should work: std::cout and std::endl are now known. std::cout << "Hello world!" << std::endl; // Should work: std::cout and std::endl are now known.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
- `std::cout` is the symbol to designate the standard output (i.e. your screen...) - `std::cout` is the symbol to designate the standard output (i.e. your screen...)
- `std::endl` is the symbol to clean-up the stream and go to next line. - `std::endl` is the symbol to clean-up the stream and go to next line.
The operator `<<` is used to indicate what you direct toward the stream; here `std::cout << "Hello world!"` tells to redirect the string toward the standard output. The operator `<<` is used to indicate what you direct toward the stream; here `std::cout << "Hello world!"` tells to redirect the string toward the standard output.
We will see that a bit more in detail in [a later chapter](./1-ProceduralProgramming/6-Streams.ipynb), but printing something is really helpful early on hence this brief introduction here. We will see that a bit more in detail in [a later chapter](./1-ProceduralProgramming/6-Streams.ipynb), but printing something is really helpful early on hence this brief introduction here.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Comments ### Comments
There are two ways to comment code in C++: There are two ways to comment code in C++:
- `//` which comments all that is after this symbol on the same line. - `//` which comments all that is after this symbol on the same line.
- `/*` ... `*/` which comments everything between the symbols. - `/*` ... `*/` which comments everything between the symbols.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int i = 0; // Everything after // is commented until the end of the line int i = 0; // Everything after // is commented until the end of the line
/* /*
commented... commented...
also commented... also commented...
*/ */
int j = 5; // no longer commented int j = 5; // no longer commented
/* /*
// This type of comment might be used inside the other style // This type of comment might be used inside the other style
*/ */
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](COPYRIGHT.md) [© Copyright](COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./1-Variables.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./1-Variables.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Ordinary variables ## Ordinary variables
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Declaration ### Declaration
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To be usable in a C++ program, a variable must be declared. This declaration shall include at least the type of To be usable in a C++ program, a variable must be declared. This declaration shall include at least the type of
the variable, followed by its name and a semicolon. the variable, followed by its name and a semicolon.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int number; // integer variable int number; // integer variable
double real; // floating-point variable double real; // floating-point variable
// C++ standard dictates nothing - don't expect 0 for all architectures even if you get one here! // C++ standard dictates nothing - don't expect 0 for all architectures even if you get one here!
std::cout << number << std::endl; std::cout << number << std::endl;
std::cout << real << std::endl; std::cout << real << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Initialisation ### Initialisation
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Although not mandatory, it is **strongly** recommended to give an Although not mandatory, it is **strongly** recommended to give an
initial value to your variables, as an expression between brackets. initial value to your variables, as an expression between brackets.
Not providing an initial value may lead to unexpected behaviour. For instance you can't make hypothesis upon the values of `number` and `real` in the cell above: you might end-up with any value... and someone else might get other values on their computer! Not providing an initial value may lead to unexpected behaviour. For instance you can't make hypothesis upon the values of `number` and `real` in the cell above: you might end-up with any value... and someone else might get other values on their computer!
If you give braces without values, a predefined and associated value If you give braces without values, a predefined and associated value
to the type is used (usually a form of 0). to the type is used (usually a form of 0).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int nb1 { 1 }; // integer variable set with the value 1 int nb1 { 1 }; // integer variable set with the value 1
int nb2 {}; // same as int nb2{0}; int nb2 {}; // same as int nb2{0};
double pi { 3.14 }; // real variable double pi { 3.14 }; // real variable
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ actually supports many other historical forms C++ actually supports many other historical forms
of initialization, which you will encounter everywhere, including in this tutorial, of initialization, which you will encounter everywhere, including in this tutorial,
with brackets and/or an equal sign. There are some subtle differences with brackets and/or an equal sign. There are some subtle differences
between each other... that you can ignore most of the time (still, if you're beginning our advice is to take the habit to use braces which are both less ambiguous and a bit more secure than the others syntaxes). between each other... that you can ignore most of the time (still, if you're beginning our advice is to take the habit to use braces which are both less ambiguous and a bit more secure than the others syntaxes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a = 5; int a = 5;
int b(5); int b(5);
int c { 5 }; // From C++ 11 onward; we advise you to use this syntax int c { 5 }; // From C++ 11 onward; we advise you to use this syntax
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In all cases, even if there is an equal sign, it is important to remember In all cases, even if there is an equal sign, it is important to remember
that it is an initialization, not an assignment (this will be that it is an initialization, not an assignment (this will be
important when we will define our own types). important when we will define our own types).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Assignment ### Assignment
A new value is stored in an existing variable using the assignment A new value is stored in an existing variable using the assignment
operator `=`. The name of the variable is on the left; the expression operator `=`. The name of the variable is on the left; the expression
on the right of the `=` sign is evaluated, and its result is assigned to the variable. on the right of the `=` sign is evaluated, and its result is assigned to the variable.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> // for std::cout and std::endl #include <iostream> // for std::cout and std::endl
{ {
int a {}, b {}, c {} ; // default initialization; set the values to 0 int a {}, b {}, c {} ; // default initialization; set the values to 0
std::cout << "Default initialization: a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "Default initialization: a = " << a << ", b = " << b << " and c = " << c << std::endl;
a = 4; a = 4;
b = 7; b = 7;
c = a + b; c = a + b;
std::cout << "After assignments: a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "After assignments: a = " << a << ", b = " << b << " and c = " << c << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Assignments may be chained: Assignments may be chained:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a {}, b {}, c {}; int a {}, b {}, c {};
a = b = c = 5; a = b = c = 5;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is also possible to define (slightly) more advanced operators of assignments that modify the value currently stored by a simple operation: It is also possible to define (slightly) more advanced operators of assignments that modify the value currently stored by a simple operation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a {}, b {}, c {} ; // default initialization; set the values to 0 int a {}, b {}, c {} ; // default initialization; set the values to 0
a += 4; // add 4 to the current value of 'a' a += 4; // add 4 to the current value of 'a'
std::cout << "a = " << a << std::endl; std::cout << "a = " << a << std::endl;
a *= 7; // multiply current value of 'a' by 7 a *= 7; // multiply current value of 'a' by 7
std::cout << "a = " << a << std::endl; std::cout << "a = " << a << std::endl;
a /= 9; // divide 'a' by 9 and assign the quotient to 'a' a /= 9; // divide 'a' by 9 and assign the quotient to 'a'
std::cout << "a = " << a << std::endl; std::cout << "a = " << a << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Scope and blocks ### Scope and blocks
A variable is destroyed when it becomes **out of scope**. A variable is destroyed when it becomes **out of scope**.
The **scope** of a variable is its lifetime: it begins when the variable is declared and ends when it is destroyed, usually when we reach the end of the block where it is defined. The **scope** of a variable is its lifetime: it begins when the variable is declared and ends when it is destroyed, usually when we reach the end of the block where it is defined.
A **block** is essentially what is between braces `{}`. A **block** is essentially what is between braces `{}`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a = 5; int a = 5;
{ {
std::cout << "a is available at this level: " << a << std::endl; std::cout << "a is available at this level: " << a << std::endl;
{ {
int b = 10; int b = 10;
std::cout << "b is available at this level: " << b << std::endl; std::cout << "b is available at this level: " << b << std::endl;
std::cout << "and a is also still available: " << a << std::endl; std::cout << "and a is also still available: " << a << std::endl;
} // b becomes out of scope } // b becomes out of scope
std::cout << "a is available at this level: " << a << "... but b is not!" << std::endl; std::cout << "a is available at this level: " << a << "... but b is not!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Within a same block, a variable name may be only used once: Within a same block, a variable name may be only used once:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a = 5; int a = 5;
std::cout << "Value = " << a << std::endl; std::cout << "Value = " << a << std::endl;
int a = 1; // COMPILATION ERROR int a = 1; // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Beware:** it is however entirely possible to reuse in an inner block a variable name... but it is to be avoided because it really clutters the understanding of the code for a reader! **Beware:** it is however entirely possible to reuse in an inner block a variable name... but it is to be avoided because it really clutters the understanding of the code for a reader!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a = 5; int a = 5;
{ {
int a = 10; int a = 10;
std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl; std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl;
{ {
std::cout << "Of course same here: " << a << std::endl; std::cout << "Of course same here: " << a << std::endl;
} }
a = a + 5; // this is the inner most 'a' that is modified a = a + 5; // this is the inner most 'a' that is modified
} // inner most a becomes out if scope } // inner most a becomes out if scope
std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' " std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' "
"is the initial one: " << a << std::endl; "is the initial one: " << a << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The special compiler used in our kernel environment does not warn about the repetition of the variable name, but a real-life compiler worth its salt would: The special compiler used in our kernel environment does not warn about the repetition of the variable name, but a real-life compiler worth its salt would:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// < We're asking here to the system clang compiler to compile and execute the code. // < We're asking here to the system clang compiler to compile and execute the code.
// Don't bother for the time being for the supplementary lines: we'll cover them in subsequent notebooks. // Don't bother for the time being for the supplementary lines: we'll cover them in subsequent notebooks.
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int a = 5; int a = 5;
{ {
int a = 10; int a = 10;
std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl; std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl;
{ {
std::cout << "Of course same here: " << a << std::endl; std::cout << "Of course same here: " << a << std::endl;
} }
a = a + 5; // this is the inner most 'a' that is modified a = a + 5; // this is the inner most 'a' that is modified
} // inner most a becomes out if scope } // inner most a becomes out if scope
std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' " std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' "
"is the initial one: " << a << std::endl; "is the initial one: " << a << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As seen above, you can declare a block very easily by firing up opening and closing braces. This is an incredibly useful feature: you may thus **fine tune the lifetime of your variable** to ensure a variable is not used past its prime. As seen above, you can declare a block very easily by firing up opening and closing braces. This is an incredibly useful feature: you may thus **fine tune the lifetime of your variable** to ensure a variable is not used past its prime.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Increment and decrement operators ### Increment and decrement operators
Finally, C++ also provides a shortcut when value is either incremented or decremented by adding `++` or `--` before or after the name of the variable. Finally, C++ also provides a shortcut when value is either incremented or decremented by adding `++` or `--` before or after the name of the variable.
* If the sign is placed before the variable, it is a **pre-increment**. * If the sign is placed before the variable, it is a **pre-increment**.
* If the sign is placed after the variable, it is a **post-increment**. * If the sign is placed after the variable, it is a **post-increment**.
An example is the better way to explain the difference between both: An example is the better way to explain the difference between both:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a = 5; int a = 5;
int b = 3; int b = 3;
a++; // increment a by 1. a++; // increment a by 1.
++a; // same, both are actually equivalents here. ++a; // same, both are actually equivalents here.
int c = a + b; int c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
c = a-- + b; // first a + b is evaluated, and only then a is decremented. c = a-- + b; // first a + b is evaluated, and only then a is decremented.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
c = a + ++b; // first b is incremented, and only then a + b is evaluated. c = a + ++b; // first b is incremented, and only then a + b is evaluated.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Honestly it's usually better to remove any ambiguity by separating explicitly both operations: Honestly it's usually better to remove any ambiguity by separating explicitly both operations:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a = 7; int a = 7;
int b = 3; int b = 3;
int c = a + b; int c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
c = a + b; c = a + b;
--a; // equivalent to a-- but for reasons related to the standard library I advise you --a; // equivalent to a-- but for reasons related to the standard library I advise you
// to rather use the pre-increment form when both are equivalent. // to rather use the pre-increment form when both are equivalent.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
++b; // same: equivalent to b++; ++b; // same: equivalent to b++;
c = a + b; c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Comparing values ### Comparing values
As shown above, `=` is the assignment operator. To compare two values, the symbol to use is `==`. As shown above, `=` is the assignment operator. To compare two values, the symbol to use is `==`.
Other comparison operators are: Other comparison operators are:
| Operator | Effect | | Operator | Effect |
|:------------- |:-------------------------------------------:| |:------------- |:-------------------------------------------:|
| `a == b` | `true` if a and b are equals | | `a == b` | `true` if a and b are equals |
| `a != b` | `true` if a and b are different | | `a != b` | `true` if a and b are different |
| `a < b` | `true` if a is less than b | | `a < b` | `true` if a is less than b |
| `a > b` | `true` if a is greater than b | | `a > b` | `true` if a is greater than b |
| `a >= b` | `true` if a is greater than or equal to b | | `a >= b` | `true` if a is greater than or equal to b |
| `a <= b` | `true` if a is less than or equal to b | | `a <= b` | `true` if a is less than or equal to b |
These operators are defined for most ordinary types and may be defined for your own types (we'll see that [later](../3-Operators/2-Comparison.ipynb)). These operators are defined for most ordinary types and may be defined for your own types (we'll see that [later](../3-Operators/2-Comparison.ipynb)).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## References ## References
A reference is a variable that acts as a kind of alias, and provides another name for the same variable. A reference is a variable that acts as a kind of alias, and provides another name for the same variable.
When defining a reference, it must be **immediately** initialized by When defining a reference, it must be **immediately** initialized by
indicating to which variable it should point; it cannot be changed after that. indicating to which variable it should point; it cannot be changed after that.
The syntax is to add a `&` character just after the type: The syntax is to add a `&` character just after the type:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a { 2 }; int a { 2 };
int b { a }; int b { a };
int& c { a }; // c is a reference to a int& c { a }; // c is a reference to a
std::cout << "Initial values : a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "Initial values : a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
a = -7; a = -7;
std::cout << "Modify a : a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "Modify a : a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
b = 42; b = 42;
std::cout << "Modify b : a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "Modify b : a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
c = 0; c = 0;
std::cout << "Modify c : a = " << a << ", b = " << b << " and c = " << c << std::endl; std::cout << "Modify c : a = " << a << ", b = " << b << " and c = " << c << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Reference is a purely C++ concept that doesn't exist in C. Reference is a purely C++ concept that doesn't exist in C.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Pointers ## Pointers
A pointer contains the address in memory of another variable. It declares itself by slipping A pointer contains the address in memory of another variable. It declares itself by slipping
a `*` character before the name. It can be initialized or not with the address a `*` character before the name. It can be initialized or not with the address
of another variable. To explicitly extract the address of this other variable, of another variable. To explicitly extract the address of this other variable,
we use the symbol `&`. we use the symbol `&`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a { 2 }; int a { 2 };
int* p {&a}; // define a pointer p which is initialized with the address of a int* p {&a}; // define a pointer p which is initialized with the address of a
std::cout << "a = " << a << std::endl; std::cout << "a = " << a << std::endl;
std::cout << "p = " << p << std::endl; std::cout << "p = " << p << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may ask the underlying value at a given address with `*` (I reckon: this syntax may be _very_ confusing at first...) You may ask the underlying value at a given address with `*` (I reckon: this syntax may be _very_ confusing at first...)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::cout << "Value stored at p = " << *p << std::endl; std::cout << "Value stored at p = " << *p << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `*` syntax may be used to modify the underlying value: The `*` syntax may be used to modify the underlying value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
*p = *p + 5; *p = *p + 5;
std::cout << "After the operation: pointer " << p << " stores the value " << *p << std::endl; std::cout << "After the operation: pointer " << p << " stores the value " << *p << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Pointers may be used as variables by themselves and see their actual content changed during execution of a program: Pointers may be used as variables by themselves and see their actual content changed during execution of a program:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int b { 3 }; int b { 3 };
p = &b; p = &b;
std::cout << "After the pointer assignation: pointer " << p << " stores the value " << *p << std::endl; std::cout << "After the pointer assignation: pointer " << p << " stores the value " << *p << std::endl;
std::cout << "(and value of a remains unchanged: " << a << ')' << std::endl; std::cout << "(and value of a remains unchanged: " << a << ')' << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We can therefore see the pointer as a kind of redefinable reference (pointers are a feature from C language whereas references are a C++ specific feature). We can therefore see the pointer as a kind of redefinable reference (pointers are a feature from C language whereas references are a C++ specific feature).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Cheatsheet: pointers and reference syntax ### Cheatsheet: pointers and reference syntax
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
| Applied to: | A type `T` | A variable `x` | Applied to: | A type `T` | A variable `x`
|:------------- |:-------------------------------------------:|:-------------------------------------------:| |:------------- |:-------------------------------------------:|:-------------------------------------------:|
| * | Pointer to an object of type `T` | Variable under `x` if `x` is a pointer, invalid code otherwise | | * | Pointer to an object of type `T` | Variable under `x` if `x` is a pointer, invalid code otherwise |
| & | Reference to an object of type `T` | Address of the variable (i.e. a pointer) | | & | Reference to an object of type `T` | Address of the variable (i.e. a pointer) |
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Chaining pointers ### Chaining pointers
It is possible to chain pointers: It is possible to chain pointers:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int n { 5 }; int n { 5 };
int* p { &n }; int* p { &n };
int** q { &p }; int** q { &p };
std::cout << "*q is a pointer to an int (i.e. an address): " << *q << " - it is the same as what is stored in p: " << p << std::endl; std::cout << "*q is a pointer to an int (i.e. an address): " << *q << " - it is the same as what is stored in p: " << p << std::endl;
std::cout << "**q gives the original `n` value: " << **q << std::endl; std::cout << "**q gives the original `n` value: " << **q << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This was something that was very common in C, but that I do not recommend in C++ as there are much better ways to achieve the same goals (that we do not have seen yet, so don't panic if you do not understand why we would need this). This was something that was very common in C, but that I do not recommend in C++ as there are much better ways to achieve the same goals (that we do not have seen yet, so don't panic if you do not understand why we would need this).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `nullptr` ### `nullptr`
A pointer can also designate no variables if initialized with the special value A pointer can also designate no variables if initialized with the special value
`nullptr`. It is then a mistake to try to access its pointed value (as we shall see later, an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) is a good idea to ensure we do not try to dereference a `nullptr` value). `nullptr`. It is then a mistake to try to access its pointed value (as we shall see later, an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) is a good idea to ensure we do not try to dereference a `nullptr` value).
It is strongly recommended to initialize a pointer when creating it: if you define an uninitialized pointer, it points to an arbitrary area of memory, which can create undefined behaviors that are not necessarily reproducible. It is strongly recommended to initialize a pointer when creating it: if you define an uninitialized pointer, it points to an arbitrary area of memory, which can create undefined behaviors that are not necessarily reproducible.
If the pointed area is known at initialization and never changes throughout the program, you should consider a reference rather than a pointer. If the pointed area is known at initialization and never changes throughout the program, you should consider a reference rather than a pointer.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int* p { nullptr }; // define a null pointer p int* p { nullptr }; // define a null pointer p
std::cout << "\t p: address = " << p << ", value = " << *p << std::endl; // Dereferencing p is misguided! std::cout << "\t p: address = " << p << ", value = " << *p << std::endl; // Dereferencing p is misguided!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`nullptr` was introduced in C++ 11; if you're working with a legacy codebase that doesn't use this standard use `NULL` (but stick to `nullptr` for modern code!) `nullptr` was introduced in C++ 11; if you're working with a legacy codebase that doesn't use this standard use `NULL` (but stick to `nullptr` for modern code!)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Constant variables and pointers ## Constant variables and pointers
When declaring a variable of a predefined type, it is possible to specify its value can't be changed afterward by using the word `const` which may be placed before or after the type: When declaring a variable of a predefined type, it is possible to specify its value can't be changed afterward by using the word `const` which may be placed before or after the type:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const double pi { 3.1415927 }; const double pi { 3.1415927 };
double const pi_2 { 3.1415927 }; // equally valid; it is just a matter of taste. Mine is to put it before, double const pi_2 { 3.1415927 }; // equally valid; it is just a matter of taste. Mine is to put it before,
// so that is what you will see in the remaining of the lecture. // so that is what you will see in the remaining of the lecture.
// There are however very persuasive arguments for the other convention: // There are however very persuasive arguments for the other convention:
// see http://slashslash.info/eastconst/ // see http://slashslash.info/eastconst/
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
pi = 5.; // COMPILATION ERROR! pi = 5.; // COMPILATION ERROR!
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
pi_2 = 7.; // COMPILATION ERROR! pi_2 = 7.; // COMPILATION ERROR!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In the case of a pointer, we can declare the pointer itself constant, and/or the value pointed to depending on whether we place the keyword `const` before or after the type name (in this case it applies to the pointed value) or after the character `*` (in this case it applies to the pointer itself): In the case of a pointer, we can declare the pointer itself constant, and/or the value pointed to depending on whether we place the keyword `const` before or after the type name (in this case it applies to the pointed value) or after the character `*` (in this case it applies to the pointer itself):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a { 2 }, b { 3 }; int a { 2 }, b { 3 };
int* p { &a }; // Both pointer and pointed values are modifiable. int* p { &a }; // Both pointer and pointed values are modifiable.
p = &b; // OK p = &b; // OK
*p = 5; // OK *p = 5; // OK
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int a { 2 }, b { 3 }; int a { 2 }, b { 3 };
int* const p { &a }; // Value is modifiable, but not the address pointed to. int* const p { &a }; // Value is modifiable, but not the address pointed to.
*p = 5; // OK *p = 5; // OK
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
p = &b; // COMPILATION ERROR p = &b; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const int* p { &a }; // Address pointed to is modifiable, but not the underlying value. const int* p { &a }; // Address pointed to is modifiable, but not the underlying value.
p = &b; // OK p = &b; // OK
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
*p = 5; // COMPILATION ERROR *p = 5; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int a { 2 }, b { 3 }; int a { 2 }, b { 3 };
const int* const p { &a }; // Nothing is modifiable const int* const p { &a }; // Nothing is modifiable
p = &b; // COMPILATION ERROR p = &b; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
*p = 5; // COMPILATION ERROR *p = 5; // COMPILATION ERROR
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**IMPORTANT**: Even if declared `const`, the pointed value is **IMPORTANT**: Even if declared `const`, the pointed value is
not intrinsically constant. It just can't be not intrinsically constant. It just can't be
modified through this precise pointer. modified through this precise pointer.
If other variables reference the same memory area and If other variables reference the same memory area and
are not constant, they are able to modify the value: are not constant, they are able to modify the value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a { 2 }, b { 3 }; int a { 2 }, b { 3 };
const int* p { &a }; // Address pointed to is modifiable, but not the underlying value. const int* p { &a }; // Address pointed to is modifiable, but not the underlying value.
std::cout << "Value pointed by pointer p (which doesn't allow value modification) is: " << *p << std::endl; std::cout << "Value pointed by pointer p (which doesn't allow value modification) is: " << *p << std::endl;
int* p2 {&a}; // Pointer to the same memory area, but no constness here. int* p2 {&a}; // Pointer to the same memory area, but no constness here.
*p2 = 10; *p2 = 10;
std::cout << "Value pointed by pointer p (and modified through p2) is: " << *p << std::endl; std::cout << "Value pointed by pointer p (and modified through p2) is: " << *p << std::endl;
a = -3; a = -3;
std::cout << "Value pointed by pointer p (and modified through variable directly) is: " << *p << std::endl; std::cout << "Value pointed by pointer p (and modified through variable directly) is: " << *p << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On the other hand, pointers can't be used as a work-around to modify a constant value: On the other hand, pointers can't be used as a work-around to modify a constant value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const int n { 3 }; const int n { 3 };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int* p { &n }; // COMPILATION ERROR int* p { &n }; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const int* p_n2 { &n }; // OK const int* p_n2 { &n }; // OK
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Arrays ## Arrays
The operator `[]` enables the creation of an array. The operator `[]` enables the creation of an array.
**Beware:** In C++, the indexes of an array start at 0. **Beware:** In C++, the indexes of an array start at 0.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int i[10] ; // Array of 10 integers - not initialised properly! int i[10] ; // Array of 10 integers - not initialised properly!
double x[3] = { 1., 2., 3. }; // Array of 3 reals, C++ 11 syntax double x[3] = { 1., 2., 3. }; // Array of 3 reals, C++ 11 syntax
std::cout << "i[2] = " << i[2] << " (may be gibberish: undefined behaviour due to lack of initialization!)" << std::endl; std::cout << "i[2] = " << i[2] << " (may be gibberish: undefined behaviour due to lack of initialization!)" << std::endl;
std::cout << "i[10] = " << i[10] << " (undefined behaviour: out of range. Warning identifies the issue)" << std::endl ; std::cout << "i[10] = " << i[10] << " (undefined behaviour: out of range. Warning identifies the issue)" << std::endl ;
std::cout << "x[1] = " << x[1] << " (expected: 2.)" << std::endl ; std::cout << "x[1] = " << x[1] << " (expected: 2.)" << std::endl ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Multi-dimensional arrays are also possible: Multi-dimensional arrays are also possible:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int k[2][3] = { { 5, 7, 0 }, { 3, 8, 9 } }; int k[2][3] = { { 5, 7, 0 }, { 3, 8, 9 } };
std::cout << "k[0][0] = " << k[0][0] << " (expected: 5)" << std::endl; std::cout << "k[0][0] = " << k[0][0] << " (expected: 5)" << std::endl;
std::cout << "k[1][2] = " << k[1][2] << " (expected: 9)" << std::endl; std::cout << "k[1][2] = " << k[1][2] << " (expected: 9)" << std::endl;
int l[2][3] = {}; // default initialization of all elements of the array. int l[2][3] = {}; // default initialization of all elements of the array.
std::cout << "l[0][0] = " << l[0][0] << " (expected: 0)" << std::endl; std::cout << "l[0][0] = " << l[0][0] << " (expected: 0)" << std::endl;
std::cout << "l[1][2] = " << l[1][2] << " (expected: 0)" << std::endl; std::cout << "l[1][2] = " << l[1][2] << " (expected: 0)" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**IMPORTANT**: so far we have considered only the case of _static_ arrays, for which the size is already known at compilation. We will deal with dynamic ones [later in this tutorial](./5-DynamicAllocation.ipynb#Arrays-on-heap) (and also with the standard libraries [alternatives](../5-UsefulConceptsAndSTL/3-Containers.ipynb) such as `std::vector` or `std::array` which are actually much more compelling). **IMPORTANT**: so far we have considered only the case of _static_ arrays, for which the size is already known at compilation. We will deal with dynamic ones [later in this tutorial](./5-DynamicAllocation.ipynb#Arrays-on-heap) (and also with the standard libraries [alternatives](../5-UsefulConceptsAndSTL/3-Containers.ipynb) such as `std::vector` or `std::array` which are actually much more compelling).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Arrays and pointers ### Arrays and pointers
The variable designating an array is similar to a constant pointer pointing to the beginning of the array. Increasing this pointer is like moving around in the array. The variable designating an array is similar to a constant pointer pointing to the beginning of the array. Increasing this pointer is like moving around in the array.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int i[2] = { 10, 20 } ; int i[2] = { 10, 20 } ;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::cout << *i << " (expected: 10)" << std::endl; std::cout << *i << " (expected: 10)" << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::cout << *i + 1 << " (expected: 11)" << std::endl; std::cout << *i + 1 << " (expected: 11)" << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::cout << *(i + 1) << " (expected: 20)" << std::endl; std::cout << *(i + 1) << " (expected: 20)" << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int* j = i; // OK! int* j = i; // OK!
++j; // OK! ++j; // OK!
std::cout << "Value pointed by j is " << *j << " (expected: 20)" << std::endl; std::cout << "Value pointed by j is " << *j << " (expected: 20)" << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./3-Types.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./3-Types.ipynb)
%% 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++ ``` c++
#include <iostream> #include <iostream>
bool undefined; // UNDEFINED !! bool undefined; // UNDEFINED !!
if (undefined) if (undefined)
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;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool defined { true }; bool defined { true };
if (defined) if (defined)
std::cout << "Defined!" << std::endl; std::cout << "Defined!" << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int n = 0; int 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;
``` ```
%% 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++ ``` c++
#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++ ``` c++
#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 (huge) shortcoming of historical `enum ` is that the same word can't be used in two different `enum`: A (huge) 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++ ``` c++
{ {
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++ ``` c++
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++ ``` c++
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++ ``` c++
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++ ``` c++
int a = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer int a = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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++ ``` c++
{ {
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: These enum types are really handy to make code more expressive, especially in function calls:
```c++ ```c++
f(print::yes, perform_checks::no); f(print::yes, perform_checks::no);
``` ```
is much more expressive (and less error-prone) than: is much more expressive (and less error-prone) than:
```c++ ```c++
f(true, false); f(true, false);
``` ```
for which you will probably need to go check the prototype to figure out what each argument stands for. for which you will probably need to go check the prototype to figure out what each argument stands for.
As we shall see [shortly](#Explicit-conversions-by-static_cast), you may perform arithmetic with the underlying integer through _explicit cast_ of the enum into an integer. As we shall see [shortly](#Explicit-conversions-by-static_cast), you may perform arithmetic with the underlying integer through _explicit cast_ of the enum into an integer.
%% Cell type:markdown id: tags: %% 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.
The STL features rather heavily a type named `std::size_t`, which by design is able to store the maximum size of a theoretically possible object of any type (including array). On most (all?) systems `std::size_t` is an alias to an `unsigned long`. More may be found about this type on [CppReference](https://en.cppreference.com/w/cpp/types/size_t). The equivalent counterpart for *signed* integers is the [`std::ptrdiff_t`](https://en.cppreference.com/w/cpp/types/ptrdiff_t), which is the signed integer type of the result of subtracting two pointers. The STL features rather heavily a type named `std::size_t`, which by design is able to store the maximum size of a theoretically possible object of any type (including array). On most (all?) systems `std::size_t` is an alias to an `unsigned long`. More may be found about this type on [CppReference](https://en.cppreference.com/w/cpp/types/size_t). The equivalent counterpart for *signed* integers is the [`std::ptrdiff_t`](https://en.cppreference.com/w/cpp/types/ptrdiff_t), which is the signed integer type of the result of subtracting two pointers.
#### 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++ ``` c++
#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:
(see the [CppReference dedicated page](https://en.cppreference.com/w/cpp/types/numeric_limits) for more details about `std::numeric_limits`). (see the [CppReference dedicated page](https://en.cppreference.com/w/cpp/types/numeric_limits) for more details about `std::numeric_limits`).
##### Integral types ##### Integral types
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++ ``` c++
#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++ ``` c++
#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 (but not its numeric modules such as _numpy_ which are using C or C++ under the hood) gets a underlying integer model that is resilient to this kind of issue but there is a performance cost behind it; types such as those used in C++ are tailored to favor optimization on your hardware. Other languages such as Python (but not its numeric modules such as _numpy_ which are using C or C++ under the hood) gets a underlying integer model that is resilient to this kind of issue but there is a performance cost behind it; 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:
##### Floating-point types ##### Floating-point types
C++ provides special values to represent infinite or not-a-number values for floating-point types (the facilities below appeared after C++ 11; there were others prior to that inherited directly from C). C++ provides special values to represent infinite or not-a-number values for floating-point types (the facilities below appeared after C++ 11; there were others prior to that inherited directly from C).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
float max_float {std::numeric_limits<float>::max()}; float max_float {std::numeric_limits<float>::max()};
max_float += 1.e+32; // Add something significant enough to max value max_float += 1.e+32; // Add something significant enough to max value
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl; std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
max_float -= 1.e+32; max_float -= 1.e+32;
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl; std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
double nan = 0. / 0.; double nan = 0. / 0.;
std::cout << "Is " << nan << " infinite ? " << std::isinf(nan) << std::endl; std::cout << "Is " << nan << " infinite ? " << std::isinf(nan) << std::endl;
std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl; std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl;
nan = nan + 1.e5; nan = nan + 1.e5;
std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl; std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl;
std::cout << "The unnatural property of nan is that the expression 'nan == nan' is " << std::boolalpha << (nan == nan) << "!" << std::endl; std::cout << "The unnatural property of nan is that the expression 'nan == nan' is " << std::boolalpha << (nan == nan) << "!" << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are subtleties about NaN (see [Cppreference](https://en.cppreference.com/w/cpp/numeric/math/nan)) but in most cases you don't need to bother much with either `inf` or `nan`, except if you have reasons to think your computation may produce either of them. In that case, you may want to check your value is correct with `std::isfinite` ([Cppreference](https://en.cppreference.com/w/cpp/numeric/math/isfinite)). There are subtleties about NaN (see [Cppreference](https://en.cppreference.com/w/cpp/numeric/math/nan)) but in most cases you don't need to bother much with either `inf` or `nan`, except if you have reasons to think your computation may produce either of them. In that case, you may want to check your value is correct with `std::isfinite` ([Cppreference](https://en.cppreference.com/w/cpp/numeric/math/isfinite)).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Conversions between digital types #### Conversions between digital types
[Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) I indicated there were small differences between the three initialization methods, that could be ignored most of the time. [Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) we indicated there were small differences between the three initialization methods, that could be ignored most of the time.
The difference is related to implicit conversion: both historical initialization methods are ok with implicit conversion __with accuracy loss__: The difference is related to implicit conversion: both historical initialization methods are ok with implicit conversion __with accuracy loss__:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
{ {
float f = 1.1234567890123; float f = 1.123456789;
double d = 2.1234567890123; double d = 2.123456789;
float f_d(d); float f_d(d); // Could also have been written: float f_d = d;
float f_dd = d;
std::cout << "A double may print around 15 significant digits, d = " << std::setprecision(16) << d << std::endl; std::cout << "A double may print around 15 significant digits, d = " << std::setprecision(16) << d << std::endl;
std::cout << "A float may print around 7 significant digits, f_d = " << std::setprecision(16) << f_d << " so we see here the value was altered from the initial double value." << std::endl; std::cout << "A float may print around 7 significant digits, f_d = " << std::setprecision(16) << f_d << " so we see here the value was altered from the initial double value." << std::endl;
std::cout << "Even without conversion involved there is an accuracy loss: f = " << std::setprecision(16) << f << std::endl; std::cout << "Even without conversion involved there is an accuracy loss if we require too high a precision: f = " << std::setprecision(16) << f << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
whereas C++ 11 introduced initialization with braces isn't: whereas C++ 11 introduced initialization with braces isn't, even if the initial number could fit in the target conversion type:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double d = 2.12345678901234567890; double d = 2.12345;
float f_d{d}; // COMPILATION ERROR float f_d{d}; // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is really related to **accuracy loss**: initialization with braces is ok if there are none: This is really related to **accuracy loss**: initialization with braces is ok only if there is no such loss:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
float f = 1.12345678901234567890; float f = 1.123456;
double d_f { f }; // OK double d_f { f }; // OK
} }
``` ```
%% 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++ ``` c++
{ {
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 can perform a different C++ conversion under the hood 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 can perform a different C++ conversion under the hood 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++ ``` c++
{ {
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](../2-ObjectProgramming/7-polymorphism.ipynb#dynamic_cast). * `dynamic_cast`, which will be introduced when we'll deal with [polymorphism](../2-ObjectProgramming/7-polymorphism.ipynb#dynamic_cast).
* `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:
### Be wary of mathematical operators and types ### Be wary of mathematical operators and types
It was already covered in the Hands-On but please bear in mind that this operation: `2/3` is an integer division, so the result will be an `int`, and might not be what you want. It was already covered in the Hands-On but please bear in mind that this operation: `2/3` is an integer division, so the result will be an `int`, and might not be what you want.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double a = 2 / 3; double a = 2 / 3;
std::cout << a << std::endl; // a == 0 std::cout << a << std::endl; // a == 0
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you really want a floating-point division, you have to cast at least one of the value: If you really want a floating-point division, you have to cast at least one of the value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double a = static_cast<double>(2) / 3; double a = static_cast<double>(2) / 3;
std::cout << a << std::endl; std::cout << a << std::endl;
// this is also valid // this is also valid
double b = 2. / 3; double b = 2. / 3;
std::cout << b << std::endl; std::cout << b << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please note that the best would be to explicitly cast both values, to avoid useless implicit conversion. Please note that the best would be to **explicitly cast both values**, to avoid useless implicit conversion.
%% Cell type:code id: tags:
``` c++
{
double a = 2. / 3.;
std::cout << a << std::endl;
}
```
%% Cell type:markdown id: tags:
## Underlying types of `enum class`
In fact, under the hood `enum class` are storing `int` values by default:
%% Cell type:code id: tags:
``` c++
#include <iostream>
enum class arbitrary_enum_class { yes, no }; // with scoped enum I can choose any value without fearing naming conflict!
// Don't bother if you're lost with `std::is_same_v<int, std::underlying_type_t<int_enum>>` - we'll cover that later in part 5.
// You just have to know here it checks whether underlying type behind enum is an `int` or not.
std::cout << "Check underlying type behind `arbitrary_enum_class` is `int`: " << std::boolalpha << std::is_same_v<int, std::underlying_type_t<arbitrary_enum_class>> << '\n';
```
%% Cell type:markdown id: tags:
However it is possible to choose another type by specifying it explicitly after a colon:
%% Cell type:code id: tags:
``` c++
enum class altitude : char // telling here explicitly to use `char` as underlying type
{
high = 'h',
low = 'l'
};
std::cout << "Check underlying type behind `altitude` is not `int`: " << std::boolalpha << std::is_same_v<int, std::underlying_type_t<altitude>> << '\n';
std::cout << "Check underlying type behind `altitude` is `char`: " << std::boolalpha << std::is_same_v<char, std::underlying_type_t<altitude>> << '\n';
```
%% 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++ ``` c++
#include <iostream> #include <iostream>
#include <cstring> // For strlen, strcpy, strncpy #include <cstring> // For strlen, strcpy, strncpy
char hello[] = {'h','e','l','l','o', '\0'}; char hello[] = {'h','e','l','l','o', '\0'};
char copy[6] = {}; // = {'\0','\0','\0','\0','\0','\0' }; char copy[6] = {}; // = {'\0','\0','\0','\0','\0','\0' };
strcpy(copy, hello); strcpy(copy, hello);
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const char* hi = "hi"; // Not putting the const here triggers a warning. const char* hi = "hi"; // Not putting the const here triggers a warning.
strncpy(copy, hi, strlen(hi)); strncpy(copy, hi, strlen(hi));
copy[strlen(hi)] = '\0'; // Don't forget to terminate the string! copy[strlen(hi)] = '\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/).
#### Drawbacks of historical strings
- Keeping track properly and not forgetting `\0` gets old really fast and the outcome is usually not user-friendly...
- Assignment doesn't work as you would expect:
%% Cell type:code id: tags:
``` c++
#include <iostream>
// Define hello, and copy it
char hello[] = {'h','e','l','l','o', '\0'};
const char* copy = hello;
// Modify hello
hello[0] = '!';
// `copy` is modified too!
std::cout << "Value of 'copy' is not the 'hello' you probably wanted: " << copy << std::endl;
```
%% 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, that provides a much simpler syntax: through the standard language library, that provides a much simpler syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <cstring> // For strlen #include <cstring> // For strlen
#include <string> // For std::string #include <string> // For std::string
const char* hello_str = "hello"; const char* hello_str = "hello";
std::string hello = hello_str; std::string hello = hello_str;
std::string hi("hi"); std::string hi("hi");
std::string copy {}; std::string copy {};
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
copy = hello; // please notice assignment is much more straightforward copy = hello; // please notice assignment is much more straightforward!
hello = "hi";
std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl; std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
const char* copy_str = copy.data(); // Returns a classic C-string (from C++11 onward) std::string dynamic {"dynamic"};
std::cout << "String '" << copy_str << "' is " << strlen(copy_str) << " characters long." << std::endl; std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` c++
const char* old_copy_str = &copy[0]; // Same before C++11...
std::cout << "String '" << old_copy_str << "' is " << strlen(old_copy_str) << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` c++ dynamic += " 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;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()` (both are interchangeable): If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
{ {
std::string cplusplus_string("C++ string!"); std::string cplusplus_string("C++ string!");
const char* c_string = cplusplus_string.c_str(); const char* c_string = cplusplus_string.c_str();
const char* c_string_2 = cplusplus_string.data(); const char* c_string_2 = cplusplus_string.data();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `const` here is important: you may access the content but should not modify it; this functionality is provided for read-only access. The `const` here is important: you may access the content but should not modify it; this functionality is provided for read-only access.
Both are interchangeable; `data()` was introduced in C++ 11 but is more generic as other containers also define it - we'll cover that in part 5.
As the principle to "convert" to C string is to return the address of the first character, you may also meet in some legacy codes:
%% Cell type:code id: tags:
``` c++
#include <string>
{
std::string cplusplus_string("C++ string!");
const char* c_string = &cplusplus_string[0];
}
```
%% Cell type:markdown id: tags:
which tells to litteraly take address of the first element.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
FYI, 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 presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now). FYI, C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some read-only operations (it is presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now).
%% 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++ ``` c++
#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;
} }
``` ```
%% 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++ ``` c++
#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;
} }
``` ```
%% 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 **at compile time** the type of an expression. * `auto` which determines automatically **at compile time** the type of an expression.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#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 versions of the code for iterating over a vector with an 'historical' `for` loop (the details don't matter: we'll deal with `std::vector` in a [later notebook](../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 versions of the code for iterating over a vector with an 'historical' `for` loop (the details don't matter: we'll deal with `std::vector` in a [later notebook](../5-UsefulConceptsAndSTL/3-Containers.ipynb)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5 }; std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 03 way of iterating over the content of a vector. // C++ 03 way of iterating over the content of a vector.
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;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It's very verbose; we could of course use an alias: It's very verbose; we could of course use an alias:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5 }; std::vector<unsigned int> primes { 2, 3, 5 };
using iterator = std::vector<unsigned int>::const_iterator; using iterator = std::vector<unsigned int>::const_iterator;
// C++ 03 way of iterating over the content of a vector - with an alias // C++ 03 way of iterating over the content of a vector - with an alias
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;
} }
} }
``` ```
%% 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++ ``` c++
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5 }; std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 decltype // C++ 11 decltype
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;
} }
} }
``` ```
%% 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++ ``` c++
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<unsigned int> primes { 2, 3, 5 }; std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 auto // C++ 11 auto
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;
} }
} }
``` ```
%% 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 mostly out of the scope of this lecture - we'll skim briefly over it in a later [notebook](../4-Templates/4-Metaprogramming.ipynb)). 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 mostly out of the scope of this lecture - we'll skim briefly over it in a later [notebook](../4-Templates/4-Metaprogramming.ipynb)).
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++ ``` c++
#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;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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;
``` ```
%% 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`. A way to circumvent this is `auto& k = j`.
`decltype(auto)` was introduced to fill this hole: contrary to `auto` it retains all these information: `decltype(auto)` was introduced to fill this hole: contrary to `auto` it retains all these information:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#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;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Fore more details about `auto`'s type deduction, you can check the C++ weekly videos at [this link](https://www.youtube.com/watch?v=tn69TCMdYbQ) and also [this one](https://www.youtube.com/watch?v=E5L66fkNlpE) regarding `decltype(auto)`'s usage. Fore more details about `auto`'s type deduction, you can check the C++ weekly videos at [this link](https://www.youtube.com/watch?v=tn69TCMdYbQ) and also [this one](https://www.youtube.com/watch?v=E5L66fkNlpE) regarding `decltype(auto)`'s usage.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `auto` and string literals ### `auto` and string literals
**Beware:** when you declare a string literals with `auto`, the type deduction makes it a `const char*`, not a `std::string`: **Beware:** when you declare a string literals with `auto`, the type deduction makes it a `const char*`, not a `std::string`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <iostream> #include <iostream>
auto hello_str = "Hello world"; // declares a char* auto hello_str = "Hello world"; // declares a char*
std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl; std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl;
std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl; std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 14 introduced a suffix to facilitate declaration of a `std::string` from a string literals... but which requires to add a specific `using namespace` first (we will see that those are in a [much later notebook](../6-InRealEnvironment/5-Namespace.ipynb)). C++ 14 introduced a suffix to facilitate declaration of a `std::string` from a string literals... but which requires to add a specific `using namespace` first (we will see that those are in a [much later notebook](../6-InRealEnvironment/5-Namespace.ipynb)).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
using namespace std::string_literals; using namespace std::string_literals;
std::string hello_string("Hello world"); // the 'classic' way to define a std::string std::string hello_string("Hello world"); // the 'classic' way to define a std::string
auto hello_str = "Hello world"s; // declares a std::string - requires first the using namespace directive auto hello_str = "Hello world"s; // declares a std::string - requires first the using namespace directive
std::cout << "Is 'hello_string' a const char*? " << std::boolalpha << std::is_same<decltype(hello_string), const char*>() << std::endl; std::cout << "Is 'hello_string' a const char*? " << std::boolalpha << std::is_same<decltype(hello_string), const char*>() << std::endl;
std::cout << "Is 'hello_string' a std::string? " << std::boolalpha << std::is_same<decltype(hello_string), std::string>() << std::endl; std::cout << "Is 'hello_string' a std::string? " << std::boolalpha << std::is_same<decltype(hello_string), std::string>() << std::endl;
std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl; std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl;
std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl; std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Not sure it it is entirely worth it (maybe when you define loads of `std::string` is a same file?) but you may see that syntax in an existing program. Not sure it it is entirely worth it (maybe when you define loads of `std::string` is a same file?) but you may see that syntax in an existing program.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function declaration and definition ## Function declaration and definition
### Function declaration ### Function declaration
The aim of a **function declaration** is just to describe its prototype: The aim of a **function declaration** is just to describe its prototype:
- The return type of the function (or `void` if the function returns nothing). - The return type of the function (or `void` if the function returns nothing).
- The number, type and ordering of the parameters (if any). Naming them specifically is entirely optional (but is useful if you choose to put your [Doxygen](../6-InRealEnvironment/6-Tools.ipynb#Doxygen) documentation along your functions declarations - see below). - The number, type and ordering of the parameters (if any). Naming them specifically is entirely optional (but is useful if you choose to put your [Doxygen](../6-InRealEnvironment/6-Tools.ipynb#Doxygen) documentation along your functions declarations - see below).
Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere. Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere.
Few examples of function declarations: Few examples of function declarations:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int ComputeMinimum(int a, int b); int ComputeMinimum(int a, int b);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void DoStuff(double); // naming the parameter is optional void DoStuff(double); // naming the parameter is optional
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int ReturnFive(); // providing a parameter is also optional... int ReturnFive(); // providing a parameter is also optional...
// Don't bother the notebook kernel warning: it IS here a function declaration! // Don't bother the notebook kernel warning: it IS here a function declaration!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Due to the use of notebooks we will not need to separate clearly function declaration from its definition, but we will revisit this [much later](../6-InRealEnvironment/2-FileStructure.ipynb) when we will see how a real project is written with different files involved. Due to the use of notebooks we will not need to separate clearly function declaration from its definition, but we will revisit this [much later](../6-InRealEnvironment/2-FileStructure.ipynb) when we will see how a real project is written with different files involved.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Function definition ### Function definition
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand. On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand.
In a function definition: In a function definition:
- No semicolon after the prototype - No semicolon after the prototype
- A block follows the prototype instead; inside this block the implementation is written. - A block follows the prototype instead; inside this block the implementation is written.
- Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation. - Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation.
- No semicolon after the closing brace.
For instance: For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void PrintDivision(int numerator, int denominator) // no semicolon here! void PrintDivision(int numerator, int denominator) // no semicolon here!
{ {
if (denominator == 0) if (denominator == 0)
std::cout << "Failure: division by zero!" << std::endl; std::cout << "Failure: division by zero!" << std::endl;
else else
{ {
int division ; int division ;
division = numerator / denominator ; division = numerator / denominator ;
std::cout << numerator << " / " << denominator << " = " << division << std::endl ; std::cout << numerator << " / " << denominator << " = " << division << std::endl ;
} }
} } // no semicolon here as well!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and when the function is invoked at some point, the implementation above is directly put in motion: and when the function is invoked at some point, the implementation above is directly put in motion:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int num = 3; int num = 3;
int denom = 2; int denom = 2;
PrintDivision(num, denom); PrintDivision(num, denom);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### A terminology note: _parameter_ and _argument_ #### A terminology note: _parameter_ and _argument_
In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**. In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**.
For the purists: For the purists:
- **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition) - **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition)
- **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell) - **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell)
I do not guarantee that I am using the right term everywhere in the code: I'm not a purist and often use one for another (if you want to remember properly a helpful mnemotechnic is that **a**rguments are **a**ctual). I do not guarantee that I am using the right term everywhere in the code: I'm not a purist and often use one for another (if you want to remember properly a helpful mnemotechnic is that **a**rguments are **a**ctual).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Functions cannot be nested, or declared within blocks #### Functions cannot be nested, or declared within blocks
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Functions cannot be nested in C++, contrary to some other langages such as Python: Functions cannot be nested in C++, contrary to some other languages such as Python:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Function1() // a function might have no arguments void Function1() // a function might have no arguments
{ {
void Subfunction() // COMPILATION ERROR! void Subfunction() // COMPILATION ERROR!
{ {
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule. To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## How to pass arguments ## How to pass arguments
### Passing arguments by value ### Passing arguments by value
In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function: In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void IncrementAndPrint(int i) void IncrementAndPrint(int value)
{ {
++i; ++value;
std::cout << "Inside the function: i = " << i << std::endl; std::cout << "Inside the function: value = " << value << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i { 5 };
IncrementAndPrint(i); IncrementAndPrint(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `i` in the block body and in the function definition are not the same: one or the other could have been named differently and the result would have been the same: #### Naming and scope
%% Cell type:markdown id: tags:
In the above example, the lonely parameter of the function `IncrementAndPrint` was named `value`, whereas at call site the variable was simply named `i`.
We could as well have used the same name in both places - there are no ambiguity whatsoever in doing so.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> void IncrementAndPrintUseI(int i)
void IncrementAndPrint2(int local_argument)
{ {
++local_argument; ++i;
std::cout << "Inside the function: local_argument = " << local_argument << std::endl; std::cout << "Inside the function: i = " << i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i { 5 };
IncrementAndPrint2(i); IncrementAndPrintUseI(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This might seem idiotic (and sure enough giving a name as simple as a one letter character is misguided in most cases!), but on
the other hand when you're considering a mathematical or physical quantity (`jacobian`, `displacement`, etc... ) you do not want
to force yourself to find another name (naming is already hard enough as it is).
The reason there are no ambiguity at all is that in each scope (remember [first notebook](../1-ProceduralProgramming/1-Variables.ipynb#Scope-and-blocks))
there is at most one `i` defined.
%% Cell type:markdown id: tags:
### Passing arguments by reference ### Passing arguments by reference
If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference: If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void IncrementAndPrintByReference(int& i) void IncrementAndPrintByReference(int& i)
{ {
++i; ++i;
std::cout << "Inside the function: i = " << i << std::endl; std::cout << "Inside the function: i = " << i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByReference(i); IncrementAndPrintByReference(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As in C++ you cannot return several values in the return type, passing by reference is a way to get in output several values (C++ 11 introduced in the standard library a workaround to get several values in return type with a so-called `std::tuple`, but the passing by reference remains the better way to do so in most cases). As in C++ you cannot return several values in the return type, passing by reference is a way to get in output several values (C++ 11 introduced in the standard library a workaround to get several values in return type with a so-called `std::tuple`, but the passing by reference remains the better way to do so in most cases).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder) int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)
{ {
if (arg2 == 0) if (arg2 == 0)
return -1; // error code. return -1; // error code.
quotient = arg1 / arg2; quotient = arg1 / arg2;
remainder = arg1 % arg2; remainder = arg1 % arg2;
return 0; // code when everything is alright. return 0; // code when everything is alright.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int quotient, remainder; int quotient, remainder;
if (ComputeDivision(5, 3, quotient, remainder) == 0) if (ComputeDivision(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (ComputeDivision(5, 0, quotient, remainder) == 0) if (ComputeDivision(5, 0, quotient, remainder) == 0)
std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
else else
std::cerr << "Can't divide by 0!" << std::endl; std::cerr << "Can't divide by 0!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### A bit of wandering: using C-like error codes ### A bit of wandering: using C-like error codes
The function above gets two outputs: the quotient and the remainder of the euclidean division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider. The function above gets two outputs: the quotient and the remainder of the euclidean division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider.
Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `ComputeDivision()` as if it was a void function is perfectly fine (and yet completely ill-advised). We will see [later](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb) the `exception` mechanism C++ recommends instead of error codes (and discuss a bit more error codes as well). Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `ComputeDivision()` as if it was a void function is perfectly fine (and yet completely ill-advised). We will see [later](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb) the `exception` mechanism C++ recommends instead of error codes (and discuss a bit more error codes as well).
Below is an example where things go awry due to the lack of check: Below is an example where things go awry due to the lack of check:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void PrintDivision(int arg1, int arg2) void PrintDivision(int arg1, int arg2)
{ {
int quotient, remainder; int quotient, remainder;
ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code. ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of " std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl; << quotient << " and a remainder of " << remainder << std::endl;
} }
PrintDivision(8, 5); PrintDivision(8, 5);
PrintDivision(8, 0); // bug! PrintDivision(8, 0); // prints garbage values...
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The developer made two important mistakes: The developer made two important mistakes:
* The return value of `ComputeDivision` is not checked, so something is printed on screen. * The return value of `ComputeDivision` is not checked, so something is printed on screen.
* This is something completely out of control: quotient and remainder do not get a default value that would help to see if something is askew. The behaviour is undefined: you have no guarantee on the values the program will print (currently I see the same values as in the previous function call, but another compiler/architecture/etc... might yield another wrong value. * This is something completely out of control: quotient and remainder do not get a default value that would help to see if something is askew. The behaviour is undefined: you have no guarantee on the values the program will print (currently I see the same values as in the previous function call, but another compiler/architecture/etc... might yield another wrong value.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### [[nodiscard]]
C++ 17 introduced a keyword named `[[nodiscard]]` that mitigates the issue mentioned above; this keyword tells the return value must be checked and not doing so is a **compilation warning**.
It doesn't prevent a developer to use improperly the `ComputeDivision` function, but at least they are warned that they're doing something wrong.
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
#include <iostream>
// Declarations
int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder);
void PrintDivision(int arg1, int arg2);
// Definitions
[[nodiscard]] int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)
{
if (arg2 == 0)
return -1; // error code.
quotient = arg1 / arg2;
remainder = arg1 % arg2;
return 0; // code when everything is alright.
}
void PrintDivision(int arg1, int arg2)
{
int quotient, remainder;
ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl;
}
int main([[maybe_unused]] int argc, char** argv)
{
PrintDivision(8, 0); // bug!
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Passing arguments by pointers ### Passing arguments by pointers
When the argument of a function is a pointer, each function call When the argument of a function is a pointer, each function call
results in the creation of a temporary pointer which is given the address provided as argument. Then using the `*` operator, you can access the results in the creation of a temporary pointer which is given the address provided as argument. Then using the `*` operator, you can access the
original variable, not a copy. original variable, not a copy.
Except in the case of interaction with a C library or some _very_ specific cases, I wouldn't advise using passing arguments by pointers: by reference does the job as neatly and in fact more efficiently (dereferencing a pointer `i` with `*i` syntax is not completely costless performance-wise). Except in the case of interaction with a C library or some _very_ specific cases, I wouldn't advise using passing arguments by pointers: by reference does the job as neatly and in fact more efficiently (dereferencing a pointer `i` with `*i` syntax is not completely costless performance-wise).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void IncrementAndPrintByPointer(int* i) void IncrementAndPrintByPointer(int* i)
{ {
*i += 1; *i += 1;
std::cout << "Inside the function: i = " << *i << std::endl; std::cout << "Inside the function: i = " << *i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByPointer(&i); IncrementAndPrintByPointer(&i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function with return value ## Function with return value
The value to return should come after the keyword `return`. The value to return should come after the keyword `return`.
A C++ function may include several return values in its implementation: A C++ function may include several return values in its implementation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
//! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative. //! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative.
int Sign(int a) int Sign(int a)
{ {
if (a > 0) if (a > 0)
return 1; return 1;
if (a == 0) if (a == 0)
return 0; return 0;
return -1; return -1;
} }
{ {
for (int a = -3; a < 4; ++a) for (int a = -3; a < 4; ++a)
std::cout << "Sign of " << a << " = " << Sign(a) << std::endl; std::cout << "Sign of " << a << " = " << Sign(a) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Alternate function syntax ## Alternate function syntax
There is now since C++ 11 another way to declare a function using so called `trailing return types`; it is not widespread but is advised by some developers (see for instance [this blog post](https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/) which lists pros and cons of both syntaxes). There is now since C++ 11 another way to declare a function using so called `trailing return types`; it is not widespread but is advised by some developers (see for instance [this blog post](https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/) which lists pros and cons of both syntaxes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother! // < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
auto Sum(int a, int b) -> int auto Sum(int a, int b) -> int
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The return type is optional: The return type is optional:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
auto SumWithoutExplicitReturnType(int a, int b) auto SumWithoutExplicitReturnType(int a, int b)
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int a = 8; int a = 8;
int b = -3; int b = -3;
std::cout << a << " + " << b << " = " << Sum(a, b) << std::endl; std::cout << a << " + " << b << " = " << Sum(a, b) << std::endl;
std::cout << a << " + " << b << " = " << SumWithoutExplicitReturnType(a, b) << std::endl; std::cout << a << " + " << b << " = " << SumWithoutExplicitReturnType(a, b) << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function overload ## Function overload
### The easy cases: arguments without ambiguity ### The easy cases: arguments without ambiguity
It is possible to define several different functions with the exact same name, provided the type of the argument differ: It is possible to define several different functions with the exact same name, provided the type of the argument differ:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
void F(); void F();
void F(int); // Ok void F(int); // Ok
double F(int, double); // Ok double F(int, double); // Ok
auto F(char) -> int; // Ok (alternate function syntax) auto F(char) -> int; // Ok (alternate function syntax)
std::string F(double, int, char*); // Ok std::string F(double, int, char*); // Ok
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **[WARNING]** Return type does not count! ### **[WARNING]** Return type does not count!
It is not the entire signature of the function that is taken into account when possible ambiguity is sought by the compiler: the return type is not taken into account. So the following cases will not be valid: It is not the entire signature of the function that is taken into account when possible ambiguity is sought by the compiler: the return type is not taken into account. So the following cases will not be valid:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void G(int); void G(int);
int G(int); // COMPILATION ERROR int G(int); // COMPILATION ERROR
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If we think about it, it is rather logical: in C++ we are not required to use the return type of a function (it's not the case in all languages: Go follows a different path on that topic for instance). The issue then is that the compiler has no way to know which `g(int)` is supposed to be called with `g(5)` for instance. If we think about it, it is rather logical: in C++ we are not required to use the return type of a function (it's not the case in all languages: Go follows a different path on that topic for instance). The issue then is that the compiler has no way to know which `g(int)` is supposed to be called with `g(5)` for instance.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **[WARNING]** This is a C++ only feature and will not work in C! ### **[WARNING]** This is a C++ only feature and will not work in C!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C you cannot do the following: if you run a simple program with overload: In C you cannot do the following: if you run a simple program with overload:
```c ```c
#include <stdio.h> #include <stdio.h>
void f() void f()
{ {
printf("No argument version"); printf("No argument version");
} }
void f(int a) void f(int a)
{ {
printf("Int argument version"); printf("Int argument version");
} }
int main() int main()
{ {
return 0; return 0;
} }
``` ```
you will get error messages such as: you will get error messages such as:
```shell ```shell
prog.c:8:6: error: redefinition of 'f' prog.c:8:6: error: redefinition of 'f'
8 | void f(int a) 8 | void f(int a)
| ^ | ^
prog.c:3:6: note: previous definition of 'f' with type 'void()' prog.c:3:6: note: previous definition of 'f' with type 'void()'
3 | void f() 3 | void f()
``` ```
(you may check this with [Wandbox](https://wandbox.org/) and select C instead of C++ as language). (you may check this with [Wandbox](https://wandbox.org/) and select C instead of C++ as language).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you're interested to understand why, you may read [this Wikipedia page](https://en.wikipedia.org/wiki/Name_mangling) (to put in a nutshell, C and C++ chose to handle very differently how to handle the symbols; C++ will use something called _mangling_ to be able to disambiguate between the different overloads). If you're interested to understand why, you may read [this Wikipedia page](https://en.wikipedia.org/wiki/Name_mangling) (to put in a nutshell, C and C++ chose to handle very differently how to handle the symbols; C++ will use something called _mangling_ to be able to disambiguate between the different overloads).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: do not make signature vary only by a reference or a pointer ### Good practice: do not make signature vary only by a reference or a pointer
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On the other hand, compiler is completely able to accept signatures that differs only by a reference or a pointer on one of the argument: On the other hand, compiler is completely able to accept signatures that differs only by a reference or a pointer on one of the argument:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void H(double a) void H(double a)
{ {
std::cout << "h(double) is called with a = " << a << '.' << std::endl; std::cout << "h(double) is called with a = " << a << '.' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void H(double* a) // Ok void H(double* a) // Ok
{ {
std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl; std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.; *a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void H(double& a) // Ok... but not advised! (see below) void H(double& a) // Ok... but not advised! (see below)
{ {
std::cout << "h(double&) is called with a = " << a << "; a is doubled by the function." << std::endl; std::cout << "h(double&) is called with a = " << a << "; a is doubled by the function." << std::endl;
a *= 2.; a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
H(5); // Ok H(5); // Ok
double x = 1.; double x = 1.;
H(&x); // Ok H(&x); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, there is a possible ambiguity between the pass-by-copy and pass-by-reference: However, there is a possible ambiguity between the pass-by-copy and pass-by-reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double x = 1.; double x = 1.;
H(x); // COMPILATION ERROR: should it call h(double) or h(double& )? H(x); // COMPILATION ERROR: should it call h(double) or h(double& )?
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You can lift the ambiguity for the pass-by-value: You can lift the ambiguity for the pass-by-value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double x = 1.; double x = 1.;
H(static_cast<double>(x)); // Ok H(static_cast<double>(x)); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But not to my knowledge for the pass-by-reference... So you should really avoid doing so: if you really need both functions, name them differently to avoid the ambiguity. But not to my knowledge for the pass-by-reference... So you should really avoid doing so: if you really need both functions, name them differently to avoid the ambiguity.
I would even avoid the pointer case: granted, there is no ambiguity for a computer standpoint, but if you get a developer who is not 100% clear about the pointer syntax he might end-up calling the wrong function: I would even avoid the pointer case: granted, there is no ambiguity for a computer standpoint, but if you get a developer who is not 100% clear about the pointer syntax he might end-up calling the wrong function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void H2(double a) void H2(double a)
{ {
std::cout << "h2(double) is called with a = " << a << '.' << std::endl; std::cout << "h2(double) is called with a = " << a << '.' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void H2(double* a) void H2(double* a)
{ {
std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl; std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.; *a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double x = 5.; double x = 5.;
double* ptr = &x; double* ptr = &x;
H2(x); // call h2(double) H2(x); // call h2(double)
H2(ptr); // call h2(double*) H2(ptr); // call h2(double*)
H2(*ptr); // call h2(double) H2(*ptr); // call h2(double)
H2(&x); // call h2(double*) H2(&x); // call h2(double*)
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Best viable function ### Best viable function
In fact, overloading may work even if the match is not perfect: the **best viable function** is chosen if possible... and some ambiguity may appear if none matches! In fact, overloading may work even if the match is not perfect: the **best viable function** is chosen if possible... and some ambiguity may appear if none matches!
The complete rules are very extensive and may be found [here](https://en.cppreference.com/w/cpp/language/overload_resolution); as a rule of thumb you should really strive to write overloaded functions with no easy ambiguity... or not using it at all: sometimes naming the function differently avoids loads of issues! The complete rules are very extensive and may be found [here](https://en.cppreference.com/w/cpp/language/overload_resolution); as a rule of thumb you should really strive to write overloaded functions with no easy ambiguity... or not using it at all: sometimes naming the function differently avoids loads of issues!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
int Min(int a, int b) int Min(int a, int b)
{ {
std::cout << "int version called!" << std::endl; std::cout << "int version called!" << std::endl;
return a < b ? a : b; return a < b ? a : b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
double Min(double a, double b) double Min(double a, double b)
{ {
std::cout << "double version called!" << std::endl; std::cout << "double version called!" << std::endl;
return a < b ? a : b; return a < b ? a : b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int i1 { 5 }, i2 { -7 }; int i1 { 5 }, i2 { -7 };
double d1 { 3.14}, d2 { -1.e24}; double d1 { 3.14}, d2 { -1.e24};
float f1 { 3.14f }, f2 { -4.2f}; float f1 { 3.14f }, f2 { -4.2f};
short s1 { 5 }, s2 { 7 }; short s1 { 5 }, s2 { 7 };
Min(5, 7); // no ambiguity Min(5, 7); // no ambiguity
Min(i1, i2); // no ambiguity Min(i1, i2); // no ambiguity
Min(f1, f2); // conversion to closest one Min(f1, f2); // conversion to closest one
Min(f1, d2); // conversion to closest one Min(f1, d2); // conversion to closest one
Min(s1, s2); // conversion to closest one Min(s1, s2); // conversion to closest one
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, with some other types it doesn't work as well if implicit conversion is dangerous and may loose data: However, with some other types it doesn't work as well if implicit conversion is dangerous and may loose data:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
unsigned int i1 { 5 }, i2 { 7 }; unsigned int i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate! Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
long i1 { 5 }, i2 { 7 }; long i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate! Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Likewise, if best candidate is not the same for each argument: Likewise, if best candidate is not the same for each argument:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
float f1 { 5.f }; float f1 { 5.f };
int i1 { 5 }; int i1 { 5 };
Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate... Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Advice: use overload only when there is no ambiguity whatsoever ### Advice: use overload only when there is no ambiguity whatsoever
That is when: That is when:
- The number of arguments is different between overloads. - The number of arguments is different between overloads.
- Or their types do not convert implicitly from one to another. For instance the following overloads are completely safe to use and the interface remains obvious for the end-user: - Or their types do not convert implicitly from one to another. For instance the following overloads are completely safe to use and the interface remains obvious for the end-user:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
#include <iostream> #include <iostream>
std::string GenerateString() std::string GenerateString()
{ {
std::cout << "No argument!"; std::cout << "No argument!";
return ""; return "";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::string GenerateString(char one_character) std::string GenerateString(char one_character)
{ {
std::cout << "One character: "; std::cout << "One character: ";
return std::string(1, one_character); return std::string(1, one_character);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::string GenerateString(char value1, char value2) std::string GenerateString(char value1, char value2)
{ {
std::cout << "Two characters: "; std::cout << "Two characters: ";
std::string ret(1, value1); std::string ret(1, value1);
ret += value2; ret += value2;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::string GenerateString(const std::string& string) std::string GenerateString(const std::string& string)
{ {
std::cout << "Std::string: "; std::cout << "Std::string: ";
return string; return string;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::string GenerateString(const char* string) std::string GenerateString(const char* string)
{ {
std::cout << "Char*: "; std::cout << "Char*: ";
return std::string(string); return std::string(string);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
std::cout << GenerateString() << std::endl; std::cout << GenerateString() << std::endl;
std::cout << GenerateString('a') << std::endl; std::cout << GenerateString('a') << std::endl;
std::cout << GenerateString('a', 'b') << std::endl; std::cout << GenerateString('a', 'b') << std::endl;
std::cout << GenerateString("Hello world!") << std::endl; std::cout << GenerateString("Hello world!") << std::endl;
std::string text("Hello!"); std::string text("Hello!");
std::cout << GenerateString(text) << std::endl; std::cout << GenerateString(text) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Optional parameters ## Optional parameters
It is possible to provide optional parameters in the **declaration** of a function: It is possible to provide optional parameters in the **declaration** of a function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother! // < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
// Declaration. // Declaration.
void FunctionWithOptional(double x, double y = 0., double z = 0.); void FunctionWithOptional(double x, double y = 0., double z = 0.);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother! // < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
#include <iostream> #include <iostream>
// Definition // Definition
void FunctionWithOptional(double x, double y, double z) // notice the absence of default value! void FunctionWithOptional(double x, double y, double z) // notice the absence of default value!
{ {
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl; std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
FunctionWithOptional(3., 5., 6.); FunctionWithOptional(3., 5., 6.);
FunctionWithOptional(3.); FunctionWithOptional(3.);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The reason not to repeat them is rather obvious: if both were accepted you may modify one of them and forget to modify the others, which would be a bad design... The reason not to repeat them is rather obvious: if both were accepted you may modify one of them and forget to modify the others, which would be a bad design...
There is a way to put it in the same place, that I do not recommend it (and your compiler should warn you most of the time): if you do not declare the function beforehand, default arguments may be specified at definition: There is a way to put it in the same place, that I do not recommend it (and your compiler should warn you most of the time): if you do not declare the function beforehand, default arguments may be specified at definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
// Definition which double acts as declaration // Definition which double acts as declaration
void FunctionWithOptional2(double x, double y = 0., double z = 0.) void FunctionWithOptional2(double x, double y = 0., double z = 0.)
{ {
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl; std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
FunctionWithOptional2(3., 5., 6.); // ok FunctionWithOptional2(3., 5., 6.); // ok
FunctionWithOptional2(3.); // ok FunctionWithOptional2(3.); // ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C and C++, arguments are only **positional**: you do not have a way to explicitly set an argument with a name for instance. In C and C++, arguments are only **positional**: you do not have a way to explicitly set an argument with a name for instance.
Therefore: Therefore:
* Optional arguments must be put together at the end of the function. * Optional arguments must be put together at the end of the function.
* You must think carefully if there are several of them and put the less likely to be set manually by the function user at then end. In our example above, if you want do call the function with a `x` and a `z` you must mandatorily also provide explicitly `y`. * You must think carefully if there are several of them and put the less likely to be set manually by the function user at then end. In our example above, if you want do call the function with a `x` and a `z` you must mandatorily also provide explicitly `y`.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Lambda functions ## Lambda functions
C++ 11 introduced a shorthand to define functions called __lambda functions__. C++ 11 introduced a shorthand to define functions called __lambda functions__.
An example is the best way to introduce them: An example is the best way to introduce them:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
// Locally defined function. // Locally defined function.
auto Square = [](double x) -> double auto Square = [](double x) -> double
{ {
return x * x; return x * x;
}; };
std::cout << Square(5.) << std::endl; std::cout << Square(5.) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Several notes: Several notes:
* Use `auto` as its return type; said type is not reproducible (see the _square_ and _cube_ example below). * Use `auto` as its return type; said type is not reproducible (see the _square_ and _cube_ example below).
* The symbol `->` that specifies the type of the returned value is optional. * The symbol `->` that specifies the type of the returned value is optional.
* Parameters come after the `[]` in parenthesis with the same syntax as ordinary functions. * Parameters come after the `[]` in parenthesis with the same syntax as ordinary functions.
* This is not the same as the [alternate syntax](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) explained earlier, even if they look similar: a lambda may be defined locally (here within a block) whereas a standard function (with usual or alternate syntax) can't. * This is not the same as the [alternate syntax](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) explained earlier, even if they look similar: a lambda may be defined locally (here within a block) whereas a standard function (with usual or alternate syntax) can't.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
// Locally defined function. // Locally defined function.
auto Square = [](double x) auto Square = [](double x)
{ {
return x * x; return x * x;
}; };
auto Cube = [](double x) auto Cube = [](double x)
{ {
return x * x * x; return x * x * x;
}; };
std::cout << "Are the lambda prototypes the same type? " std::cout << "Are the lambda prototypes the same type? "
<< (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl; << (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Inside the `[]` you might specify values that are transmitted to the body of the function; by default nothing is transmitted: Inside the `[]` you might specify values that are transmitted to the body of the function; by default nothing is transmitted:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto APlusB = [](int b) auto APlusB = [](int b)
{ {
return a + b; return a + b;
}; };
std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body. std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto APlusB = [a](int b) // Notice the `[a]` here! auto APlusB = [a](int b) // Notice the `[a]` here!
{ {
return a + b; return a + b;
}; };
std::cout << APlusB(3) << std::endl; std::cout << APlusB(3) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The values captured in the lambda might be transmitted by reference: The values captured in the lambda might be transmitted by reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto AddToA = [&a](int b) // Notice the `[&a]` here! auto AddToA = [&a](int b) // Notice the `[&a]` here!
{ {
a += b; a += b;
}; };
AddToA(3); AddToA(3);
std::cout << a << std::endl; std::cout << a << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is possible to capture everything (in the scope where the lambda is defined) by reference by using `[&]` but it is really ill-advised; don't do this! It is possible to capture everything (in the scope where the lambda is defined) by reference by using `[&]` but it is really ill-advised; don't do this!
Lambda functions really shines when you want to use them in a very special context; see below an example using the [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) function provided by the standard library (don't worry about `std::sort` - we will address it later in the notebook dedicated to [algorithms](../5-UsefulConceptsAndSTL/7-Algorithms.ipynb). If you want to know more you may also consult [cppreference](https://en.cppreference.com/w/cpp/algorithm/sort)). Lambda functions really shines when you want to use them in a very special context; see below an example using the [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) function provided by the standard library (don't worry about `std::sort` - we will address it later in the notebook dedicated to [algorithms](../5-UsefulConceptsAndSTL/7-Algorithms.ipynb). If you want to know more you may also consult [cppreference](https://en.cppreference.com/w/cpp/algorithm/sort)).
Let's imagine that for some reasons we want to sort integers in a weird fashion: first the odd numbers properly ordered and then the even numbers. We can give this admittedly pointless choice through a lambda: Let's imagine that for some reasons we want to sort integers in a weird fashion: first the odd numbers properly ordered and then the even numbers. We can give this admittedly pointless choice through a lambda:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <algorithm> // for sort #include <algorithm> // for sort
{ {
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 }; std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::cout << "Initial list = "; std::cout << "Initial list = ";
for (int value : list) for (int value : list)
std::cout << value << ' '; std::cout << value << ' ';
// My very specific sort operation: // My very specific sort operation:
// Returns true if lhs is odd and rhs isn't or if lhs < rhs. // Returns true if lhs is odd and rhs isn't or if lhs < rhs.
auto odd_first = [](auto lhs, auto rhs) auto odd_first = [](auto lhs, auto rhs)
{ {
const bool is_lhs_odd = !(lhs % 2 == 0); const bool is_lhs_odd = !(lhs % 2 == 0);
const bool is_rhs_odd = !(rhs % 2 == 0); const bool is_rhs_odd = !(rhs % 2 == 0);
if (is_lhs_odd != is_rhs_odd) if (is_lhs_odd != is_rhs_odd)
return is_lhs_odd; return is_lhs_odd;
return lhs < rhs; return lhs < rhs;
}; };
std::sort(list.begin(), list.end(), odd_first); std::sort(list.begin(), list.end(), odd_first);
std::cout << std::endl << "Sorted list = "; std::cout << std::endl << "Sorted list = ";
for (int value : list) for (int value : list)
std::cout << value << ' '; std::cout << value << ' ';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the use of an intermediate local variable for the lambda is not mandatory; the lambda may be provided on the fly: Please notice the use of an intermediate local variable for the lambda is not mandatory; the lambda may be provided on the fly:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
{ {
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 }; std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::vector<int> even_only; std::vector<int> even_only;
// Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later! // Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later!
std::copy_if(list.cbegin(), std::copy_if(list.cbegin(),
list.cend(), list.cend(),
std::back_inserter(even_only), std::back_inserter(even_only),
[](int value) [](int value)
{ {
return value % 2 == 0; return value % 2 == 0;
}); });
for (int value : even_only) for (int value : even_only)
std::cout << value << ' '; std::cout << value << ' ';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Passing a function as a an argument ## Passing a function as a an argument
In some cases, you might want to pass a function as an argument (and honestly most of the time you should refrain to do so: it may underline your design is not top notch). In some cases, you might want to pass a function as an argument (and honestly most of the time you should refrain to do so: it may underline your design is not top notch).
The syntax to do so is a bit ugly and stems directly from C; it relies upon using a pointer to a function. The syntax to do so is a bit ugly and stems directly from C; it relies upon using a pointer to a function.
The syntax looks like: The syntax looks like:
```c++ ```c++
unsigned int (*f) (int, double) unsigned int (*f) (int, double)
``` ```
where: where:
* `unsigned int` is the return type. * `unsigned int` is the return type.
* `int, double` are the type of the parameters of the function given as argument. * `int, double` are the type of the parameters of the function given as argument.
* `f` is the name of the argument. * `f` is the name of the argument.
It will be clearer in an example: It will be clearer in an example:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void PrintFunctionCall(int (*f) (int, int), int m, int n) void PrintFunctionCall(int (*f) (int, int), int m, int n)
{ {
std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl; std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int Multiply(int a, int b) int Multiply(int a, int b)
{ {
return a * b; return a * b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
int Add(int a, int b) int Add(int a, int b)
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintFunctionCall(Multiply, 5, 6); PrintFunctionCall(Multiply, 5, 6);
PrintFunctionCall(Add, 5, 6); PrintFunctionCall(Add, 5, 6);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are other ways to do this task: There are other ways to do this task:
* Using a template parameter. Templates will be reached [later in this tutorial](../4-Templates/0-main.ipynb), but for me it's usually the way to go. * Using a template parameter. Templates will be reached [later in this tutorial](../4-Templates/0-main.ipynb), but for me it's usually the way to go.
* Using [functors](../3-Operators/5-Functors.ipynb) * Using [functors](../3-Operators/5-Functors.ipynb)
* Using `std::function`, introduced in C++ 11. However <a href="https://vittorioromeo.info/index/blog/passing_functions_to_functions.html">this blog</a> explains why it's not a good idea; on top of the arguments given there it doesn't seem to respect the prototype closely (a function with double instead of int is for instance accepted). * Using `std::function`, introduced in C++ 11. However <a href="https://vittorioromeo.info/index/blog/passing_functions_to_functions.html">this blog</a> explains why it's not a good idea; on top of the arguments given there it doesn't seem to respect the prototype closely (a function with double instead of int is for instance accepted).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## A very special function: __main__ ## A very special function: __main__
Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where: Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where:
* __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2. * __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2.
* __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given. * __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given.
Please notice the internal mechanics of C/C++ compiler returns these values; if a user type `isPrime qwerty 20`, the main functions will return argc = 3. It is up to the writer of the main to ensure the arguments are correct. Please notice the internal mechanics of C/C++ compiler returns these values; if a user type `isPrime qwerty 20`, the main functions will return argc = 3. It is up to the writer of the main to ensure the arguments are correct.
If some of these values should be interpreted as numbers, it is also up to the developer to foresee the conversion from the C-string to a numerical value. If some of these values should be interpreted as numbers, it is also up to the developer to foresee the conversion from the C-string to a numerical value.
In the very specific of our Jupyter notebook, a unique main might be defined or not in the file: _cling_ performs some magic to generate one under the hood. In the very specific of our Jupyter notebook, a unique main might be defined or not in the file: _cling_ performs some magic to generate one under the hood.
The __main__ function may also be defined as __int main()__ without arguments if the program doesn't actually need any. The __main__ function may also be defined as __int main()__ without arguments if the program doesn't actually need any.
Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers. Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers.
The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes. The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes. These two macros are defined inside `cstdlib` header.
We will deal with main functions later when we will work in a true C++ environment. We will deal with main functions later when we will work in a true C++ environment.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Unused arguments
It might happen that a function provides parameters which are not actually used in the implementation; if you've set up the appropriate warning flags for your compiler, it should warn you about these unused parameters:
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int argc, char** argv)
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
To counteract this you have three strategies:
%% Cell type:markdown id: tags:
### Not naming the parameters
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int , char** )
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Casting the parameters to void
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int argc, char** argv)
{
static_cast<void>(argc);
static_cast<void>(argv);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Using [[maybe_unused]] (since C++ 17)
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
We strongly advised you to use the latest, if your code is using at least C++ 17:
- Not naming the parameters might seem innocuous here, but in more meaningful codes you lose expressivity: the fact that the argument isn't used doesn't mean you don't want the intel about what it is about (we'll see later in object programming `override` [we'll see later](../2-ObjectProgramming/7-polymorphism.ipynb#override-keyword)). Naming bears information, and dropping it is therefore loss of information.
- Casting to `void` is not straightforward for developers who don't know the idiom. Even so, the fact that it's not directly indicated at the declaration site makes it less expressive.
`[[maybe_unused]]` may in fact be used as well for local variables:
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
int a;
return EXIT_SUCCESS;
}
```
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
[[maybe_unused]] int a; // fixes the warning
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## `inline` functions ## `inline` functions
You may also in a function declaration and definition function prepend the prototype by an `inline`. This indicates the compiler this function might be **inlined**: this means the content of the function may be copied directly, thus avoiding a function call and potentially making your code a tiny bit faster. So for instance if you have a function: You may also in a function declaration and definition function prepend the prototype by an `inline`. This indicates the compiler this function might be **inlined**: this means the content of the function may be copied directly, thus avoiding a function call and potentially making your code a tiny bit faster. So for instance if you have a function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
inline double Square(double x) inline double Square(double x)
{ {
return x * x; return x * x;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
when this function is called somewhere, the compiler may replace directly the function by the code inside the definition: when this function is called somewhere, the compiler may replace directly the function by the code inside the definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Square(5.); // The compiler might substitute 5. * 5. to the actual function call here Square(5.); // The compiler might substitute 5. * 5. to the actual function call here
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This behaviour is pretty similar to the often frowned-upon **macros** from C, but the use of `inline` is absolutely not considered a bad practice... provided you have in mind the way it works: This behaviour is pretty similar to the often frowned-upon **macros** from C, but the use of `inline` is absolutely not considered a bad practice... provided you have in mind the way it works:
* You have probably notice the conditional in my statements regarding `inline`: the keyword is an _hint_ given to the compiler... that might be followed or not. * You have probably notice the conditional in my statements regarding `inline`: the keyword is an _hint_ given to the compiler... that might be followed or not.
* On the syntactic side, `inline` must be provided both in the declaration `and` the definition. * On the syntactic side, `inline` must be provided both in the declaration `and` the definition.
* `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation). * `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation).
* Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead. * Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead.
The `Square` example was sound: this is typically the kind of functions that might be inlined. The `Square` example was sound: this is typically the kind of functions that might be inlined.
Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled: Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#define SQUARE(x) ((x) * (x)) // macro #define SQUARE(x) ((x) * (x)) // macro
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
double x = 5.; double x = 5.;
std::cout << Square(++x) << std::endl; std::cout << Square(++x) << std::endl;
} }
{ {
double x = 5.; double x = 5.;
std::cout << SQUARE(++x) << std::endl; std::cout << SQUARE(++x) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Dynamic allocations](./5-DynamicAllocation.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Dynamic allocations](./5-DynamicAllocation.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
In C++, we can finely control the life cycle of objects and manage the memory allocated to them. This is what makes it possible to create more powerful applications than with many other languages, but it is also the main source of errors in the language. Pointers and dynamic memory management: watch out for danger! In C++, we can finely control the life cycle of objects and manage the memory allocated to them. This is what makes it possible to create more powerful applications than with many other languages, but it is also the main source of errors in the language. Pointers and dynamic memory management: watch out for danger!
## Stack ## Stack
The ordinary variables of C++ have a lifetime limited to the current instruction block, whether it is the current function, or an instruction block attached to an `if`, `for` or just independent. The ordinary variables of C++ have a lifetime limited to the current instruction block, whether it is the current function, or an instruction block attached to an `if`, `for` or just independent.
The memory allocated to them is located in an area called a **stack**, and is automatically relieved when exiting the current block using the **last in, first out** principle. The memory allocated to them is located in an area called a **stack**, and is automatically relieved when exiting the current block using the **last in, first out** principle.
If you want to learn more about memory layout, have a look [here]( https://www.geeksforgeeks.org/memory-layout-of-c-program/). If you want to learn more about memory layout, have a look [here]( https://www.geeksforgeeks.org/memory-layout-of-c-program/).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
{ {
int a { 5 }; int a { 5 };
double b { 7.4 }; double b { 7.4 };
} // at the end of this block, b is released first and then a - but 99.99 % of the time you shouldn't care } // at the end of this block, b is released first and then a - but 99.99 % of the time you shouldn't care
// about that order! // about that order!
// a and b are not available here // a and b are not available here
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are few limitations with the stack: There are few limitations with the stack:
* The number of memory you can allocate on the stack is rather limited. On a current POSIX OS the order of magnitude is ~ 8 MB (on Unix type `ulimit -s` in a terminal to get this information). If you allocate more you will get a **stack overflow** (and now you know why the [most popular developers forum](https://stackoverflow.com/) is named this way!) * The number of memory you can allocate on the stack is rather limited. On a current POSIX OS the order of magnitude is ~ 8 MB (on Unix type `ulimit -s` in a terminal to get this information). If you allocate more you will get a **stack overflow** (and now you know why the [most popular developers forum](https://stackoverflow.com/) is named this way!)
* The information is very local; you can't use it elsewhere. If you pass the variable as argument in a function for instance a copy is made (or if you're using a reference or a pointer you have to be sure all is done when the block is exited!) * The information is very local; you can't use it elsewhere. If you pass the variable as argument in a function for instance a copy is made (or if you're using a reference or a pointer you have to be sure all is done when the block is exited!)
* Stack information must be known at compile time: if you're allocating an array on the stack you must know its size beforehand. * Stack information must be known at compile time: if you're allocating an array on the stack you must know its size beforehand.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Heap and free store ## Heap and free store
You can in fact also explicitly place a variable in another memory area called **heap** or **free store**; doing so overcomes the stack limitations mentioned above. You can in fact also explicitly place a variable in another memory area called **heap** or **free store**; doing so overcomes the stack limitations mentioned above.
This is done by calling the `new` operator, which reserves the memory and returns its address, so that the user can store it _with a pointer_. This is done by calling the `new` operator, which reserves the memory and returns its address, so that the user can store it _with a pointer_.
The **heap** is independent of the **stack** and the variable thus created exists as long as the `delete` operator is not explicitly called. The creation and destruction of this type of variable is the responsibility of the programmer. The **heap** is independent of the **stack** and the variable thus created exists as long as the `delete` operator is not explicitly called. The creation and destruction of this type of variable is the responsibility of the programmer.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int* n = new int(5); // variable created on the heap and initialized with value 5. int* n = new int(5); // variable created on the heap and initialized with value 5.
std::cout << *n << std::endl; std::cout << *n << std::endl;
delete n; // deletion must be explicitly called; if not there is a memory leak! delete n; // deletion must be explicitly called; if not there is a memory leak!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
What is especially tricky is that: What is especially tricky is that:
* Creating and destroying can be done in places very disconnected in your program. * Creating and destroying can be done in places very disconnected in your program.
* You must ensure that whatever the runtime path used in your program each variable allocated on the heap: * You must ensure that whatever the runtime path used in your program each variable allocated on the heap:
- is destroyed (otherwise you get a **memory leak**) - is destroyed (otherwise you get a **memory leak**)
- is only destroyed once (or your program will likely crash with a message about **double deletion**). - is only destroyed once (or your program will likely crash with a message about **double deletion**).
In sophisticated programs, this could lead in serious and tedious bookkeeping to ensure all variables are properly handled, even if tools such as [Valgrind](http://www.valgrind.org/) or [Address sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) may help to find out those you will probably have forgotten somewhere along the way. In sophisticated programs, this could lead in serious and tedious bookkeeping to ensure all variables are properly handled, even if tools such as [Valgrind](http://www.valgrind.org/) or [Address sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) may help to find out those you will probably have forgotten somewhere along the way.
To be honest, C++ gets quite a bad name due to this tedious memory handling; fortunately the RAII idiom provides a neat way to automate nicely memory management (which we'll study [later](../5-UsefulConceptsAndSTL/2-RAII.ipynb)) and some vocal critics on forums that regret the lack of [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) might actually not be aware of this fundamental (from my point of view at least) idiom. To be honest, C++ gets quite a bad name due to this tedious memory handling; fortunately the RAII idiom provides a neat way to automate nicely memory management (which we'll study [later](../5-UsefulConceptsAndSTL/2-RAII.ipynb)) and some vocal critics on forums that regret the lack of [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) might actually not be aware of this fundamental (from my point of view at least) idiom.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Free store? ### Free store?
**Free store** is very similar in functionality to the **heap** (to the point I had to [check the difference](https://stackoverflow.com/questions/1350819/c-free-store-vs-heap) before writing this...) , and more often than not one word might be used as the other. If you want to be pedantic: **Free store** is very similar in functionality to the **heap** (to the point I had to [check the difference](https://stackoverflow.com/questions/1350819/c-free-store-vs-heap) before writing this...) , and more often than not one word might be used as the other. If you want to be pedantic:
* When memory is handled by `new`/`delete`, you should talk about **free store**. * When memory is handled by `new`/`delete`, you should talk about **free store**.
* When memory is handled by `malloc`/`free` (the C functions), you should talk about **heap**. * When memory is handled by `malloc`/`free` (the C functions), you should talk about **heap**.
Pedantry aside, the important thing to know is to never mix both syntax: if you allocate memory by `new` don't use `free` to relieve it. Pedantry aside, the important thing to know is to never mix both syntax: if you allocate memory by `new` don't use `free` to relieve it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Arrays on heap ## Arrays on heap
If you want to init an array which size you do not know at compile time or that might overflow the stack, you may to do with `new` syntax mixed with `[]`: If you want to init an array which size you do not know at compile time or that might overflow the stack, you may to do with `new` syntax mixed with `[]`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <random> #include <random>
int* throw_dice(std::size_t ndigit) { int* throw_dice(std::size_t ndigit) {
// Don't bother much here - this is lifted from https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution // Don't bother much here - this is lifted from https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
std::random_device rd; // a seed source for the random number engine std::random_device rd; // a seed source for the random number engine
std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd() std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> distrib(1, 6); std::uniform_int_distribution<> distrib(1, 6);
int* dice_result = new int[ndigit]; int* dice_result = new int[ndigit];
for (std::size_t i = 0; i < ndigit; ++i) for (std::size_t i = 0; i < ndigit; ++i)
dice_result[i] = distrib(gen); dice_result[i] = distrib(gen);
return dice_result; return dice_result;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
auto Ndice = 5ul; auto Ndice = 5ul;
int* throw_5_dices = throw_dice(Ndice); int* throw_5_dice = throw_dice(Ndice);
for (std::size_t i = 0; i < Ndice; ++i) for (std::size_t i = 0; i < Ndice; ++i)
std::cout << throw_5_dices[i] << std::endl; std::cout << throw_5_dice[i] << std::endl;
delete[] throw_5_dices; delete[] throw_5_dice;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
Ndice = 3; Ndice = 3;
int* throw_7_dices = throw_dice(Ndice); int* throw_7_dice = throw_dice(Ndice);
for (std::size_t i = 0; i < Ndice; ++i) for (std::size_t i = 0; i < Ndice; ++i)
std::cout << throw_7_dices[i] << std::endl; std::cout << throw_7_dice[i] << std::endl;
delete[] throw_7_dices; delete[] throw_7_dice;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice that: Please notice that:
* No value can be assigned in construction: you must first allocate the memory for the array and only in a second time fill it. * No value can be assigned in construction: you must first allocate the memory for the array and only in a second time fill it.
* A `[]` **must** be added to the **delete** instruction to indicate to the compiler this is actually an array that is destroyed. * A `[]` **must** be added to the **delete** instruction to indicate to the compiler this is actually an array that is destroyed.
In fact, my advice would be to avoid entirely to deal directly with such arrays and use containers from the standard library such as `std::vector`: In fact, my advice would be to avoid entirely to deal directly with such arrays and use containers from the standard library such as `std::vector`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
{ {
std::vector<int> pi_first_five_digits { 3, 1, 4, 1, 5 }; std::vector<int> pi_first_five_digits { 3, 1, 4, 1, 5 };
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
that does the exact same job in a shorter way and is much more secure to use (spoiler: `std::vector` is built upon the RAII idiom mentioned briefly in this notebook). that does the exact same job in a shorter way and is much more secure to use (spoiler: `std::vector` is built upon the RAII idiom mentioned briefly in this notebook).
We shall see `std::vector` more deeply [later](../5-UsefulConceptsAndSTL/3-Containers.ipynb) but will nonetheless use it before this as it is a rather elementary brick in most C++ codes. We shall see `std::vector` more deeply [later](../5-UsefulConceptsAndSTL/3-Containers.ipynb) but will nonetheless use it before this as it is a rather elementary brick in most C++ codes.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Input and output streams](./6-Streams.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Input and output streams](./6-Streams.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Predefined streams ## Predefined streams
The standard C++ includes an input/output library that specifies a common interface for all data exchanges with the outside world, based in particular on the insertion `<<` and extraction `>>` operators. The standard C++ includes an input/output library that specifies a common interface for all data exchanges with the outside world, based in particular on the insertion `<<` and extraction `>>` operators.
### `std::cout` ### `std::cout`
We have already dealt profusely with `std::cout` which provide the link to the Unix channel `stdout`: We have already dealt profusely with `std::cout` which provide the link to the Unix channel `stdout`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << "Hello world!" << std::endl; std::cout << "Hello world!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std::cerr` ### `std::cerr`
There is also `std::cerr`, which is related to Unix `stderr`: There is also `std::cerr`, which is related to Unix `stderr`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int n = -4; int n = -4;
if (n < 0) if (n < 0)
std::cerr << "Positive or null value expected!" << std::endl; std::cerr << "Positive or null value expected!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std:cin` ### `std:cin`
And finally `std::cin`, related to Unix channel `stdin`. Line crossings are ignored (assimilated to spaces and tabs). And finally `std::cin`, related to Unix channel `stdin`. Line crossings are ignored (assimilated to spaces and tabs).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox std::cin/clang %%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using // Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system. // a hack that calls the underlying clang compiler on your system.
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <random> #include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::random_device rd; // Will be used to obtain a seed for the random number engine std::random_device rd; // Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd() std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100); std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen); auto hidden = dis(gen);
int guess = -1; int guess = -1;
while (guess != hidden) while (guess != hidden)
{ {
std::cout << "Find the value between 0 and 100: "; std::cout << "Find the value between 0 and 100: ";
std::cin >> guess; std::cin >> guess;
if (guess > hidden) if (guess > hidden)
std::cout << " Too high!" << std::endl; std::cout << " Too high!" << std::endl;
else if (guess < hidden) else if (guess < hidden)
std::cout << " Too low!" << std::endl; std::cout << " Too low!" << std::endl;
} }
std::cout << "Congratulations! You have found the hidden number!" << std::endl; std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`std::cin` is a bit more tricky to use than the others, as the risk the operation fails is really higher. For instance, if you give a string in the code above it will become crazy and keep printing the same message "Too high!" or "Too low!" (be ready to restart the kernel...). The following code fixes this: `std::cin` is a bit more tricky to use than the others, as the risk the operation fails is really higher. For instance, if you give a string in the code above it will become crazy and keep printing the same message "Too high!" or "Too low!" (be ready to restart the kernel...). The following code fixes this:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox std::cin/clang %%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using // Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system. // a hack that calls the underlying clang compiler on your system.
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <random> #include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::random_device rd; //Will be used to obtain a seed for the random number engine std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100); std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen); auto hidden = dis(gen);
int guess = -1; int guess = -1;
while (guess != hidden) while (guess != hidden)
{ {
do do
{ {
if (!std::cin) if (!std::cin)
{ {
std::cin.clear(); // clear the states of std::cin, putting it back to `goodbit`. std::cin.clear(); // clear the states of std::cin, putting it back to `goodbit`.
std::cin.ignore(10000, '\n'); // clean-up what might remain in std::cin before using it again. std::cin.ignore(10000, '\n'); // clean-up what might remain in std::cin before using it again.
} }
std::cout << "Find the value between 0 and 100: "; std::cout << "Find the value between 0 and 100: ";
std::cin >> guess; std::cin >> guess;
} while (!std::cin); } while (!std::cin);
if (guess > hidden) if (guess > hidden)
std::cout << " Too high!" << std::endl; std::cout << " Too high!" << std::endl;
else if (guess < hidden) else if (guess < hidden)
std::cout << " Too low!" << std::endl; std::cout << " Too low!" << std::endl;
} }
std::cout << "Congratulations! You have found the hidden number!" << std::endl; std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you want to learn more about `std::cin`, you might want to look at [this post](https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input) on StackOverflow. If you want to learn more about `std::cin`, you might want to look at [this post](https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input) on StackOverflow.
If you need to use it extensively, you should look more deeply the behaviour of the bit flags (`goodbit`, `badbit`, `failbit`, `eofbit`). If you need to use it extensively, you should look more deeply the behaviour of the bit flags (`goodbit`, `badbit`, `failbit`, `eofbit`).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Input/output with files ## Input/output with files
The same syntax with operators `<<` and `>>` may be used to interact with files; the streams are built with `std::ofstream` for an output stream and `std::ifstream` for an input stream. The same syntax with operators `<<` and `>>` may be used to interact with files; the streams are built with `std::ofstream` for an output stream and `std::ifstream` for an input stream.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <fstream> // for std::ifstream and std::ofstream #include <fstream> // for std::ifstream and std::ofstream
#include <iostream> #include <iostream>
{ {
std::ofstream out("File.tmp"); std::ofstream out("File.tmp");
out << 5 << std::endl; out << 5 << std::endl;
out << -7 << std::endl; out << -7 << std::endl;
out << 9 << std::endl; out << 9 << std::endl;
out.close(); // file is written on disk when closed; automatically done when `out` gets out of scope otherwise out.close(); // file is written on disk when closed; automatically done when `out` gets out of scope otherwise
std::ifstream in("File.tmp"); std::ifstream in("File.tmp");
int value; int value;
while (in >> value) while (in >> value)
std::cout << value << std::endl; std::cout << value << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `getline()` ### `getline()`
When reading a file, if you want to interpret it line by line you should also consider `getline()`; this function may get a third argument to choose which separator to use (`\n` by default). When reading a file, if you want to interpret it line by line you should also consider `getline()`; this function may get a third argument to choose which separator to use (`\n` by default).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <string> #include <string>
{ {
std::ifstream in("File.tmp"); // assumes previous cell has been played recently! std::ifstream in("File.tmp"); // assumes previous cell has been played recently!
std::string line; std::string line;
while (getline(in, line)) while (getline(in, line))
std::cout << line << std::endl; std::cout << line << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `ostream` and `istream` ## `ostream` and `istream`
If you want to devise a function that may take as argument either a `std::cout` or a `std::ofstream`, you should use a `std::ostream` (we'll study [later](../2-ObjectProgramming/6-inheritance.ipynb) why this works but just take my word for now): If you want to devise a function that may take as argument either a `std::cout` or a `std::ofstream`, you should use a `std::ostream` (we'll study [later](../2-ObjectProgramming/6-inheritance.ipynb) why this works but just take my word for now):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void PrintOnStream(std::ostream& out) void PrintOnStream(std::ostream& out)
{ {
out << "Printed on the chosen stream!" << std::endl; out << "Printed on the chosen stream!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintOnStream(std::cout); PrintOnStream(std::cout);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
#include <fstream> #include <fstream>
{ {
std::ofstream out("test_stream.txt"); std::ofstream out("test_stream.txt");
PrintOnStream(out); PrintOnStream(out);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
// Read the content of the line previously written. // Read the content of the line previously written.
std::ifstream in("test_stream.txt"); std::ifstream in("test_stream.txt");
std::string line; std::string line;
getline(in, line); getline(in, line);
std::cout << line << std::endl; std::cout << line << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Binary files
Exactly as in C, you may want to write directly your output in binary format rather than in ascii format by specifying `std::ios::binary` as second argument of `std::ofstream` or `std::ifstream`.
It may be combined with others with `|` operator (here `std::ios::app` which tells to append the new content at the end the file if it already exists - without this cue the default behaviour is to overwrite pre-existing `test_stream.binary` file).
%% Cell type:code id: tags:
``` c++
#include <fstream>
{
std::ofstream out("test_stream.binary", std::ios::binary | std::ios::app);
}
```
%% Cell type:markdown id: tags:
Using binary is *much* faster than ascii; if you want to write lots of data (typically in HPC) you should probably consider it.
Drawbacks are:
- Files written this way are less portable.
- Files can't be read directly by a human.
- Writing and reading such files requires more handiwork by the developer.
Still, it is an option that should be on the table as I/O might really be a bottleneck in runtime.
For more details see [the documentation on base class for all I/O stream classes](https://en.cppreference.com/w/cpp/io/ios_base) - fmtflags are also interesting, but a bit to deep for the training.
%% Cell type:markdown id: tags:
## Conversion ## Conversion
Stream syntax was until C++ 11 the only way to convert: Stream syntax was until C++ 11 the only way to convert:
- A string into a number with `std::istringstream` - A string into a number with `std::istringstream`
- A number into a string with `std::ostringstream`; the `str()` method returns the content as a `std::string`. - A number into a string with `std::ostringstream`; the `str()` method returns the content as a `std::string`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
std::string number_as_string = "657"; std::string number_as_string = "657";
int number; int number;
std::istringstream iconv(number_as_string); std::istringstream iconv(number_as_string);
iconv >> number; iconv >> number;
std::cout << "Number + 1 = " << number + 1 << std::endl; std::cout << "Number + 1 = " << number + 1 << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::ostringstream oconv; std::ostringstream oconv;
oconv << "The number is " << number; oconv << "The number is " << number;
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To reuse a `std::ostringstream`, you must set its content to an empty string with an overloaded `str()`: To reuse a `std::ostringstream`, you must set its content to an empty string with an overloaded `str()`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::ostringstream oconv; std::ostringstream oconv;
oconv << "The number is " << number; oconv << "The number is " << number;
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
oconv.str(""); // reset oconv oconv.str(""); // reset oconv
oconv << "My new content is now there!"; oconv << "My new content is now there!";
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course as for `std::cin` you may check the state of the object is still valid - if conversion is incorrect it won't!: Of course as for `std::cin` you may check the state of the object is still valid - if conversion is incorrect it won't!:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
std::string number_as_string = "abd"; std::string number_as_string = "abd";
int number; int number;
std::istringstream iconv(number_as_string); std::istringstream iconv(number_as_string);
iconv >> number; // invalid conversion! iconv >> number; // invalid conversion!
if (!iconv) if (!iconv)
std::cerr << "Invalid string!" << std::endl; std::cerr << "Invalid string!" << std::endl;
else else
std::cout << "Number + 1 = " << number + 1 << std::endl; std::cout << "Number + 1 = " << number + 1 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C++ 11, `std::to_string()` and the [`stoi` (and similar functions for long)](https://en.cppreference.com/w/cpp/string/basic_string/stol) were introduced to provide similar functionality with a more direct syntax: In C++ 11, `std::to_string()` and the [`stoi` (and similar functions for long)](https://en.cppreference.com/w/cpp/string/basic_string/stol) were introduced to provide similar functionality with a more direct syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::string int_to_string("Number is "); std::string int_to_string("Number is ");
int_to_string += std::to_string(number); int_to_string += std::to_string(number);
std::cout << int_to_string << std::endl; std::cout << int_to_string << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
std::string number_as_string = "4557"; std::string number_as_string = "4557";
int number = std::stoi(number_as_string); int number = std::stoi(number_as_string);
std::cout << "Number is " << number << std::endl; std::cout << "Number is " << number << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well... It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well...
C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support) (still true in 2024...) C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support) (still true in 2024...)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Formatting and manipulators ## Formatting and manipulators
You may act upon the exact formatting of the output. You may act upon the exact formatting of the output.
I'll be honest: it's not what is the most refined tool in the C++ library, and you may long for the simplicity and power of something like Python (or even C `printf`, which is much simpler to use while being a mess under the hood...). I'll be honest: it's not what is the most refined tool in the C++ library, and you may long for the simplicity and power of something like Python (or even C `printf`, which is much simpler to use while being a mess under the hood...).
Once again `std::format` from C++ 20 should be a game changer here! Once again `std::format` from C++ 20 should be a game changer here!
The difficulty is that some settings apply only to the next entry onto the stream (`width` here), while others change the behaviour permanently (until told otherwise of course, e.g. `precision` here). Here are few examples of these syntaxes: The difficulty is that some settings apply only to the next entry onto the stream (`width` here), while others change the behaviour permanently (until told otherwise of course, e.g. `precision` here). Here are few examples of these syntaxes:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout.precision(2); // number of decimal digits std::cout.precision(2); // number of decimal digits
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 1.237 ; std::cout << 1.237 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 100.1245 ; std::cout << 100.1245 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << '\n' ; std::cout << '\n' ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 1.5774e-2 ; std::cout << 1.5774e-2 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 12. << '\n' ; std::cout << 12. << '\n' ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Manipulators** provide a shorter syntax to add some of the properties as the `width` or the `precision`: **Manipulators** provide a shorter syntax to add some of the properties as the `width` or the `precision`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <iomanip> // for std::setprecision #include <iomanip> // for std::setprecision
{ {
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout << std::setprecision(2) << std::setw(8) << 1.237; std::cout << std::setprecision(2) << std::setw(8) << 1.237;
std::cout << std::setw(8) << 100.1245; std::cout << std::setw(8) << 100.1245;
std::cout << '\n'; std::cout << '\n';
std::cout << std::setw(8) << 1.5774e-2; std::cout << std::setw(8) << 1.5774e-2;
std::cout << std::setw(8) << 12. << '\n'; std::cout << std::setw(8) << 12. << '\n';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Introduction to the concept of object](./1-Introduction.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Introduction to the concept of object](./1-Introduction.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Motivation ## Motivation
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Sometimes, there are variables that are bound to be initialized and used together. Let's consider the coordinates of a vector in a three-dimensional space: Sometimes, there are variables that are bound to be initialized and used together. Let's consider the coordinates of a vector in a three-dimensional space:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <cmath> // For std::sqrt #include <cmath> // For std::sqrt
double Norm(double v_x, double v_y, double v_z) double Norm(double v_x, double v_y, double v_z)
{ {
return std::sqrt( v_x * v_x + v_y * v_y + v_z * v_z ); return std::sqrt( v_x * v_x + v_y * v_y + v_z * v_z );
} }
{ {
double v1_x, v1_y, v1_z; double v1_x, v1_y, v1_z;
v1_x = 1.; v1_x = 1.;
v1_y = 5.; v1_y = 5.;
v1_z = -2.; v1_z = -2.;
std::cout << Norm(v1_x, v1_y, v1_z) << std::endl; std::cout << Norm(v1_x, v1_y, v1_z) << std::endl;
double v2_x, v2_y, v2_z; double v2_x, v2_y, v2_z;
v2_x = 2.; v2_x = 2.;
v2_y = 2.; v2_y = 2.;
v2_z = 4.; v2_z = 4.;
std::cout << Norm(v2_x, v2_y, v2_z) << std::endl; std::cout << Norm(v2_x, v2_y, v2_z) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The code above is completely oblivious of the close relationship between `x`, `y` and `z`, and for instance the `Norm` function takes three distinct arguments. The code above is completely oblivious of the close relationship between `x`, `y` and `z`, and for instance the `Norm` function takes three distinct arguments.
This is not just an inconveniency: this can lead to mistake if there is an error in the variables passed: This is not just an inconveniency: this can lead to mistake if there is an error in the variables passed:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double v1_x, v1_y, v1_z; double v1_x, v1_y, v1_z;
v1_x = 1.; v1_x = 1.;
v1_y = 5.; v1_y = 5.;
v1_z = -2.; v1_z = -2.;
double v2_x, v2_y, v2_z; double v2_x, v2_y, v2_z;
v2_x = 2.; v2_x = 2.;
v2_y = 2.; v2_y = 2.;
v2_z = 4.; v2_z = 4.;
const double norm1 = Norm(v1_x, v1_y, v2_z); // probably not what was intended, but the program const double norm1 = Norm(v1_x, v1_y, v2_z); // probably not what was intended, but the program
// has no way to figure out something is fishy! // has no way to figure out something is fishy!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## The C response: the `struct` ## The C response: the `struct`
C introduced the `struct` to be able to group nicely data together and limit the risk I exposed above: C introduced the `struct` to be able to group nicely data together and limit the risk I exposed above:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Vector struct Vector
{ {
double x; double x;
double y; double y;
double z; double z;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
double Norm(Vector v) double Norm(Vector v)
{ {
return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
} }
{ {
Vector v1; Vector v1;
v1.x = 1.; v1.x = 1.;
v1.y = 5.; v1.y = 5.;
v1.z = -2.; v1.z = -2.;
std::cout << Norm(v1) << std::endl; std::cout << Norm(v1) << std::endl;
Vector v2; Vector v2;
v2.x = 2.; v2.x = 2.;
v2.y = 2.; v2.y = 2.;
v2.z = 4.; v2.z = 4.;
std::cout << Norm(v2) << std::endl; std::cout << Norm(v2) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Calling `Norm` is now both more elegant (only one argument) and less dangerous (I can't mix by mistake coordinates from different objects). Calling `Norm` is now both more elegant (only one argument) and less dangerous (I can't mix by mistake coordinates from different objects).
Let's introduce at this point a bit of vocabulary: Let's introduce at this point a bit of vocabulary:
- `x`, `y` and `z` in the structure are called **member variables** or **data attributes** (often shorten as **attributes** even if in a class this is actually not completely proper). On a side note: some C++ purists will be adamant only **member variables** should be used; but I rather use **data attributes** which is the term preferred in many others object programming languages. - `x`, `y` and `z` in the structure are called **member variables** or **data attributes** (often shorten as **attributes** even if in a class this is actually not completely proper). On a side note: some C++ purists will be adamant only **member variables** should be used; but I rather use **data attributes** which is the term preferred in many others object programming languages.
- `Vector` is a **struct**, which is a somewhat simplified **class** (we will explain the difference when we'll introduce classes). - `Vector` is a **struct**, which is a somewhat simplified **class** (we will explain the difference when we'll introduce classes).
- `v1` and `v2` are **objects**. - `v1` and `v2` are **objects**.
Let's also highlight the `.` syntax which allows to access the attributes of an object (e.g `v1.x`). Let's also highlight the `.` syntax which allows to access the attributes of an object (e.g `v1.x`).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The semicolon at the end of a `struct` ### The semicolon at the end of a `struct`
This comes historically from the C, where a `struct` could be defined and initialized at the same time: This comes historically from the C, where a `struct` could be defined and initialized at the same time:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// < Following code isn't supported by our kernel, so we're using native compiler instead. // < Following code isn't supported by our kernel, so we're using native compiler instead.
#include <cstdlib> #include <cstdlib>
int main() int main()
{ {
struct VectorAndInstantiate struct VectorAndInstantiate
{ {
double x; double x;
double y; double y;
double z; double z;
} v1; // Here the struct is declared and at the same time an object v1 is created } v1; // Here the struct is declared and at the same time an object v1 is created
v1.x = 1.; v1.x = 1.;
v1.y = 5.; v1.y = 5.;
v1.z = -2.; v1.z = -2.;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is absolutely **not** encouraged in C++, but it may help you to remember always closing a `struct` (or later a `class`) with a semicolon. This is absolutely **not** encouraged in C++, but it may help you to remember always closing a `struct` (or later a `class`) with a semicolon.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Passing a struct to a function ## Passing a struct to a function
In the `norm` function above, we passed as argument an object of `Vector` type by value. When we introduced functions, we saw there were three ways to pass an argument: In the `norm` function above, we passed as argument an object of `Vector` type by value. When we introduced functions, we saw there were three ways to pass an argument:
* By value. * By value.
* By reference. * By reference.
* By pointers. * By pointers.
I didn't mention there the copy cost of a pass-by-value: copying a plain old data (POD) type such as an `int` or a `double` is actually cheap, and is recommended over a reference. But the story is not the same for an object: the cost of copying the object in the case of a pass-by-value may actually be quite high (imagine if there were an array with thousands of `double` values inside for instance) - and that's supposing the object is copyable (but we're not quite ready yet to deal with [that aspect](../3-Operators/4-CanonicalForm.ipynb#Uncopyable-class)). I didn't mention there the copy cost of a pass-by-value: copying a plain old data (POD) type such as an `int` or a `double` is actually cheap, and is recommended over a reference. But the story is not the same for an object: the cost of copying the object in the case of a pass-by-value may actually be quite high (imagine if there were an array with thousands of `double` values inside for instance) - and that's supposing the object is copyable (but we're not quite ready yet to deal with [that aspect](../3-Operators/4-CanonicalForm.ipynb#Uncopyable-class)).
### Pass-by-const-reference ### Pass-by-const-reference
So most of the time it is advised to pass arguments by reference, often along a `const` qualifier if the object is not to be modified by the function: So most of the time it is advised to pass arguments by reference, often along a `const` qualifier if the object is not to be modified by the function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
double NormWithoutCopy(const Vector& v) double NormWithoutCopy(const Vector& v)
{ {
return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
} }
{ {
Vector v1; Vector v1;
v1.x = 1.; v1.x = 1.;
v1.y = 5.; v1.y = 5.;
v1.z = -2.; v1.z = -2.;
std::cout << NormWithoutCopy(v1) << std::endl; std::cout << NormWithoutCopy(v1) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Pass-by-pointer ### Pass-by-pointer
Of course, if for some reason you prefer to use pointers it is also possible: Of course, if for some reason you prefer to use pointers it is also possible:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
double Norm(const Vector* const v) // can keep the name here: no possible ambiguity double Norm(const Vector* const v) // can keep the name here: no possible ambiguity
{ {
return std::sqrt((*v).x * (*v).x + (*v).y * (*v).y + (*v).z * (*v).z); return std::sqrt((*v).x * (*v).x + (*v).y * (*v).y + (*v).z * (*v).z);
} }
{ {
Vector v1; Vector v1;
v1.x = 1.; v1.x = 1.;
v1.y = 5.; v1.y = 5.;
v1.z = -2.; v1.z = -2.;
std::cout << Norm(&v1) << std::endl; std::cout << Norm(&v1) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is more than little verbosy, so a shortcut has been introduced; `->` means you dereference a pointer and then calls the attribute: This is more than little verbosy, so a shortcut has been introduced; `->` means you dereference a pointer and then calls the attribute:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
double NormWithPointerShortcut(const Vector* const v) double NormWithPointerShortcut(const Vector* const v)
{ {
return std::sqrt(v->x * v->x + v->y * v->y + v->z * v->z); return std::sqrt(v->x * v->x + v->y * v->y + v->z * v->z);
} }
{ {
Vector v1; Vector v1;
v1.x = 1.; v1.x = 1.;
v1.y = 5.; v1.y = 5.;
v1.z = -2.; v1.z = -2.;
std::cout << NormWithPointerShortcut(&v1) << std::endl; std::cout << NormWithPointerShortcut(&v1) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Initialization of objects ## Initialization of objects
So far, we have improved the way the `Norm` function is called, but the initialization of a vector is still a bit tedious. Let's wrap up a function to ease that: So far, we have improved the way the `Norm` function is called, but the initialization of a vector is still a bit tedious. Let's wrap up a function to ease that:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Init(Vector& v, double x, double y, double z) void Init(Vector& v, double x, double y, double z)
{ {
v.x = x; v.x = x;
v.y = y; v.y = y;
v.z = z; v.z = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Vector v1; Vector v1;
Init(v1, 1., 5., -2.); Init(v1, 1., 5., -2.);
std::cout << "Norm = " << Norm(v1) << std::endl; std::cout << "Norm = " << Norm(v1) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [(Base) constructors and destructor](./3-constructors-destructor.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [(Base) constructors and destructor](./3-constructors-destructor.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction to base constructor ## Introduction to base constructor
In fact, our previous `Init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class. In fact, our previous `Init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.
Several constructors may be defined for a given class, provided there is no signature overlap. Several constructors may be defined for a given class, provided there is no signature overlap.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Vector struct Vector
{ {
double x_; double x_;
double y_; double y_;
double z_; double z_;
Vector(); // Constructor Vector(); // Constructor
Vector(double x, double y, double z); // Another constructor Vector(double x, double y, double z); // Another constructor
double Norm() const; double Norm() const;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Data attributes may be initialized more efficiently with a special syntax shown below: Data attributes may be initialized more efficiently with a special syntax shown below:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_(x), // See the syntax here: `:` to introduce data attributes initialization, : x_(x), // See the syntax here: `:` to introduce data attributes initialization,
y_(y), // and commas to separate the different data attributes. y_(y), // and commas to separate the different data attributes.
z_(z) z_(z)
{ {
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
// < without this line the perfectly valid - and recommended! - braces in data // < without this line the perfectly valid - and recommended! - braces in data
// attribute initialization are not accepted by our kernel. // attribute initialization are not accepted by our kernel.
Vector::Vector() Vector::Vector()
: x_{0}, // braces may also be used since C++ 11, and are the recommended choice : x_{0}, // braces may also be used since C++ 11, and are the recommended choice
y_{}, // the value inside may be skipped if it is 0 y_{}, // the value inside may be skipped if it is 0
z_{} z_{}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
double Vector::Norm() const double Vector::Norm() const
{ {
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
Vector v(5., 6., -4.2); // note the creation of an object with a constructor call. Vector v(5., 6., -4.2); // note the creation of an object with a constructor call.
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **[WARNING]** How to call a constructor without argument ### **[WARNING]** How to call a constructor without argument
There is a technicality for constructor without arguments: they must be called **without** parenthesis (the reason is a possible confusion with a [functor](../3-Operators/5-Functors.ipynb) - see Item 6 of [More Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this): There is a technicality for constructor without arguments: they must be called **without** parenthesis (the reason is a possible confusion with a [functor](../3-Operators/5-Functors.ipynb) - see Item 6 of [More Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
Vector v; // no parenthesis here! Vector v; // no parenthesis here!
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### "Auto-to-stick" syntax for constructor calls ### "Auto-to-stick" syntax for constructor calls
A way to avoid the mistake entirely is to call the so-called "auto-to-stick" alternate syntax: A way to avoid the mistake entirely is to call the so-called "auto-to-stick" alternate syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
auto v = Vector(5, 10, 15); // auto-to-stick syntax: a new perfectly fine way to declare an object. auto v = Vector(5, 10, 15); // auto-to-stick syntax: a new perfectly fine way to declare an object.
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This syntax completely removes the ambiguity: you **have to** keep the `()`, so the constructor with no arguments doesn't become a special case that is handled differently. This syntax completely removes the ambiguity: you **have to** keep the `()`, so the constructor with no arguments doesn't become a special case that is handled differently.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
auto v = Vector(); // auto-to-stick syntax auto v = Vector(); // auto-to-stick syntax
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This auto-to-stick syntax is not widely used, but is advised by some developers as a natural evolution of the syntax of the language (it is very akin to the [alternate syntax for functions](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) we saw earlier). This auto-to-stick syntax is not widely used, but is advised by some developers as a natural evolution of the syntax of the language (it is very akin to the [alternate syntax for functions](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) we saw earlier).
I advise you to read this very interesting [FluentCpp post](https://www.fluentcpp.com/2018/09/28/auto-stick-changing-style/) about this syntax which ponders about the relunctance we might have to embrace evolution of our languages - so the reading is of interest even for developers using other languages. I advise you to read this very interesting [FluentCpp post](https://www.fluentcpp.com/2018/09/28/auto-stick-changing-style/) about this syntax which ponders about the relunctance we might have to embrace evolution of our languages - so the reading is of interest even for developers using other languages.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## The importance of the `:` syntax ## The importance of the `:` syntax
We saw just above that we may init data attributes either in the body of the constructor or before the body with the `:` syntax. We saw just above that we may init data attributes either in the body of the constructor or before the body with the `:` syntax.
In the above example, both are fine and work as expected. In the above example, both are fine and work as expected.
However, there are several cases for which you **must** use the `:` syntax; here are two of them. However, there are several cases for which you **must** use the `:` syntax; here are two of them.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct First struct First
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Second struct Second
{ {
const First& first_; const First& first_;
Second(const First& first, int value); Second(const First& first, int value);
const int value_; const int value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Second::Second(const First& first, int value) Second::Second(const First& first, int value)
{ {
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here! first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
value_ = value; // COMPILATION ERROR: can't change the value of a const variable! value_ = value; // COMPILATION ERROR: can't change the value of a const variable!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Second::Second(const First& first, int value) Second::Second(const First& first, int value)
: first_(first), // OK! : first_(first), // OK!
value_(value) value_(value)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With the `:` syntax, the attributes are filled with their expected values _as soon as_ the object is created. With the `:` syntax, the attributes are filled with their expected values _as soon as_ the object is created.
On the other hand, the body of the constructor is run _after_ the actual creation occurs. On the other hand, the body of the constructor is run _after_ the actual creation occurs.
So concerning data attributes if their value is set in the body of the constructor it is actually an _assignment_ that takes place and replace the default value built at construction. So concerning data attributes if their value is set in the body of the constructor it is actually an _assignment_ that takes place and replace the default value built at construction.
The cases in which the data attributes **must** be defined by the `:` constructor are: The cases in which the data attributes **must** be defined by the `:` constructor are:
- When the data attribute can't be copied (this case covers both cases already seen). - When the data attribute can't be copied (this case covers both cases already seen).
- When the type of the data attribute doesn't foresee a default constructor (i.e. a constructor without arguments). - When the type of the data attribute doesn't foresee a default constructor (i.e. a constructor without arguments).
Anyway, when you can you should really strive to use the `:` syntax to define data attributes. Anyway, when you can you should really strive to use the `:` syntax to define data attributes.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: In the `:` syntax to initialize data attributes, define them in the same order as in the class declaration ### Good practice: In the `:` syntax to initialize data attributes, define them in the same order as in the class declaration
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
For instance, don't do that: For instance, don't do that:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct PoorlyDefinedVector struct PoorlyDefinedVector
{ {
double x_; double x_;
double y_; double y_;
PoorlyDefinedVector(double x, double y); PoorlyDefinedVector(double x, double y);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PoorlyDefinedVector::PoorlyDefinedVector(double x, double y) PoorlyDefinedVector::PoorlyDefinedVector(double x, double y)
: y_(y), // y_ is defined first, whereas x_ comes first in data attribute list! : y_(y), // y_ is defined first, whereas x_ comes first in data attribute list!
x_(x) x_(x)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may have unwanted effects if you do not respect the same ordering. You may have unwanted effects if you do not respect the same ordering.
Fortunately, compilers are able to emit warnings to advise you to remedy this if you have properly activated them with options such as `-Wall` (we shall discuss this in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb)). Here is an example when calling directly `clang`; you may also try with `gcc` which will also provides a warning: Fortunately, compilers are able to emit warnings to advise you to remedy this if you have properly activated them with options such as `-Wall` (we shall discuss this in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb)). Here is an example when calling directly `clang`; you may also try with `gcc` which will also provides a warning:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
#include <cstdlib> #include <cstdlib>
struct PoorlyDefinedVector struct PoorlyDefinedVector
{ {
double x_; double x_;
double y_; double y_;
PoorlyDefinedVector(double x, double y); PoorlyDefinedVector(double x, double y);
}; };
PoorlyDefinedVector::PoorlyDefinedVector(double x, double y) PoorlyDefinedVector::PoorlyDefinedVector(double x, double y)
: y_(y), // y_ is defined first, whereas x_ comes first in data attribute list! : y_(y), // y_ is defined first, whereas x_ comes first in data attribute list!
x_(x) x_(x)
{ } { }
int main() int main()
{ {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Delegating constructor ## Delegating constructor
Since C++ 11, it is possible to use a base constructor when defining another constructor: Since C++ 11, it is possible to use a base constructor when defining another constructor:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Vector2 struct Vector2
{ {
double x_, y_, z_; double x_, y_, z_;
Vector2(); Vector2();
Vector2(double x); Vector2(double x);
Vector2(double x, double y, double z); Vector2(double x, double y, double z);
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void Vector2::Print() const void Vector2::Print() const
{ {
std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl; std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
Vector2::Vector2() Vector2::Vector2()
: x_(-1.), : x_(-1.),
y_(-1.), y_(-1.),
z_(-1.) z_(-1.)
{ {
std::cout << "Calling Vector2 constructor with no arguments." << std::endl; std::cout << "Calling Vector2 constructor with no arguments." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector2::Vector2(double x, double y, double z) Vector2::Vector2(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector2::Vector2(double x) Vector2::Vector2(double x)
: Vector2() : Vector2()
{ {
x_ = x; // As the first constructor is assumed to build fully the object, you can't assign data attributes x_ = x; // As the first constructor is assumed to build fully the object, you can't assign data attributes
// before the body of the constructor. // before the body of the constructor.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
std::cout << "Constructor with no argument:" << std::endl; std::cout << "Constructor with no argument:" << std::endl;
Vector2 v1; Vector2 v1;
v1.Print(); v1.Print();
std::cout << "Constructor with no delegation:" << std::endl; std::cout << "Constructor with no delegation:" << std::endl;
Vector2 v2(3., 7., 5.); Vector2 v2(3., 7., 5.);
v2.Print(); v2.Print();
std::cout << "Constructor that calls a delegate constructor:" << std::endl; std::cout << "Constructor that calls a delegate constructor:" << std::endl;
Vector2 v3(3.); Vector2 v3(3.);
v3.Print(); v3.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Default constructor ## Default constructor
If in a `class` or `struct` no constructor is defined, a default one is assumed: it takes no argument and sports an empty body. If in a `class` or `struct` no constructor is defined, a default one is assumed: it takes no argument and sports an empty body.
As soon as another constructor is defined, this default constructor no longer exists: As soon as another constructor is defined, this default constructor no longer exists:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct ClassWithoutConstructor struct ClassWithoutConstructor
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ClassWithoutConstructor my_object; ClassWithoutConstructor my_object;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct ClassWithConstructorWithArg struct ClassWithConstructorWithArg
{ {
ClassWithConstructorWithArg(int a); ClassWithConstructorWithArg(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a) ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ClassWithConstructorWithArg my_object; // COMPILATION ERROR! ClassWithConstructorWithArg my_object; // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well thought-out arguments, you do not want your end-user to bypass this with an inconsiderate call to a constructor without arguments! This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well thought-out arguments, you do not want your end-user to bypass this with an inconsiderate call to a constructor without arguments!
If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself): If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct ClassWithConstructorWithAndWithoutArg struct ClassWithConstructorWithAndWithoutArg
{ {
ClassWithConstructorWithAndWithoutArg() = default; ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a); ClassWithConstructorWithAndWithoutArg(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a) ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ClassWithConstructorWithAndWithoutArg my_object; // OK! ClassWithConstructorWithAndWithoutArg my_object; // OK!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: provide in the data attribute declaration a default value ## Good practice: provide in the data attribute declaration a default value
In a constructor, you are expected to initialize properly all the data attributes. You can't expect a default behaviour if you fail to do so: In a constructor, you are expected to initialize properly all the data attributes. You can't expect a default behaviour if you fail to do so:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct BadlyInitialized struct BadlyInitialized
{ {
int a_; int a_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
BadlyInitialized my_object; BadlyInitialized my_object;
std::cout << "Undefined behaviour: no guarantee for the value of the data attribute!: " << my_object.a_ << std::endl; std::cout << "Undefined behaviour: no guarantee for the value of the data attribute!: " << my_object.a_ << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You are therefore supposed to define explicitly all the data attributes in all of your constructors. It was easy to get trumped by this in C++98/03: if you added a new data attribute and forgot to initialize it in one of your constructor, you would have undefined behaviour that is one of the worst bug to track down! (as on your machine/architecture you may have a "good" behaviour haphazardly). You are therefore supposed to define explicitly all the data attributes in all of your constructors. It was easy to get trumped by this in C++98/03: if you added a new data attribute and forgot to initialize it in one of your constructor, you would have undefined behaviour that is one of the worst bug to track down! (as on your machine/architecture you may have a "good" behaviour haphazardly).
Fortunately, C++ 11 introduced a mechanism I strongly recommend to provide a default value: Fortunately, C++ 11 introduced a mechanism I strongly recommend to provide a default value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct SafeClass struct SafeClass
{ {
int a_ { 5 }; // The default value is provided here in the class declaration. int a_ { 5 }; // The default value is provided here in the class declaration.
SafeClass() = default; SafeClass() = default;
SafeClass(int new_value); SafeClass(int new_value);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
SafeClass::SafeClass(int new_value) SafeClass::SafeClass(int new_value)
: a_(new_value) : a_(new_value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
SafeClass no_Arg; SafeClass no_Arg;
std::cout << "If constructor doesn't change the value, default is used: " << no_Arg.a_ << std::endl; std::cout << "If constructor doesn't change the value, default is used: " << no_Arg.a_ << std::endl;
SafeClass modified(10); SafeClass modified(10);
std::cout << "If constructor changes the value, choice is properly used: " << modified.a_ << std::endl; std::cout << "If constructor changes the value, choice is properly used: " << modified.a_ << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice doing so doesn't prevent you to use the efficient initialization of `a_` in the constructor with arguments: the values thus provided in the data attributes definitions are used only if the constructor doesn't supersede them. Please notice doing so doesn't prevent you to use the efficient initialization of `a_` in the constructor with arguments: the values thus provided in the data attributes definitions are used only if the constructor doesn't supersede them.
In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use. In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: use `explicit` constructors by default ## Good practice: use `explicit` constructors by default
Let's study the following case: Let's study the following case:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct ClassWithIntConstructor struct ClassWithIntConstructor
{ {
ClassWithIntConstructor(int a); ClassWithIntConstructor(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a) ClassWithIntConstructor::ClassWithIntConstructor(int a)
{ {
std::cout << "Constructor called with argument " << a << std::endl; std::cout << "Constructor called with argument " << a << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ClassWithIntConstructor my_object(5); ClassWithIntConstructor my_object(5);
my_object = 7; // Dubious but correct: assigning an integer! my_object = 7; // Dubious but correct: assigning an integer!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So what happens here? In fact, the compiler implicitly convert the integer read into a `ClassWithIntConstructor` constructor with an integer argument... So what happens here? In fact, the compiler implicitly convert the integer read into a `ClassWithIntConstructor` constructor with an integer argument...
There are situations in which this might be deemed the right thing to do (none to my mind but I guess it depends on your programming style) but more often than not it's not what is intended and a good old compiler yell would be much preferable. There are situations in which this might be deemed the right thing to do (none to my mind but I guess it depends on your programming style) but more often than not it's not what is intended and a good old compiler yell would be much preferable.
To do so, in C++ 11 you must stick the keyword **explicit** in the declaration in front of the constructor. Personally I tend to always provide it to my constructors, following the likewise advice by [Effective Modern C++](../bibliography.ipynb#Effective-Modern-C++). To do so, in C++ 11 you must stick the keyword **explicit** in the declaration in front of the constructor. Personally I tend to always provide it to my constructors, following the likewise advice by [Effective Modern C++](../bibliography.ipynb#Effective-Modern-C++).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct ClassWithExplicitIntConstructor struct ClassWithExplicitIntConstructor
{ {
explicit ClassWithExplicitIntConstructor(int a); explicit ClassWithExplicitIntConstructor(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a) ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{ {
std::cout << "Constructor called with argument " << a << std::endl; std::cout << "Constructor called with argument " << a << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ClassWithExplicitIntConstructor my_object(5); ClassWithExplicitIntConstructor my_object(5);
my_object = 7; // COMPILATION ERROR! YAY! my_object = 7; // COMPILATION ERROR! YAY!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Destructor ## Destructor
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The pendant of the constructor is the **destructor**, which is called when the object is terminated. Contrary to constructors, there is only one destructor for a given class, and by design it takes no parameter. The pendant of the constructor is the **destructor**, which is called when the object is terminated. Contrary to constructors, there is only one destructor for a given class, and by design it takes no parameter.
The syntax is like a constructor with no parameter with an additional `~` in front of the name. The syntax is like a constructor with no parameter with an additional `~` in front of the name.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Array struct Array
{ {
Array(int unique_id, std::size_t array_size); Array(int unique_id, std::size_t array_size);
~Array(); // Destructor! ~Array(); // Destructor!
double* underlying_array_ = nullptr; double* underlying_array_ = nullptr;
const int unique_id_; const int unique_id_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Array::Array(int unique_id, std::size_t array_size) Array::Array(int unique_id, std::size_t array_size)
: unique_id_(unique_id) : unique_id_(unique_id)
{ {
underlying_array_ = new double[array_size]; underlying_array_ = new double[array_size];
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
Array::~Array() Array::~Array()
{ {
std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl; std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl;
delete[] underlying_array_; delete[] underlying_array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Array array1(1, 5ul); Array array1(1, 5ul);
{ {
Array array2(2, 3ul); Array array2(2, 3ul);
{ {
Array array3(3, 5ul); Array array3(3, 5ul);
} }
Array array4(4, 2ul); Array array4(4, 2ul);
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It's important to notice the ordering here: as soon as an object becomes out of scope, it is immediately destroyed; the creation order doesn't matter at all! It's important to notice the ordering here: as soon as an object becomes out of scope, it is immediately destroyed; the creation order doesn't matter at all!
We will see a bit [later](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) how to take advantage of this behaviour to write programs that do not leak memory. We will see a bit [later](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) how to take advantage of this behaviour to write programs that do not leak memory.
### Default destructor ### Default destructor
If not specified, C++ implicitly defines a destructor with an empty body. Personally I like even in this case to make it explicit, which is done the same way as for a constructor from C++11 onward: If not specified, C++ implicitly defines a destructor with an empty body. Personally I like even in this case to make it explicit, which is done the same way as for a constructor from C++11 onward:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct MyClass struct MyClass
{ {
MyClass() = default; // explicit default constructor MyClass() = default; // explicit default constructor
~MyClass() = default; // explicit default destructor ~MyClass() = default; // explicit default destructor
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is however a matter of personal taste; see for instance [this post from FluentCpp](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie) for the opposite advice of not defining explicitly default constructor / destructor if you don't have to. This is however a matter of personal taste; see for instance [this post from FluentCpp](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie) for the opposite advice of not defining explicitly default constructor / destructor if you don't have to.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Static attributes](./5-static.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Static attributes](./5-static.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static in C ## Static in C
As a reminder, we have seen in a [previous notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C. As a reminder, we have seen in a [previous notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C.
What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idiom presented here). What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idiom presented here).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static methods ## Static methods
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a `static` keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances. Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a `static` keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.
Static attributes are following the exact same rules as the standard ones regarding the access status (public or private). Static attributes are following the exact same rules as the standard ones regarding the access status (public or private).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
struct Class struct Class
{ {
static std::string ClassName(); static std::string ClassName();
Class() = default; Class() = default;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::string Class::ClassName() std::string Class::ClassName()
{ {
return "Class"; return "Class";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << "A static method may be called without any object instantiated: " std::cout << "A static method may be called without any object instantiated: "
<< Class::ClassName() << std::endl; << Class::ClassName() << std::endl;
Class obj; Class obj;
std::cout << "But any object of the class may access it as if it was a regular method: " std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl; << obj.ClassName() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static data attributes - to avoid... (see next section to understand why!) ## Static data attributes - to avoid... (see next section to understand why!)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// cling - used by our kernel - doesn't enable proper initialization of a static data attribute; // cling - used by our kernel - doesn't enable proper initialization of a static data attribute;
// we therefore use the native clang instead. // we therefore use the native clang instead.
#include <iostream> #include <iostream>
#include <vector> #include <vector>
// ======================== // ========================
// Declarations // Declarations
// ======================== // ========================
struct Class struct Class
{ {
Class(); Class();
~Class(); ~Class();
static unsigned int Ninstance_; static unsigned int Ninstance_;
}; };
void Print(); void Print();
// ======================== // ========================
// Definitions // Definitions
// ======================== // ========================
Class::Class() Class::Class()
{ {
++Ninstance_; ++Ninstance_;
} }
Class::~Class() Class::~Class()
{ {
--Ninstance_; --Ninstance_;
} }
// IMPORTANT: this line must be put in a compiled file! // IMPORTANT: this line must be put in a compiled file!
unsigned int Class::Ninstance_ = 0; unsigned int Class::Ninstance_ = 0;
void Print() void Print()
{ {
std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl; std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl;
} }
// ======================== // ========================
// Main // Main
// ======================== // ========================
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
Print(); Print();
Class obj; Class obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl; std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl;
Print(); Print();
{ {
std::vector<Class> vec(5); std::vector<Class> vec(5);
Print(); Print();
} }
Print(); Print();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static order initialization fiasco - and its fix ## Static order initialization fiasco - and its fix
However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead: However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <vector> #include <vector>
struct Class3 struct Class3
{ {
Class3(); Class3();
~Class3(); ~Class3();
static unsigned int& Ninstance(); // notice the reference and the fact it's now a method static unsigned int& Ninstance(); // notice the reference and the fact it's now a method
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
unsigned int& Class3::Ninstance() unsigned int& Class3::Ninstance()
{ {
static unsigned int ret = 0; // the initial value, please notice the use of C static here! static unsigned int ret = 0; // the initial value, please notice the use of C static here!
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Class3::Class3() Class3::Class3()
{ {
Ninstance()++; Ninstance()++;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Class3::~Class3() Class3::~Class3()
{ {
Ninstance()--; Ninstance()--;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Print3() void Print3()
{ {
std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl; std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Print3(); Print3();
Class3 obj; Class3 obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl; std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl;
Print3(); Print3();
{ {
Class3* vec = new Class3[5]; Class3* vec = new Class3[5];
Print3(); Print3();
delete[] vec; delete[] vec;
} }
Print3(); Print3();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To understand better the possible issue and the fix proposed, you may have a look at: To understand better the possible issue and the fix proposed, you may have a look at:
* Item 26 of [More effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) * Item 26 of [More effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)
* The dedicated item on [isocpp FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order) * The dedicated item on [isocpp FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### New C++ 17 fix ### New C++ 17 fix
C++ 17 actually provides a way to define the value in the header file within the class declaration with the `inline` keyword: C++ 17 actually provides a way to define the value in the header file within the class declaration with the `inline` keyword:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Class4 struct Class4
{ {
Class4(); Class4();
~Class4(); ~Class4();
static inline unsigned int Ninstance_ = 0; static inline unsigned int Ninstance_ = 0;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave us the hint! Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave us the hint!
And make sure not to forget the `inline` keyword, without which you go straight back to the fiasco situation... And make sure not to forget the `inline` keyword, without which you go straight back to the fiasco situation...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Introduction](./1-Intro.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Introduction](./1-Intro.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Motivation ## Motivation
We've seen at length in the object programming part that classes are basically new types defined by the developer. However sometimes we would like to use exactly the same syntax as for the base type. Let's see for instance a basic class to handle tri-dimensional vectors: We've seen at length in the object programming part that classes are basically new types defined by the developer. However sometimes we would like to use exactly the same syntax as for the base type. Let's see for instance a basic class to handle tri-dimensional vectors:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class Vector class Vector
{ {
// Friendship because `Add()` needs to access private members and no accessors were defined. // Friendship because `Add()` needs to access private members and no accessors were defined.
friend Vector Add(const Vector& v1, const Vector& v2); friend Vector Add(const Vector& v1, const Vector& v2);
public : public :
Vector(double x, double y, double z); Vector(double x, double y, double z);
Vector() = default; Vector() = default;
void Print() const; void Print() const;
private : private :
double x_ = 0.; double x_ = 0.;
double y_ = 0.; double y_ = 0.;
double z_ = 0.; double z_ = 0.;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector Add(const Vector& v1, const Vector& v2) Vector Add(const Vector& v1, const Vector& v2)
{ {
Vector ret; Vector ret;
ret.x_ = v1.x_ + v2.x_; ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_; ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_; ret.z_ = v1.z_ + v2.z_;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void Vector::Print() const void Vector::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Vector v1(3., 5., 7.); Vector v1(3., 5., 7.);
Vector v2(7., 5., 3.); Vector v2(7., 5., 3.);
Vector v3 = Add(v1, v2); Vector v3 = Add(v1, v2);
v3.Print(); v3.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now the same with a _plain old data type_ is much more natural to write with no (apparent) method: Now the same with a _plain old data type_ is much more natural to write with no (apparent) method:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
double x1 = 3.; double x1 = 3.;
double x2 = 7.; double x2 = 7.;
double x3 = x1 + x2; double x3 = x1 + x2;
std::cout << x3 << std::endl; std::cout << x3 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ provides the way to mimic this behaviour with **operator overloading**. This is a very powerful conceit, but also one that should be approached with some care... C++ provides the way to mimic this behaviour with **operator overloading**. This is a very powerful conceit, but also one that should be approached with some care...
We will see the general way to define such an operator in this notebook and see in dedicated notebooks which are the ones specifically useful. We will see the general way to define such an operator in this notebook and see in dedicated notebooks which are the ones specifically useful.
## Overloading an operator ## Overloading an operator
To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload. In the following we will just replace the `Add` method by `operator+`. To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload. In the following we will just replace the `Add` method by `operator+`.
The following code illustrate how to do so: The following code illustrate how to do so:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
// < We need to help the kernel interpret properly the code below, which is perfectly valid C++. Don't bother about this magics! // < We need to help the kernel interpret properly the code below, which is perfectly valid C++. Don't bother about this magics!
#include <iostream> #include <iostream>
class VectorPlus class VectorPlus
{ {
public : public :
VectorPlus(double x, double y, double z); VectorPlus(double x, double y, double z);
VectorPlus() = default; VectorPlus() = default;
void Print() const; void Print() const;
// Friendship as free function operator+ wouldn't otherwise be allowed to access data attributes. // Friendship as free function operator+ wouldn't otherwise be allowed to access data attributes.
friend VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2); friend VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2);
private : private :
double x_ = 0.; double x_ = 0.;
double y_ = 0.; double y_ = 0.;
double z_ = 0.; double z_ = 0.;
}; };
VectorPlus::VectorPlus(double x, double y, double z) VectorPlus::VectorPlus(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
void VectorPlus::Print() const void VectorPlus::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2) VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2)
{ {
// Provides a symmetric implementation of operator +: both vectors are at the same level! // Provides a symmetric implementation of operator +: both vectors are at the same level!
VectorPlus ret; VectorPlus ret;
ret.x_ = v1.x_ + v2.x_; ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_; ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_; ret.z_ = v1.z_ + v2.z_;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlus v1(3., 5., 7.); VectorPlus v1(3., 5., 7.);
VectorPlus v2(7., 5., 3.); VectorPlus v2(7., 5., 3.);
VectorPlus v3 = v1 + v2; // Nicer syntax! VectorPlus v3 = v1 + v2; // Nicer syntax!
v3.Print(); v3.Print();
VectorPlus v4 = operator+(v1, v2); // but "usual" method syntax is possible as well VectorPlus v4 = operator+(v1, v2); // but "usual" method syntax is possible as well
v4.Print(); v4.Print();
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It should be noted that for most operators it is also possible to define them as a class method instead: It should be noted that for most operators it is also possible to define them as a class method instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
class VectorPlusAsMethod class VectorPlusAsMethod
{ {
public : public :
VectorPlusAsMethod(double x, double y, double z); VectorPlusAsMethod(double x, double y, double z);
VectorPlusAsMethod() = default; VectorPlusAsMethod() = default;
void Print() const; void Print() const;
VectorPlusAsMethod operator+(const VectorPlusAsMethod& v) const; VectorPlusAsMethod operator+(const VectorPlusAsMethod& v) const;
private : private :
double x_ {}; double x_ {};
double y_ {}; double y_ {};
double z_ {}; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
VectorPlusAsMethod::VectorPlusAsMethod(double x, double y, double z) VectorPlusAsMethod::VectorPlusAsMethod(double x, double y, double z)
: x_{x}, : x_{x},
y_{y}, y_{y},
z_{z} z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void VectorPlusAsMethod::Print() const void VectorPlusAsMethod::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlusAsMethod VectorPlusAsMethod::operator+(const VectorPlusAsMethod& v) const VectorPlusAsMethod VectorPlusAsMethod::operator+(const VectorPlusAsMethod& v) const
{ {
VectorPlusAsMethod ret; VectorPlusAsMethod ret;
ret.x_ = x_ + v.x_; ret.x_ = x_ + v.x_;
ret.y_ = y_ + v.y_; ret.y_ = y_ + v.y_;
ret.z_ = z_ + v.z_; ret.z_ = z_ + v.z_;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlusAsMethod v1(3., 5., 7.); VectorPlusAsMethod v1(3., 5., 7.);
VectorPlusAsMethod v2(7., 5., 3.); VectorPlusAsMethod v2(7., 5., 3.);
VectorPlusAsMethod v3 = v1 + v2; VectorPlusAsMethod v3 = v1 + v2;
v3.Print(); v3.Print();
VectorPlusAsMethod v4 = v1.operator+(v2); // but "usual" method syntax is possible as well VectorPlusAsMethod v4 = v1.operator+(v2); // but "usual" method syntax is possible as well
v4.Print(); v4.Print();
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We see here in the definition of the `operator+` that both `VectorPlusAsMethod` objects added aren't symmetric: one is the data attribute while the other is the data attribute of another object given as an argument. If is often advised to rather use the free function version to avoid this asymmetry, but it is mostly a matter of taste as both are working. We see here in the definition of the `operator+` that both `VectorPlusAsMethod` objects added aren't symmetric: one is the data attribute while the other is the data attribute of another object given as an argument. If is often advised to rather use the free function version to avoid this asymmetry, but it is mostly a matter of taste as both are working.
As a side note, please remark the `VectorPlusAsMethod::operator+` implementation is able to reach the private data attributes of the argument `v`; this means the private status is set **at class level** and not at object level. As a side note, please remark the `VectorPlusAsMethod::operator+` implementation is able to reach the private data attributes of the argument `v`; this means the private status is set **at class level** and not at object level.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator between different types ## Operator between different types
It is also possible to define an operator which acts upon two objects of different nature: It is also possible to define an operator which acts upon two objects of different nature:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
class VectorPlusDouble class VectorPlusDouble
{ {
public : public :
VectorPlusDouble(double x, double y, double z); VectorPlusDouble(double x, double y, double z);
VectorPlusDouble() = default; VectorPlusDouble() = default;
void Print() const; void Print() const;
VectorPlusDouble operator+(double value) const; VectorPlusDouble operator+(double value) const;
private : private :
double x_ {}; double x_ {};
double y_ {}; double y_ {};
double z_ {}; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void VectorPlusDouble::Print() const void VectorPlusDouble::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
VectorPlusDouble::VectorPlusDouble(double x, double y, double z) VectorPlusDouble::VectorPlusDouble(double x, double y, double z)
: x_{x}, : x_{x},
y_{y}, y_{y},
z_{z} z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlusDouble VectorPlusDouble::operator+(double value) const VectorPlusDouble VectorPlusDouble::operator+(double value) const
{ {
VectorPlusDouble ret; VectorPlusDouble ret;
ret.x_ = x_ + value; ret.x_ = x_ + value;
ret.y_ = y_ + value; ret.y_ = y_ + value;
ret.z_ = z_ + value; ret.z_ = z_ + value;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = vector + 5.; VectorPlusDouble vector_plus_5 = vector + 5.;
vector_plus_5.Print(); vector_plus_5.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, pay attention to the fact this operator is not commutative. It is indeed a shortcut to However, pay attention to the fact this operator is not commutative. It is indeed a shortcut to
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = vector.operator+(5.); VectorPlusDouble vector_plus_5 = vector.operator+(5.);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and the following won't compile: and the following won't compile:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = 5. + vector; // COMPILATION ERROR! VectorPlusDouble vector_plus_5 = 5. + vector; // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you want it to be possible, you have to define the operator with arguments in both orders; you therefore need to use out-of-class prototype of the function. If you want it to be possible, you have to define the operator with arguments in both orders; you therefore need to use out-of-class prototype of the function.
Of course, it is a **good practice** in this case to define one in way of the other: Of course, it is a **good practice** in this case to define one in way of the other:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
class VectorPlusDoubleCommutative class VectorPlusDoubleCommutative
{ {
public : public :
VectorPlusDoubleCommutative(double x, double y, double z); VectorPlusDoubleCommutative(double x, double y, double z);
VectorPlusDoubleCommutative() = default; VectorPlusDoubleCommutative() = default;
void Print() const; void Print() const;
friend VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value); friend VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value);
friend VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v); friend VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v);
private : private :
double x_ {}; double x_ {};
double y_ {}; double y_ {};
double z_ {}; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void VectorPlusDoubleCommutative::Print() const void VectorPlusDoubleCommutative::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
VectorPlusDoubleCommutative::VectorPlusDoubleCommutative(double x, double y, double z) VectorPlusDoubleCommutative::VectorPlusDoubleCommutative(double x, double y, double z)
: x_{x}, : x_{x},
y_{y}, y_{y},
z_{z} z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value) VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value)
{ {
VectorPlusDoubleCommutative ret; VectorPlusDoubleCommutative ret;
ret.x_ = v.x_ + value; ret.x_ = v.x_ + value;
ret.y_ = v.y_ + value; ret.y_ = v.y_ + value;
ret.z_ = v.z_ + value; ret.z_ = v.z_ + value;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v) VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v)
{ {
return v + value; // good practice: make it rely upon the other `operator+` defined! return v + value; // good practice: make it rely upon the other `operator+` defined!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
VectorPlusDoubleCommutative vector(5., 3.2, -1.); VectorPlusDoubleCommutative vector(5., 3.2, -1.);
VectorPlusDoubleCommutative vector_plus_5 = vector + 5.; VectorPlusDoubleCommutative vector_plus_5 = vector + 5.;
VectorPlusDoubleCommutative vector_plus_5_commutated = 5. + vector; VectorPlusDoubleCommutative vector_plus_5_commutated = 5. + vector;
vector_plus_5.Print(); vector_plus_5.Print();
vector_plus_5_commutated.Print(); vector_plus_5_commutated.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Limitations ## Limitations
You cannot change: You cannot change:
* The number of operators arguments * The number of operators arguments
* The [precedence rules](https://en.cppreference.com/w/cpp/language/operator_precedence) (between `+` and `*` for instance) * The [precedence rules](https://en.cppreference.com/w/cpp/language/operator_precedence) (between `+` and `*` for instance)
You can't _invent_ new operators, but only redefine operators in the following list (taken from [Operators in C and C++](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B), please consult for more details): You can't _invent_ new operators, but only redefine operators in the following list (taken from [Operators in C and C++](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B), please consult for more details):
* Arithmetic operators: `+ - * / % ++ --` * Arithmetic operators: `+ - * / % ++ --`
* Comparison operators: `== != <= >= < > <=>` * Comparison operators: `== != <= >= < > <=>`
* Logical operators: `! && ||` * Logical operators: `! && ||`
* Bitwise operators: `~ & | ^ << >>` * Bitwise operators: `~ & | ^ << >>`
* Assignment operators: `= += -= *= /= %= &= |= ^= <<= >>=` * Assignment operators: `= += -= *= /= %= &= |= ^= <<= >>=`
* Member and pointer operators: `[] * & -> ->*` * Member and pointer operators: `[] * & -> ->*`
* Other operators: `() , "" new new[] delete delete[]` * Other operators: `() , "" new new[] delete delete[]`
* Conversion operators - see next section * Conversion operators - see next section
That list might be incomplete: `""` operator was introduced in C++ 11 (cf. [this blog post](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)), and `<=>` operator was introduced in C++ 20, for instance. That list might be incomplete: `""` operator was introduced in C++ 11 (cf. [this blog post](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)), and `<=>` operator was introduced in C++ 20, for instance.
If not defined, some of them exist by default: If not defined, some of them exist by default:
``` ```
= =
-> ->* -> ->*
new delete new delete
``` ```
Some can never be redefined: Some can never be redefined:
``` ```
: :: . .* ? ?: sizeof : :: . .* ? ?: sizeof
``` ```
## Conversion operators ## Conversion operators
A conversion operator is a method of transforming an object into a given type. When the compiler needs to force the type of an object, implicitly or explicitly, it is this operator that will be called. A conversion operator is required for each type of conversion. A conversion operator is a method of transforming an object into a given type. When the compiler needs to force the type of an object, implicitly or explicitly, it is this operator that will be called. A conversion operator is required for each type of conversion.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
class Rational class Rational
{ {
public : public :
Rational(int numerator, int denominator); Rational(int numerator, int denominator);
operator int() const; operator int() const;
operator double() const; operator double() const;
private : private :
int numerator_ { }; int numerator_ { };
int denominator_ { }; int denominator_ { };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_(numerator), : numerator_(numerator),
denominator_(denominator) denominator_(denominator)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Rational::operator int() const Rational::operator int() const
{ {
return numerator_ / denominator_; return numerator_ / denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Rational::operator double() const Rational::operator double() const
{ {
return static_cast<double>(numerator_) / denominator_; return static_cast<double>(numerator_) / denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
Rational val_r(15, 7); Rational val_r(15, 7);
std::cout << "val as double: " << static_cast<double>(val_r) << std::endl ; std::cout << "val as double: " << static_cast<double>(val_r) << std::endl ;
std::cout << "val as integer: " << static_cast<int>(val_r) << std::endl ; std::cout << "val as integer: " << static_cast<int>(val_r) << std::endl ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As for constructors, you should add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs: As for constructors, you should add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
// Won't run in the notebook, as it's not declared within the class declaration // Won't run in the notebook, as it's not declared within the class declaration
explicit operator int() const; explicit operator int() const;
explicit operator double() const; explicit operator double() const;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Comparison operators](./2-Comparison.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Comparison operators](./2-Comparison.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator <, >, <=, >= ## Operator <, >, <=, >=
These 4 operators are **not** implicitly defined for a class (and that is very natural: what would be the rationale to devise the default `<` operator for a `ComplexNumber` or a `Car` class?) These 4 operators are **not** implicitly defined for a class (and that is very natural: what would be the rationale to devise the default `<` operator for a `ComplexNumber` or a `Car` class?)
Let's take again our `Rational` class to illustrate this: Let's take again our `Rational` class to illustrate this:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
class Rational class Rational
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational(int numerator, int denominator);
explicit operator double() const; explicit operator double() const;
private : private :
int numerator_ {}; int numerator_ {};
int denominator_ {}; int denominator_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_{numerator}, : numerator_{numerator},
denominator_{denominator} denominator_{denominator}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Rational::operator double() const Rational::operator double() const
{ {
return static_cast<double>(numerator_) / denominator_; return static_cast<double>(numerator_) / denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator<(const Rational& lhs, const Rational& rhs) bool operator<(const Rational& lhs, const Rational& rhs)
{ {
return static_cast<double>(lhs) < static_cast<double>(rhs); return static_cast<double>(lhs) < static_cast<double>(rhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Rational r1(15, 7); Rational r1(15, 7);
Rational r2(27, 4); Rational r2(27, 4);
std::cout << "Is r1 lower than r2? -> " << std::boolalpha << (r1 < r2) << std::endl; std::cout << "Is r1 lower than r2? -> " << std::boolalpha << (r1 < r2) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Defining `operator<` does not automatically define the others, but a **good practice** is to define the other comparison operators once `operator<` and `operator!=` are defined, and in function of these two as follows: Defining `operator<` does not automatically define the others, but a **good practice** is to define the other comparison operators once `operator<` and `operator!=` are defined, and in function of these two as follows:
* `operator>(lhs, rhs)` is `operator<(rhs, lhs)` * `operator>(lhs, rhs)` is `operator<(rhs, lhs)`
* `operator>=(lhs, rhs)` is `!operator<(lhs, rhs)` * `operator>=(lhs, rhs)` is `!operator<(lhs, rhs)`
* `operator<=(lhs, rhs)` is `!operator>(lhs, rhs)` * `operator<=(lhs, rhs)` is `!operator>(lhs, rhs)`
As always with operators overloading, make sure your implementation is consistent: it's not because you are allowed by the language to define an operator `<` which is not the negation of `>=` that you should do it! As always with operators overloading, make sure your implementation is consistent: it's not because you are allowed by the language to define an operator `<` which is not the negation of `>=` that you should do it!
## Operator == and != ## Operator == and !=
* None is defined by default. * None is defined by default.
* They are independent from each other: defining one **doesn't** define the other one... * They are independent from each other: defining one **doesn't** define the other one...
* ... but **never** define `operator!=` as something other than `!(operator==)` * ... but **never** define `operator!=` as something other than `!(operator==)`
* As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though! * As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though!
* Make sure you're thought well the result of your comparison: * Make sure you're thought well the result of your comparison:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class Vector class Vector
{ {
public: public:
Vector(int x, int y, int z); Vector(int x, int y, int z);
~Vector(); ~Vector();
friend bool operator==(const Vector& lhs, const Vector& rhs); friend bool operator==(const Vector& lhs, const Vector& rhs);
private: private:
int* array_ { nullptr }; int* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector::Vector(int x, int y, int z) Vector::Vector(int x, int y, int z)
{ {
array_ = new int[3]; array_ = new int[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector::~Vector() Vector::~Vector()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator==(const Vector& lhs, const Vector& rhs) bool operator==(const Vector& lhs, const Vector& rhs)
{ {
return lhs.array_ == rhs.array_; // BUG! return lhs.array_ == rhs.array_; // BUG!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
Vector v1(3, 4, 5); Vector v1(3, 4, 5);
Vector v2(3, 4, 5); Vector v2(3, 4, 5);
std::cout << "Are equal? ('true' expected): " << (v1 == v2) << std::endl; std::cout << "Are equal? ('true' expected): " << (v1 == v2) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The issue here is that we compare the `array_` pointers of `v1` and `v2`; the content might be the same but the address in memory is clearly not! So make sure in overloading these operators you are really comparing what you think you are comparing (and remember unit tests are your friends for this kind of checks!). The issue here is that we compare the `array_` pointers of `v1` and `v2`; the content might be the same but the address in memory is clearly not! So make sure in overloading these operators you are really comparing what you think you are comparing (and remember unit tests are your friends for this kind of checks!).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## C++ 20 refinements ## C++ 20 refinements
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Spaceship operator ### Spaceship operator
In C++ 17 and below, you have to define all the comparison operators, which can quickly become rather tedious (and error-prone): In C++ 17 and below, you have to define all the comparison operators, which can quickly become rather tedious (and error-prone):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
class Rational class Rational20
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational20(int numerator, int denominator);
explicit operator double() const; explicit operator double() const;
private : private :
int numerator_ {}; int numerator_ {};
int denominator_ {}; int denominator_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
Rational::Rational(int numerator, int denominator) Rational20::Rational20(int numerator, int denominator)
: numerator_{numerator}, : numerator_{numerator},
denominator_{denominator} denominator_{denominator}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Rational::operator double() const Rational20::operator double() const
{ {
return static_cast<double>(numerator_) / static_cast<double>(denominator_); return static_cast<double>(numerator_) / static_cast<double>(denominator_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator<(const Rational& lhs, const Rational& rhs) bool operator<(const Rational20& lhs, const Rational20& rhs)
{ {
return static_cast<double>(lhs) < static_cast<double>(rhs); return static_cast<double>(lhs) < static_cast<double>(rhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator==(const Rational& lhs, const Rational& rhs) bool operator==(const Rational20& lhs, const Rational20& rhs)
{ {
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs) ) < 1.e-16; // crude... return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs) ) < 1.e-16; // crude...
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator!=(const Rational& lhs, const Rational& rhs) bool operator!=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator==(lhs, rhs); return !operator==(lhs, rhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator>(const Rational& lhs, const Rational& rhs) bool operator>(const Rational20& lhs, const Rational20& rhs)
{ {
return operator<(rhs, lhs); return operator<(rhs, lhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator>=(const Rational& lhs, const Rational& rhs) bool operator>=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator<(lhs, rhs); return !operator<(lhs, rhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator<=(const Rational& lhs, const Rational& rhs) bool operator<=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator>(lhs, rhs); return !operator>(lhs, rhs);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Rational a(5, 2); Rational20 a(5, 2);
Rational b(17, 5); Rational20 b(17, 5);
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl; std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl; std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl; std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl; std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl; std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl; std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators: C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel - // We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel -
// doesn't support yet `std::partial_ordering`. // doesn't support yet `std::partial_ordering`.
#include <cmath> #include <cmath>
#include <compare> #include <compare>
#include <iostream> #include <iostream>
// =========================== // ===========================
// Declarations // Declarations
// =========================== // ===========================
class Rational20 class Rational20
{ {
public : public :
explicit Rational20(int numerator, int denominator); explicit Rational20(int numerator, int denominator);
explicit operator double() const; explicit operator double() const;
private : private :
int numerator_ {}; int numerator_ {};
int denominator_ {}; int denominator_ {};
}; };
bool operator==(const Rational20& lhs, const Rational20& rhs); bool operator==(const Rational20& lhs, const Rational20& rhs);
std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs); std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs);
// =========================== // ===========================
// Definitions // Definitions
// =========================== // ===========================
Rational20::Rational20(int numerator, int denominator) Rational20::Rational20(int numerator, int denominator)
: numerator_{numerator}, : numerator_{numerator},
denominator_{denominator} denominator_{denominator}
{ } { }
Rational20::operator double() const Rational20::operator double() const
{ {
return static_cast<double>(numerator_) / static_cast<double>(denominator_); return static_cast<double>(numerator_) / static_cast<double>(denominator_);
} }
bool operator==(const Rational20& lhs, const Rational20& rhs) bool operator==(const Rational20& lhs, const Rational20& rhs)
{ {
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs)) < 1.e-16; // crude... return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs)) < 1.e-16; // crude...
} }
std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs) std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs)
{ {
return static_cast<double>(lhs) <=> static_cast<double>(rhs); return static_cast<double>(lhs) <=> static_cast<double>(rhs);
} }
// =========================== // ===========================
// Main // Main
// =========================== // ===========================
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
Rational20 a(5, 2); Rational20 a(5, 2);
Rational20 b(17, 5); Rational20 b(17, 5);
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl; std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl; std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl; std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl; std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl; std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl; std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Default behaviour ### Default behaviour
In C++ 17 and below, you also can't define a default behaviour for the comparison operators for your class. In C++ 17 and below, you also can't define a default behaviour for the comparison operators for your class.
Sometimes it really is appropriate - for the case of `Rational` the default behaviour would probably be something inappropriate, but in other cases this lack of default behaviour may be irksome when you for instance want to check all data attributes are equal: Sometimes it really is appropriate - for the case of `Rational` the default behaviour would probably be something inappropriate, but in other cases this lack of default behaviour may be irksome when you for instance want to check all data attributes are equal:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Toolbox struct Toolbox
{ {
unsigned int Nscrewdriver_; unsigned int Nscrewdriver_;
unsigned int Nhammer_; unsigned int Nhammer_;
unsigned int Nnails_; unsigned int Nnails_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
bool operator==(const Toolbox& lhs, const Toolbox& rhs) bool operator==(const Toolbox& lhs, const Toolbox& rhs)
{ {
return lhs.Nscrewdriver_ == rhs.Nscrewdriver_ return lhs.Nscrewdriver_ == rhs.Nscrewdriver_
&& lhs.Nhammer_ == rhs.Nhammer_ && lhs.Nhammer_ == rhs.Nhammer_
&& lhs.Nnails_ == rhs.Nnails_; && lhs.Nnails_ == rhs.Nnails_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well! This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well!
C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead: C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel - // We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel -
// doesn't support yet properly spaceship operator. // doesn't support yet properly spaceship operator.
#include <iostream> #include <iostream>
struct AutomatedToolbox struct AutomatedToolbox
{ {
bool operator==(const AutomatedToolbox&) const = default; bool operator==(const AutomatedToolbox&) const = default;
auto operator<=>(const AutomatedToolbox&) const = default; auto operator<=>(const AutomatedToolbox&) const = default;
unsigned int Nscrewdriver_; unsigned int Nscrewdriver_;
unsigned int Nhammer_; unsigned int Nhammer_;
unsigned int Nnails_; unsigned int Nnails_;
}; };
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
AutomatedToolbox toolbox1; AutomatedToolbox toolbox1;
toolbox1.Nscrewdriver_ = 5; toolbox1.Nscrewdriver_ = 5;
toolbox1.Nhammer_ = 4; toolbox1.Nhammer_ = 4;
toolbox1.Nnails_ = 200; toolbox1.Nnails_ = 200;
AutomatedToolbox toolbox2; AutomatedToolbox toolbox2;
toolbox2.Nscrewdriver_ = 5; toolbox2.Nscrewdriver_ = 5;
toolbox2.Nhammer_ = 4; toolbox2.Nhammer_ = 4;
toolbox2.Nnails_ = 200; toolbox2.Nnails_ = 200;
std::cout << std::boolalpha << (toolbox1 == toolbox2) << std::endl; std::cout << std::boolalpha << (toolbox1 == toolbox2) << std::endl;
std::cout << std::boolalpha << (toolbox1 < toolbox2) << std::endl; std::cout << std::boolalpha << (toolbox1 < toolbox2) << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you can experiment, the `operator<` will compare data attributes in order of their declaration as if it was implemented this way: As you can experiment, the `operator<` will compare data attributes in order of their declaration as if it was implemented this way:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
// What the operator< would look like if we wrote it ourselves: // What the operator< would look like if we wrote it ourselves:
bool operator<(const Toolbox& lhs, const Toolbox& rhs) bool operator<(const Toolbox& lhs, const Toolbox& rhs)
{ {
if (lhs.Nscrewdriver_ != rhs.Nscrewdriver_) if (lhs.Nscrewdriver_ != rhs.Nscrewdriver_)
return lhs.Nscrewdriver_ < rhs.Nscrewdriver_; return lhs.Nscrewdriver_ < rhs.Nscrewdriver_;
if (lhs.Nhammer_ != rhs.Nhammer_) if (lhs.Nhammer_ != rhs.Nhammer_)
return lhs.Nhammer_ < rhs.Nhammer_; return lhs.Nhammer_ < rhs.Nhammer_;
if (lhs.Nnails_ != rhs.Nnails_) if (lhs.Nnails_ != rhs.Nnails_)
return lhs.Nnails_ < rhs.Nnails_; return lhs.Nnails_ < rhs.Nnails_;
return false; return false;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[This example](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator), or [that example](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) show other defaulting of a comparison used directly upon the spaceship operator. [This example](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator), or [that example](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) show other defaulting of a comparison used directly upon the spaceship operator.
**Be aware** the default implementation may not be what you expected (see the `Rational` example, where even the default `operator==` is unlikely to be what you want). **Be aware** the default implementation may not be what you expected (see the `Rational` example, where even the default `operator==` is unlikely to be what you want).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Stream operators](./3-Stream.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Stream operators](./3-Stream.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator << ## Operator <<
So far, we have very often defined methods and/or functions named `Print()` to provide a visual printing of the content of a class. So far, we have very often defined methods and/or functions named `Print()` to provide a visual printing of the content of a class.
However, it's even better if we could use the `<<` syntax directly, and the way to do so is to provide an overload of `operator<<`. However, it's even better if we could use the `<<` syntax directly, and the way to do so is to provide an overload of `operator<<`.
The recommended way to implement it is to use under the hood a `Print(std::ostream&)` (or whatever you want to call it): The recommended way to implement it is to use under the hood a `Print(std::ostream&)` (or whatever you want to call it):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
class Rational class Rational
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational(int numerator, int denominator);
// Might even be private; in which case friendship needs to be declared // Might even be private; in which case friendship needs to be declared
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private : private :
int numerator_ {}; int numerator_ {};
int denominator_ {}; int denominator_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_{numerator}, : numerator_{numerator},
denominator_{denominator} denominator_{denominator}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Rational::Print(std::ostream& out) const void Rational::Print(std::ostream& out) const
{ {
out << numerator_ << " / " << denominator_; out << numerator_ << " / " << denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::ostream& operator<<(std::ostream& out, const Rational& r) std::ostream& operator<<(std::ostream& out, const Rational& r)
{ {
r.Print(out); // see how the bulk of the work is done by the 'Print()' method! r.Print(out); // see how the bulk of the work is done by the 'Print()' method!
return out; return out;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Rational r1(22, 7); Rational r1(22, 7);
Rational r2(77, 17); Rational r2(77, 17);
std::cout << "Rational = " << r1 << std::endl; std::cout << "Rational = " << r1 << std::endl;
std::cout << "Call may also be chained due to the signature of the function: " << r1 std::cout << "Call may also be chained due to the signature of the function: " << r1
<< " and " << r2 << std::endl; << " and " << r2 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the slightly weird expected syntax for the `operator<<`: the `std::ostream` appears actually twice; the reason for that is to enable chained calls: Please notice the slightly weird expected syntax for the `operator<<`: the `std::ostream` appears actually twice; the reason for that is to enable chained calls:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Rational r1(22, 7); Rational r1(22, 7);
Rational r2(84, 9); Rational r2(84, 9);
std::cout << "Rationals = " << r1 << " and " << r2 << std::endl; std::cout << "Rationals = " << r1 << " and " << r2 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator >> ## Operator >>
`operator>>` might be overloaded in a similar way; it might be used for instance to create an object from data read in a file. As usual with `operator>>`, you should be cautious and handle well the case in which the flux becomes invalid: `operator>>` might be overloaded in a similar way; it might be used for instance to create an object from data read in a file. As usual with `operator>>`, you should be cautious and handle well the case in which the flux becomes invalid:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox std::cin/clang %%cppmagics std::cin/clang
#include <iostream> #include <iostream>
class Rational class Rational
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational(int numerator, int denominator);
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
friend std::istream& operator>>(std::istream& in, Rational& r); friend std::istream& operator>>(std::istream& in, Rational& r);
private: private:
//! Set method to modify numerator and denominator. I made the arbitrary choice to make it private //! Set method to modify numerator and denominator. I made the arbitrary choice to make it private
//! to illustrate friendship. //! to illustrate friendship.
void Set(int numerator, int denominator); void Set(int numerator, int denominator);
private : private :
int numerator_ {}; int numerator_ {};
int denominator_ {}; int denominator_ {};
}; };
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_{numerator}, : numerator_{numerator},
denominator_{denominator} denominator_{denominator}
{ } { }
void Rational::Print(std::ostream& out) const void Rational::Print(std::ostream& out) const
{ {
out << numerator_ << " / " << denominator_; out << numerator_ << " / " << denominator_;
} }
std::ostream& operator<<(std::ostream& out, const Rational& r) std::ostream& operator<<(std::ostream& out, const Rational& r)
{ {
r.Print(out); r.Print(out);
return out; return out;
} }
void Rational::Set(int numerator, int denominator) void Rational::Set(int numerator, int denominator)
{ {
numerator_ = numerator; numerator_ = numerator;
denominator_ = denominator; denominator_ = denominator;
} }
std::istream& operator>>(std::istream& in, Rational& r) std::istream& operator>>(std::istream& in, Rational& r)
{ {
int numerator {}, denominator {}; int numerator {}, denominator {};
in >> numerator >> denominator; // Notice the operator>> chained call in >> numerator >> denominator; // Notice the operator>> chained call
if (!in) if (!in)
{ {
// If istream is bad; do not modify 'r' // If istream is bad; do not modify 'r'
return in; return in;
} }
r.Set(numerator, denominator); // ok due to friendship! r.Set(numerator, denominator); // ok due to friendship!
return in; return in;
} }
int main() int main()
{ {
Rational r1(0,0); Rational r1(0,0);
std::cout << "Enter a rational by separating calls by a space" << std::endl; std::cout << "Enter a rational by separating calls by a space" << std::endl;
std::cin >> r1; std::cin >> r1;
if (std::cin) if (std::cin)
std::cout << "Value read is " << r1 << std::endl; std::cout << "Value read is " << r1 << std::endl;
else else
std::cerr << "Invalid input!" << std::endl; std::cerr << "Invalid input!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This code is far from perfect: it handles correctly the case gibberish is given through `std::cin`, but if you put more than two blocks separated by spaces the first two ones are used and the others aren't dealt with... As we have already said, proper and complete handling of input stream is though! This code is far from perfect: it handles correctly the case gibberish is given through `std::cin`, but if you put more than two blocks separated by spaces the first two ones are used and the others aren't dealt with... As we have already said, proper and complete handling of input stream is though!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Assignment operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb) # [Getting started in C++](./) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Assignment operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Assignment operator ## Assignment operator
We have not yet addressed one of the most natural operator: the one that might be used to allocate a value to an object. We have not yet addressed one of the most natural operator: the one that might be used to allocate a value to an object.
### Default behaviour (for a simple case) ### Default behaviour (for a simple case)
This one is provided by default: This one is provided by default:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
class Vector class Vector
{ {
public: public:
Vector(double x, double y, double z); Vector(double x, double y, double z);
Vector& operator=(const Vector&) = default; Vector& operator=(const Vector&) = default;
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double x_ {}; double x_ {};
double y_ {}; double y_ {};
double z_ {}; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_{x}, : x_{x},
y_{y}, y_{y},
z_{z} z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Vector::Print(std::ostream& out) const void Vector::Print(std::ostream& out) const
{ {
out << "(" << x_ << ", " << y_ << ", " << z_ << ")"; out << "(" << x_ << ", " << y_ << ", " << z_ << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Vector v1(3., 5., 7.); Vector v1(3., 5., 7.);
Vector v2(-4., -16., 0.); Vector v2(-4., -16., 0.);
v2 = v1; v2 = v1;
v2.Print(std::cout); v2.Print(std::cout);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The pointer case ### The pointer case
So end of the story? Not exactly... Let's write the same and store the values in a dynamic array instead (of course you shouldn't do that in a real case: `std::vector` or `std::array` would avoid the hassle below!): So end of the story? Not exactly... Let's write the same and store the values in a dynamic array instead (of course you shouldn't do that in a real case: `std::vector` or `std::array` would avoid the hassle below!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
class Vector2 class Vector2
{ {
public: public:
Vector2(double x, double y, double z); Vector2(double x, double y, double z);
~Vector2(); ~Vector2();
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double* array_ { nullptr }; double* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector2::Vector2(double x, double y, double z) Vector2::Vector2(double x, double y, double z)
{ {
array_ = new double[3]; array_ = new double[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector2::~Vector2() Vector2::~Vector2()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Vector2::Print(std::ostream& out) const void Vector2::Print(std::ostream& out) const
{ {
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")"; out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
// Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete` // Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete`
Vector2* v1 = new Vector2(3., 5., 7.); Vector2* v1 = new Vector2(3., 5., 7.);
Vector2* v2 = new Vector2(-4., -16., 0.); Vector2* v2 = new Vector2(-4., -16., 0.);
v2 = v1; v2 = v1;
std::cout << "Delete v1 @ " << std::hex << v1 << std::endl; std::cout << "Delete v1 @ " << std::hex << v1 << std::endl;
delete v1; delete v1;
std::cout << "Delete v2 @ " << std::hex << v2 << std::endl; std::cout << "Delete v2 @ " << std::hex << v2 << std::endl;
delete v2; // WARNING: Comment this line for not kernel to crash delete v2; // WARNING: Comment this line for not kernel to crash
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
At the time of this writing, this makes the kernel crash... In a more realistic environment (below when the code is compiled with clang and executed, but you can play with [Wandbox](https://wandbox.org) if you want to see how the different compilers handle it) the reason appears more clearly: At the time of this writing, this makes the kernel crash... In a more realistic environment (below when the code is compiled with clang and executed, but you can play with [Wandbox](https://wandbox.org) if you want to see how the different compilers handle it) the reason appears more clearly:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Exact same code as abovce, but given to your clang++ compiler to be compiled and executed // Exact same code as abovce, but given to your clang++ compiler to be compiled and executed
#include <iostream> #include <iostream>
class Vector2 class Vector2
{ {
public: public:
Vector2(double x, double y, double z); Vector2(double x, double y, double z);
~Vector2(); ~Vector2();
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double* array_ { nullptr }; double* array_ { nullptr };
}; };
Vector2::Vector2(double x, double y, double z) Vector2::Vector2(double x, double y, double z)
{ {
array_ = new double[3]; array_ = new double[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
Vector2::~Vector2() Vector2::~Vector2()
{ {
delete[] array_; delete[] array_;
} }
void Vector2::Print(std::ostream& out) const void Vector2::Print(std::ostream& out) const
{ {
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")"; out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
// Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete` // Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete`
Vector2* v1 = new Vector2(3., 5., 7.); Vector2* v1 = new Vector2(3., 5., 7.);
Vector2* v2 = new Vector2(-4., -16., 0.); Vector2* v2 = new Vector2(-4., -16., 0.);
v2 = v1; v2 = v1;
std::cout << "Delete v1 @ " << std::hex << v1 << std::endl; std::cout << "Delete v1 @ " << std::hex << v1 << std::endl;
delete v1; delete v1;
std::cout << "Delete v2 @ " << std::hex << v2 << std::endl; std::cout << "Delete v2 @ " << std::hex << v2 << std::endl;
delete v2; delete v2;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice. So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice.
One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!): One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
class Vector3 class Vector3
{ {
public: public:
Vector3(double x, double y, double z); Vector3(double x, double y, double z);
~Vector3(); ~Vector3();
Vector3& operator=(const Vector3& rhs); Vector3& operator=(const Vector3& rhs);
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double* array_ { nullptr }; double* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector3::Vector3(double x, double y, double z) Vector3::Vector3(double x, double y, double z)
{ {
array_ = new double[3]; array_ = new double[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector3::~Vector3() Vector3::~Vector3()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void Vector3::Print(std::ostream& out) const void Vector3::Print(std::ostream& out) const
{ {
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")"; out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Vector3& Vector3::operator=(const Vector3& rhs) Vector3& Vector3::operator=(const Vector3& rhs)
{ {
// Array already initialized in constructor; just change its content. // Array already initialized in constructor; just change its content.
for (auto i = 0ul; i < 3ul; ++i) for (auto i = 0ul; i < 3ul; ++i)
array_[i] = rhs.array_[i]; array_[i] = rhs.array_[i];
return *this; // The (logical) return value for such a method. return *this; // The (logical) return value for such a method.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Vector3 v1(3., 5., 7.); Vector3 v1(3., 5., 7.);
Vector3 v2(-4., -16., 0.); Vector3 v2(-4., -16., 0.);
v2 = v1; v2 = v1;
v1.Print(std::cout); v1.Print(std::cout);
std::cout << std::endl; std::cout << std::endl;
v2.Print(std::cout); v2.Print(std::cout);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Uncopyable class ### Uncopyable class
In fact when we said by default an assignment operator is made available for the class, we were overly simplifying the issue. Let's consider for instance a class with a reference data attribute: In fact when we said by default an assignment operator is made available for the class, we were overly simplifying the issue. Let's consider for instance a class with a reference data attribute:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class ClassWithRef class ClassWithRef
{ {
public: public:
ClassWithRef(int& index); ClassWithRef(int& index);
private: private:
int& index_; int& index_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
ClassWithRef::ClassWithRef(int& index) ClassWithRef::ClassWithRef(int& index)
: index_(index) : index_(index)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a = 5; int a = 5;
ClassWithRef obj(a); ClassWithRef obj(a);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a = 5; int a = 5;
int b = 7; int b = 7;
ClassWithRef obj1(a); ClassWithRef obj1(a);
ClassWithRef obj2(b); ClassWithRef obj2(b);
obj2 = obj1; // COMPILATION ERROR obj2 = obj1; // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A class with a reference can't be copied: the reference is to be set at construction, and be left unchanged later. So a copy is out of the question, hence the compilation error. A class with a reference can't be copied: the reference is to be set at construction, and be left unchanged later. So a copy is out of the question, hence the compilation error.
The same is true as well for a class with an uncopyable data attribute: the class is then automatically uncopyable as well. The same is true as well for a class with an uncopyable data attribute: the class is then automatically uncopyable as well.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Copy constructor ### Copy constructor
Please notice however that it is still possible to allocate a new object which would be a copy of another one with a **copy constructor**. The syntax is given below: Please notice however that it is still possible to allocate a new object which would be a copy of another one with a **copy constructor**. The syntax is given below:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class ClassWithRef2 class ClassWithRef2
{ {
public: public:
ClassWithRef2(int& index); ClassWithRef2(int& index);
ClassWithRef2(const ClassWithRef2& ) = default; ClassWithRef2(const ClassWithRef2& ) = default;
private: private:
int& index_; int& index_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
ClassWithRef2::ClassWithRef2(int& index) ClassWithRef2::ClassWithRef2(int& index)
: index_(index) : index_(index)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
int a = 5; int a = 5;
ClassWithRef2 obj1(a); ClassWithRef2 obj1(a);
ClassWithRef2 obj2(obj1); // ok ClassWithRef2 obj2(obj1); // ok
ClassWithRef2 obj3 {obj1}; // ok ClassWithRef2 obj3 {obj1}; // ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are effectively two ways to copy an object: There are effectively two ways to copy an object:
* With an assignment operator. * With an assignment operator.
* With a copy constructor. * With a copy constructor.
It is NOT the same underlying method which is called under the hood, you might have a different behaviour depending on which one is called or not! It is NOT the same underlying method which is called under the hood, you might have a different behaviour depending on which one is called or not!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The dangers of copy constructions... and how I (Sébastien) avoid them ### The dangers of copy constructions... and how I (Sébastien) avoid them
Copy construction may in fact be quite dangerous: Copy construction may in fact be quite dangerous:
* As we've just seen, assignment and construction may differ in implementation, which is not a good thing. There are ways to define one in function of the other (see for instance item 11 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)) but they aren't that trivial. * As we've just seen, assignment and construction may differ in implementation, which is not a good thing. There are ways to define one in function of the other (see for instance item 11 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)) but they aren't that trivial.
* Depending on somewhat complicated rules that have evolved with standard versions, some might or might not be defined implicitly. * Depending on somewhat complicated rules that have evolved with standard versions, some might or might not be defined implicitly.
* More importantly, assignment operators may be a nightmare to maintain. Imagine you have a class for which you overload manually the assignment operator and/or the copy constructor. If later you add a new data attribute, you have to make sure not to forget to add it in both implementations; if you forget once you will enter the realm of undefined behaviour... and good luck for you to find the origin of the bug! * More importantly, assignment operators may be a nightmare to maintain. Imagine you have a class for which you overload manually the assignment operator and/or the copy constructor. If later you add a new data attribute, you have to make sure not to forget to add it in both implementations; if you forget once you will enter the realm of undefined behaviour... and good luck for you to find the origin of the bug!
To avoid that I took the extreme rule to (almost) never overload those myself: To avoid that I took the extreme rule to (almost) never overload those myself:
* As explained briefly above, using appropriate container may remove the need. `Vector3` could be written with a `std::array` instead of the dynamic array, and the STL object will be properly copied with default behaviour! * As explained briefly above, using appropriate container may remove the need. `Vector3` could be written with a `std::array` instead of the dynamic array, and the STL object will be properly copied with default behaviour!
* I define explicitly in my classes the behaviour of these operators with `= default` or `= delete` syntaxes. More often than not, my objects have no business being copied and `= delete` is really my default choice (this keyword indicates to the compiler the operator should not be provided for the class). * I define explicitly in my classes the behaviour of these operators with `= default` or `= delete` syntaxes. More often than not, my objects have no business being copied and `= delete` is really my default choice (this keyword indicates to the compiler the operator should not be provided for the class).
I don't pretend it is the universal choice, just my way to avoid the potential issues with manually overloaded copy operators (some would say the [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) would avoid the most problematic one, but in the real world I find it difficult to stick with this principle...) I don't pretend it is the universal choice, just my way to avoid the potential issues with manually overloaded copy operators (some would say the [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) would avoid the most problematic one, but in the real world I find it difficult to stick with this principle...)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Canonical form of a class ## Canonical form of a class
So a typical class of mine looks like: So a typical class of mine looks like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class AlmostCanonicalClass class AlmostCanonicalClass
{ {
public: // or even protected or private for some of them! public: // or even protected or private for some of them!
//! Constructor. //! Constructor.
AlmostCanonicalClass(...); AlmostCanonicalClass(...);
//! Destructor. //! Destructor.
~AlmostCanonicalClass() = default; ~AlmostCanonicalClass() = default;
//! Disable copy constructor. //! Disable copy constructor.
AlmostCanonicalClass(const AlmostCanonicalClass& ) = delete; AlmostCanonicalClass(const AlmostCanonicalClass& ) = delete;
//! Disable copy assignment. //! Disable copy assignment.
AlmostCanonicalClass& operator=(const AlmostCanonicalClass& ) = delete; AlmostCanonicalClass& operator=(const AlmostCanonicalClass& ) = delete;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Your IDE or a script might even be handy to generate this by default (with additional Doxygen comments for good measure). Your IDE or a script might even be handy to generate this by default (with additional Doxygen comments for good measure).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [Advanced] The true canonical class ### [Advanced] The true canonical class
Why _almost_ canonical class? Because C++ 11 introduced a very powerful **move semantics** (see the [notebook](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) about it) and so the true canonical class is: Why _almost_ canonical class? Because C++ 11 introduced a very powerful **move semantics** (see the [notebook](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) about it) and so the true canonical class is:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class TrueCanonicalClass class TrueCanonicalClass
{ {
public: // or even protected or private for some of them! public: // or even protected or private for some of them!
//! Constructor. //! Constructor.
TrueCanonicalClass(...); TrueCanonicalClass(...);
//! Destructor. //! Destructor.
~TrueCanonicalClass() = default; ~TrueCanonicalClass() = default;
//! Disable copy constructor. //! Disable copy constructor.
TrueCanonicalClass(const TrueCanonicalClass& ) = delete; TrueCanonicalClass(const TrueCanonicalClass& ) = delete;
//! Disable copy assignment. //! Disable copy assignment.
TrueCanonicalClass& operator=(const TrueCanonicalClass& ) = delete; TrueCanonicalClass& operator=(const TrueCanonicalClass& ) = delete;
//! Disable move constructor. //! Disable move constructor.
TrueCanonicalClass(TrueCanonicalClass&& ) = delete; TrueCanonicalClass(TrueCanonicalClass&& ) = delete;
//! Disable move assignment. //! Disable move assignment.
TrueCanonicalClass& operator=(TrueCanonicalClass&& ) = delete; TrueCanonicalClass& operator=(TrueCanonicalClass&& ) = delete;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In my programs, I like to declare explicitly all of them, using `default` and `delete` to provide automatic implementation for most of them. In my programs, I like to declare explicitly all of them, using `default` and `delete` to provide automatic implementation for most of them.
I admit it's a bit of boilerplate (and to be honest a script does the job for me in my project...), if you don't want to there are in fact rules that specify which of them you need to define: for instance if a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three. See [this cppreference link](https://en.cppreference.com/w/cpp/language/rule_of_three) for more about these rules and [this blog post](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/) for an opposite point of view. I admit it's a bit of boilerplate (and to be honest a script does the job for me in my project...), if you don't want to there are in fact rules that specify which of them you need to define: for instance if a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three. See [this cppreference link](https://en.cppreference.com/w/cpp/language/rule_of_three) for more about these rules and [this blog post](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/) for an opposite point of view.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## What is a functor? ## What is a functor?
### Disclaimer: not a functional programming functor! ### Disclaimer: not a functional programming functor!
First of call, for those of you with a background in functional programming, what C++ calls a `functor` is [not what you are used to](https://en.wikipedia.org/wiki/Functor_(functional_programming)). First of call, for those of you with a background in functional programming, what C++ calls a `functor` is [not what you are used to](https://en.wikipedia.org/wiki/Functor_(functional_programming)).
### Functor in C++ ### Functor in C++
We can provide a class with an `operator()` that allows to give its objects a function behavior. In other words: if you write an expression in which an object is used as if it were a function, it is its execution operator that is invoked. We can provide a class with an `operator()` that allows to give its objects a function behavior. In other words: if you write an expression in which an object is used as if it were a function, it is its execution operator that is invoked.
We can also see these *functional objects*, or *functors*, as functions to which we would have added state parameters from one call to another. We can also see these *functional objects*, or *functors*, as functions to which we would have added state parameters from one call to another.
The return type and the arguments are not constrained and might be defined as you wish in the class (`int` is used here for return type, and an `int` is required as argument): The return type and the arguments are not constrained and might be defined as you wish in the class (`int` is used here for return type, and an `int` is required as argument):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
class LinearFunction class LinearFunction
{ {
public : public :
LinearFunction(int constant); LinearFunction(int constant);
int operator()(int value) int operator()(int value) const;
{
return constant_ * value; // here due to usual Xeus-cling issue when out of class
// definition is used for operators
}
private : private :
int constant_ {}; int constant_ {};
} ; } ;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef int LinearFunction::operator()(int value) const
{
return constant_ * value;
}
```
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
LinearFunction::LinearFunction(int constant) LinearFunction::LinearFunction(int constant)
: constant_{constant} : constant_{constant}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int values[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int values[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
LinearFunction Double(2); LinearFunction Double(2);
LinearFunction Triple(3); LinearFunction Triple(3);
for (int value : values) for (int value : values)
std::cout << value << "\t" << Double(value) << "\t" << Triple(value) << std::endl; std::cout << value << "\t" << Double(value) << "\t" << Triple(value) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This might seem inconsequential, but they are sometimes extremely useful. This might seem inconsequential, but they are sometimes extremely useful.
## Functors in STL ## Functors in STL
STL itself defines some functors: imagine you want to sort a `std::vector` decreasingly; you can't directly put `>` as the comparison operation in `sort` and therefore may use a specific STL-defined `greater`: STL itself defines some functors: imagine you want to sort a `std::vector` decreasingly; you can't directly put `>` as the comparison operation in `sort` and therefore may use a specific STL-defined `greater`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
{ {
std::vector<int> values { -980, 12, 2987, -8323, 4, 275, -8936, 27, -988, 92784 }; std::vector<int> values { -980, 12, 2987, -8323, 4, 275, -8936, 27, -988, 92784 };
std::sort(values.begin(), values.end(), std::greater<int>()); std::sort(values.begin(), values.end(), std::greater<int>());
for (auto value : values) for (auto value : values)
std::cout << value << " "; std::cout << value << " ";
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 11 and above limits the need for functors with lambda functions (see [earlier](../1-ProceduralProgramming/4-Functions.ipynb#Lambda-functions)), but in older codes, functors were one of the way to pass your own rules to some STL algorithms, along with pointer to functions. C++ 11 and above limits the need for functors with lambda functions (see [earlier](../1-ProceduralProgramming/4-Functions.ipynb#Lambda-functions)), but in older codes, functors were one of the way to pass your own rules to some STL algorithms, along with pointer to functions.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [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:
## 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](https://en.cppreference.com/w/cpp/algorithm/min)...) The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL [provides it already](https://en.cppreference.com/w/cpp/algorithm/min)...)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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++ ``` c++
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++ ``` c++
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++ ``` c++
#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++ ``` c++
template<class T> template<class T>
T MinWithTemplate(T lhs, T rhs) T MinWithTemplate(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++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << MinWithTemplate<int>(5, -8) << std::endl; std::cout << MinWithTemplate<int>(5, -8) << std::endl;
std::cout << MinWithTemplate(5, -8) << std::endl; std::cout << MinWithTemplate(5, -8) << std::endl;
std::cout << MinWithTemplate(5., -8.) << std::endl; std::cout << MinWithTemplate(5., -8.) << std::endl;
std::cout << MinWithTemplate(5.f, -8.f) << std::endl; std::cout << MinWithTemplate(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 or several of the parameters. In other words, for the following case it won't work: * In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one or several of the parameters. In other words, for the following case it won't work:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
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++ ``` c++
{ {
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++ ``` c++
{ {
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 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 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++ ``` c++
#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++ ``` c++
#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++ ``` c++
#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:
```c++ ```c++
static_assert(std::is_same<T, int>(), "Check T is an integer"); static_assert(std::is_same<T, int>(), "Check T is an integer");
``` ```
is a tad overkill! 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++ ``` c++
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++ ``` c++
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++ ``` c++
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++ ``` c++
#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 template parameters. The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters.
Spot the template type is not defined for the `integer` variable. The type is automatically deduced from the `5` argument value as an `int`. This variable could have been defined as `HoldAValue<int> integer {5};` (cf. [types deduction](../1-ProceduralProgramming/3-Types.ipynb#decltype-and-auto)) Spot the template type is not defined for the `integer` variable. The type is automatically deduced from the `5` argument value as an `int`. This variable could have been defined as `HoldAValue<int> integer {5};` (cf. [types deduction](../1-ProceduralProgramming/3-Types.ipynb#decltype-and-auto))
### 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++ ``` c++
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++ ``` c++
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++ ``` c++
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++ ``` c++
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++ ``` c++
#include <iostream>
{ {
HoldAValue2 hold(9); HoldAValue2 hold(9);
hold.Convert<double>(); const double converted_value = hold.Convert<double>();
std::cout << "Value = " << converted_value << '\n';
} }
``` ```
%% 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++ ``` c++
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++ ``` c++
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++ ``` c++
#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++ ``` c++
{ {
HoldAValue3<int> hold(5); HoldAValue3<int> hold(5);
Print(hold); // LINK ERROR, and that is not something amiss with notebook kernel! Print(hold); // LINK ERROR, and that is not something amiss with notebook kernel!
} }
``` ```
%% 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++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
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++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
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++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
#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++ ``` c++
{ {
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.archive.org/web/20211003194958/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.archive.org/web/20211003194958/https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it.
%% Cell type:markdown id: tags: %% 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++ ``` c++
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++ ``` c++
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 (std::size_t i = 0; 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++ ``` c++
{ {
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++ ``` c++
{ {
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++ ``` c++
{ {
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++ ``` c++
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:
Since C++ 17, there are exactly zero differences between both keywords (before C++ 17, they were almost everywhere equivalent except in a very specific case - called template template parameter that we'll see shortly - in which typename did not work. You can check [this link](https://stackoverflow.com/questions/2023977/what-is-the-difference-between-typename-and-class-template-parameters) if you want to know more); some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions! Since C++ 17, there are exactly zero differences between both keywords (before C++ 17, they were almost everywhere equivalent except in a very specific case - called template template parameter that we'll see shortly - in which typename did not work. You can check [this link](https://stackoverflow.com/questions/2023977/what-is-the-difference-between-typename-and-class-template-parameters) if you want to know more); 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 significantly. * 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 significantly.
* 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:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit. In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit.
## Mandatory `this` in calling base class methods ## Mandatory `this` in calling base class methods
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Base class Base
{ {
public: public:
Base() = default; Base() = default;
void BaseMethod() void BaseMethod()
{ } { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Derived : public Base<T> class Derived : public Base<T>
{ {
public: public:
Derived() = default; Derived() = default;
void DerivedMethod(); void DerivedMethod();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
BaseMethod(); // Compilation error! BaseMethod(); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method. Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method.
To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)): To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
this->BaseMethod(); // Now ok! this->BaseMethod(); // Now ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes). Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Derived2 : public Base<T> class Derived2 : public Base<T>
{ {
public: public:
Derived2() = default; Derived2() = default;
using parent = Base<T>; using parent = Base<T>;
void DerivedMethod() void DerivedMethod()
{ {
parent::BaseMethod(); // also ok! parent::BaseMethod(); // also ok!
} }
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Mandatory `typename` keyword ## Mandatory `typename` keyword
In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section. In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section.
The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`. The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
{ {
std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5! std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, this simple construct fails in the following case: However, this simple construct fails in the following case:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<ContainerT::value_type>(5) << std::endl; std::cout << static_cast<ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_). You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_).
The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue): The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'! PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'!
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintFive<std::vector<int>>(); // Ok! PrintFive<std::vector<int>>(); // Ok!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided. C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter ### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter
Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages: Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// `cling` C++ interpreter used by kernel doesn't support yet C++ 20 concepts. // `cling` C++ interpreter used by kernel doesn't support yet C++ 20 concepts.
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <list> #include <list>
template<typename T> template<typename T>
concept HasValueType = requires(T) concept HasValueType = requires(T)
{ {
typename T::value_type; typename T::value_type;
}; };
template<class ContainerT> template<HasValueType ContainerT>
requires HasValueType<ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
PrintFive<std::vector<int>>(); PrintFive<std::vector<int>>();
PrintFive<std::list<int>>(); PrintFive<std::list<int>>();
PrintFive<int>(); // COMPILATION ERROR! But with a clear error message. PrintFive<int>(); // COMPILATION ERROR! But with a clear error message.
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Mandatory `template` keyword ## Mandatory `template` keyword
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Likewise, compiler sometimes needs help to figure out a template is involved. For instance: Likewise, compiler sometimes needs help to figure out a template is involved. For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Foo struct Foo
{ {
template<int N> template<int N>
void PrintN() const; void PrintN() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<int N> template<int N>
void Foo::PrintN() const void Foo::PrintN() const
{ {
std::cout << N << std::endl; std::cout << N << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.PrintN<0>() << std::endl; std::cout << u.PrintN<0>() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved. Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved.
To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler. To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler.
Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler. Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword! std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: always check obvious template instantiations in test! ## Good practice: always check obvious template instantiations in test!
If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue: If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Foo foo; Foo foo;
Print(foo); // Compilation error! Print(foo); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`: Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`:
* `U` must provide a template `PrintN` method with an integer parameter. * `U` must provide a template `PrintN` method with an integer parameter.
* An overload of operator `<<` must be provided for the return type of this method. * An overload of operator `<<` must be provided for the return type of this method.
`Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands). `Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands).
So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works. So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works.
**Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it. **Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## No virtual template method! ## No virtual template method!
All is in the title here: a method can't be both virtual and template... All is in the title here: a method can't be both virtual and template...
We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue. We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Hints to more advanced concepts with templates](./5-MoreAdvanced.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Hints to more advanced concepts with templates](./5-MoreAdvanced.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We have barely scratched the surface of what can be done with templates; I will here just drop few names and a very brief explanation to allow you to dig deeper if it might seem of interest for your codes (a Google search for either of them will give you plenty of references) and also avoid you frowing upon a seemingly daunting syntax... We have barely scratched the surface of what can be done with templates; I will here just drop few names and a very brief explanation to allow you to dig deeper if it might seem of interest for your codes (a Google search for either of them will give you plenty of references) and also avoid you frowing upon a seemingly daunting syntax...
## Curiously recurrent template pattern (CRTP) ## Curiously recurrent template pattern (CRTP)
One of my own favourite idiom (so much I didn't resist writing an [entry](../7-Appendix/Crtp.ipynb) about it in the appendix). One of my own favourite idiom (so much I didn't resist writing an [entry](../7-Appendix/Crtp.ipynb) about it in the appendix).
The idea behind it is to provide a same set of a given functionality to classes that have otherwise nothing in common. The idea behind it is to provide a same set of a given functionality to classes that have otherwise nothing in common.
The basic example is if you want to assign a unique identifier to a class of yours: the implementation would be exactly the same in each otherwise different class in which you need this: The basic example is if you want to assign a unique identifier to a class of yours: the implementation would be exactly the same in each otherwise different class in which you need this:
* Initializing properly this identifier at construction. * Initializing properly this identifier at construction.
* Check no other objects of the same class use it already. * Check no other objects of the same class use it already.
* Provide an accessor `GetUniqueIdentifier()`. * Provide an accessor `GetUniqueIdentifier()`.
Usual inheritance or composition aren't very appropriate to put in common once and for all (DRY principle!) these functionalities: either they may prove dangerous (inheritance) or be very wordy (composition). Usual inheritance or composition aren't very appropriate to put in common once and for all (DRY principle!) these functionalities: either they may prove dangerous (inheritance) or be very wordy (composition).
The **curiously recurrent template pattern** is a very specific inheritance: The **curiously recurrent template pattern** is a very specific inheritance:
```c++ ```c++
class MyClass : public UniqueIdentifier<MyClass> class MyClass : public UniqueIdentifier<MyClass>
``` ```
where your class inherits from a template class which parameter is... your class itself. where your class inherits from a template class which parameter is... your class itself.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Traits ## Traits
A **trait** is a member of a class which gives exclusively an information about type. For instance let's go back to the `HoldAValue` class we wrote [earlier](./2-Specialization.ipynb) in our template presentation: A **trait** is a member of a class which gives exclusively an information about type. For instance let's go back to the `HoldAValue` class we wrote [earlier](./2-Specialization.ipynb) in our template presentation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T> template<class T>
class HoldAValue class HoldAValue
{ {
public: public:
HoldAValue(T value); HoldAValue(T value);
T GetValue() const; T GetValue() const;
private: private:
T value_; T value_;
}; };
template<class T> template<class T>
HoldAValue<T>::HoldAValue(T value) HoldAValue<T>::HoldAValue(T value)
: value_(value) : value_(value)
{ } { }
template<class T> template<class T>
T HoldAValue<T>::GetValue() const T HoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
HoldAValue<int> hint(5); HoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl; std::cout << hint.GetValue() << std::endl;
HoldAValue<std::string> sint("Hello world!"); HoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl; std::cout << sint.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This class was not especially efficient: the accessor `GetValue()`: This class was not especially efficient: the accessor `GetValue()`:
- Requires `T` is copyable. - Requires `T` is copyable.
- Copy `T`, which is potentially a time-consuming operator. - Copy `T`, which is potentially a time-consuming operator.
We could replace by `const T& GetValue() const` to solve both those issues, but it's a bit on the nose (and less efficient) for plain old data type. The best of both world may be achieved by a trait: We could replace by `const T& GetValue() const` to solve both those issues, but it's a bit on the nose (and less efficient) for plain old data type. The best of both world may be achieved by a trait:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <type_traits> // for std::conditional, std::is_trivial #include <type_traits> // for std::conditional, std::is_trivial
template<class T> template<class T>
class ImprovedHoldAValue class ImprovedHoldAValue
{ {
public: public:
// Traits: information about type! // Traits: information about type!
using return_value = using return_value =
typename std::conditional<std::is_trivial<T>::value, T, const T&>::type; typename std::conditional<std::is_trivial<T>::value, T, const T&>::type;
ImprovedHoldAValue(T value); ImprovedHoldAValue(T value);
return_value GetValue() const; return_value GetValue() const;
// For the tutorial purpose! // For the tutorial purpose!
return_value GetValueAlternateSyntax() const; return_value GetValueAlternateSyntax() const;
private: private:
T value_; T value_;
}; };
template<class T> template<class T>
ImprovedHoldAValue<T>::ImprovedHoldAValue(T value) ImprovedHoldAValue<T>::ImprovedHoldAValue(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Beware the trait that acts as the return value must be scoped correctly in the definition: Beware the trait that acts as the return value must be scoped correctly in the definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
typename ImprovedHoldAValue<T>::return_value ImprovedHoldAValue<T>::GetValue() const typename ImprovedHoldAValue<T>::return_value ImprovedHoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
(unless you use the alternate syntax for function, which you should definitely consider!): (unless you use the alternate syntax for function, which you should definitely consider!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
auto ImprovedHoldAValue<T>::GetValueAlternateSyntax() const -> return_value auto ImprovedHoldAValue<T>::GetValueAlternateSyntax() const -> return_value
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And the result remain the same, albeit more efficient as a copy is avoided: And the result remain the same, albeit more efficient as a copy is avoided:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
ImprovedHoldAValue<int> hint(5); ImprovedHoldAValue<int> hint(5);
std::cout << hint.GetValue() << std::endl; std::cout << hint.GetValue() << std::endl;
ImprovedHoldAValue<std::string> sint("Hello world!"); ImprovedHoldAValue<std::string> sint("Hello world!");
std::cout << sint.GetValue() << std::endl; std::cout << sint.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We can even roll up a class which is uncopyable to check the constant reference is properly used: We can even roll up a class which is uncopyable to check the constant reference is properly used:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
class UncopyableObject class UncopyableObject
{ {
public: public:
explicit UncopyableObject(double value); explicit UncopyableObject(double value);
UncopyableObject(const UncopyableObject& rhs) = default; UncopyableObject(const UncopyableObject& rhs) = default;
UncopyableObject(UncopyableObject&& rhs) = default; UncopyableObject(UncopyableObject&& rhs) = default;
UncopyableObject& operator=(const UncopyableObject&) = delete; UncopyableObject& operator=(const UncopyableObject&) = delete;
UncopyableObject& operator=(UncopyableObject&&) = delete; UncopyableObject& operator=(UncopyableObject&&) = delete;
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double value_; double value_;
}; };
std::ostream& operator<<(std::ostream& out, const UncopyableObject& object); std::ostream& operator<<(std::ostream& out, const UncopyableObject& object);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
UncopyableObject::UncopyableObject(double value) UncopyableObject::UncopyableObject(double value)
: value_{value} : value_{value}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
void UncopyableObject::Print(std::ostream& out) const void UncopyableObject::Print(std::ostream& out) const
{ {
out << value_; out << value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
std::ostream& operator<<(std::ostream& out, const UncopyableObject& object) std::ostream& operator<<(std::ostream& out, const UncopyableObject& object)
{ {
object.Print(out); object.Print(out);
return out; return out;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
ImprovedHoldAValue<UncopyableObject> object(UncopyableObject(10.)); ImprovedHoldAValue<UncopyableObject> object(UncopyableObject(10.));
std::cout << object.GetValue() << std::endl; std::cout << object.GetValue() << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In fact sometimes you may even have **traits class**: class which sole purpose is to provide type information! Such classes are often used as template parameters of other classes. In fact sometimes you may even have **traits class**: class which sole purpose is to provide type information! Such classes are often used as template parameters of other classes.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Policies ## Policies
Policies are a way to provide a class for which a given aspect is entirely configurable by another class you provide as a template parameter. Policies are a way to provide a class for which a given aspect is entirely configurable by another class you provide as a template parameter.
STL uses up policies: for instance there is a second optional template parameter to `std::vector` which deals with the way to allocate the memory (and only with that aspect). So you may provide your own way to allocate the memory and provide it to `std::vector`, which will use it instead of its default behaviour. [Modern C++ design](../bibliography.ipynb#Modern-C++-Design) dedicates a whole chapter of his book to this example: he wrote an allocator aimed at being more efficient for the allocation of small objects. STL uses up policies: for instance there is a second optional template parameter to `std::vector` which deals with the way to allocate the memory (and only with that aspect). So you may provide your own way to allocate the memory and provide it to `std::vector`, which will use it instead of its default behaviour. [Modern C++ design](../bibliography.ipynb#Modern-C++-Design) dedicates a whole chapter of his book to this example: he wrote an allocator aimed at being more efficient for the allocation of small objects.
The syntax of a policy is a template class which also derives from at least one of its template parameter: The syntax of a policy is a template class which also derives from at least one of its template parameter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class ColorPolicyT> template<class ColorPolicyT>
class Car : public ColorPolicyT class Car : public ColorPolicyT
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
struct Blue struct Blue
{ {
void Print() const void Print() const
{ {
std::cout << "My color is blue!" << std::endl; std::cout << "My color is blue!" << std::endl;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
// Let's assume in the future a car provides a mechanism to change its color at will: // Let's assume in the future a car provides a mechanism to change its color at will:
class Changing class Changing
{ {
public: public:
void Display() const // I do not use `Print()` intentionally to illustrate there is no constraint void Display() const // I do not use `Print()` intentionally to illustrate there is no constraint
// but in true code it would be wise to use same naming scheme! // but in true code it would be wise to use same naming scheme!
{ {
std::cout << "Current color is " << color_ << "!" << std::endl; std::cout << "Current color is " << color_ << "!" << std::endl;
} }
void ChangeColor(const std::string& new_color) void ChangeColor(const std::string& new_color)
{ {
color_ = new_color; color_ = new_color;
} }
private: private:
std::string color_ = "white"; std::string color_ = "white";
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Car<Blue> blue_car; Car<Blue> blue_car;
blue_car.Print(); blue_car.Print();
Car<Changing> future_car; Car<Changing> future_car;
future_car.Display(); future_car.Display();
future_car.ChangeColor("black"); future_car.ChangeColor("black");
future_car.Display(); future_car.Display();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Variadic templates ## Variadic templates
If you have already written some C, you must be familiar with `printf`: If you have already written some C, you must be familiar with `printf`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <cstdio> #include <cstdio>
{ {
int i = 5; int i = 5;
double d = 3.1415; double d = 3.1415;
printf("i = %d\n", i); printf("i = %d\n", i);
printf("i = %d and d = %lf\n", i, d); printf("i = %d and d = %lf\n", i, d);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This function is atypical as it may take an arbitrary number of arguments. You can devise similar function of your own in C (look for `va_arg` if you insist...) but it was not recommended: under the hood it is quite messy, and limits greatly the checks your compiler may perform on your code (especially regarding the type of the arguments). This function is atypical as it may take an arbitrary number of arguments. You can devise similar function of your own in C (look for `va_arg` if you insist...) but it was not recommended: under the hood it is quite messy, and limits greatly the checks your compiler may perform on your code (especially regarding the type of the arguments).
C++ 11 introduced **variadic templates**, which provides a much neater way to provide this kind of functionality (albeit with a *very* tricky syntax: check all the `...` below... and it becomes worse if you need to propagate them). C++ 11 introduced **variadic templates**, which provides a much neater way to provide this kind of functionality (albeit with a *very* tricky syntax: check all the `...` below... and it becomes worse if you need to propagate them).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
// Overload when one value only. // Overload when one value only.
template<class T> template<class T>
void Print(T value) void Print(T value)
{ {
std::cout << value << std::endl; std::cout << value << std::endl;
} }
// Overload with a variadic number of arguments // Overload with a variadic number of arguments
template<class T, class ...Args> template<class T, class ...Args>
void Print(T value, Args... args) // args here will be all parameters passed to the function from the void Print(T value, Args... args) // args here will be all parameters passed to the function from the
// second one onward. // second one onward.
{ {
Print(value); Print(value);
Print(args...); // Will call recursively `Print()` with one less argument. Print(args...); // Will call recursively `Print()` with one less argument.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Print(5, "hello", "world", "ljksfo", 3.12); Print(5, "hello", "world", "ljksfo", 3.12);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Print("One"); Print("One");
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Print(); // Compilation error: no arguments isn't accepted! Print(); // Compilation error: no arguments isn't accepted!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To learn more about them, I recommend [Effective Modern C++](../bibliography.ipynb#Effective-Modern-C++), which provides healthy explanations about `std::forward` and `std::move` you will probably need soon if you want to use these variadic templates. To learn more about them, I recommend [Effective Modern C++](../bibliography.ipynb#Effective-Modern-C++), which provides healthy explanations about `std::forward` and `std::move` you will probably need soon if you want to use these variadic templates.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Template template parameters (not a mistake...) ## Template template parameters (not a mistake...)
You may want to be way more specific when defining a template parameter: instead of telling it might be whatever you want, you may impose that a specific template parameter should only be a type which is itself an instantiation of a template. You may want to be way more specific when defining a template parameter: instead of telling it might be whatever you want, you may impose that a specific template parameter should only be a type which is itself an instantiation of a template.
Let's consider a very dumb template function which purpose is to call print the value of `size()` for a STL container. We'll see them more extensively in a [dedicated notebook](../5-UsefulConceptsAndSTL/3-Containers.ipynb), but for now you just have to know that these containers take two template parameters: Let's consider a very dumb template function which purpose is to call print the value of `size()` for a STL container. We'll see them more extensively in a [dedicated notebook](../5-UsefulConceptsAndSTL/3-Containers.ipynb), but for now you just have to know that these containers take two template parameters:
- One that describe the type inside the container (e.g. `double` for `std::vector<double>`). - One that describe the type inside the container (e.g. `double` for `std::vector<double>`).
- Another optional one which specifies how the memory is allocated. - Another optional one which specifies how the memory is allocated.
We could not bother and use directly a usual template parameter: We could not bother and use directly a usual template parameter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintSize1(const ContainerT& container) void PrintSize1(const ContainerT& container)
{ {
std::cout << container.size() << std::endl; std::cout << container.size() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may use it on seamlessly on usual STL containers: You may use it on seamlessly on usual STL containers:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
#include <list> #include <list>
#include <deque> #include <deque>
{ {
std::vector<double> vector { 3.54, -73.1, 1004. }; std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 }; std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 }; std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 };
PrintSize1(vector); PrintSize1(vector);
PrintSize1(list); PrintSize1(list);
PrintSize1(deque); PrintSize1(deque);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, it would also work for any class that define a `size()` parameters, regardless of its nature. However, it would also work for any class that define a `size()` parameters, regardless of its nature.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
struct NonTemplateClass struct NonTemplateClass
{ {
std::string size() const std::string size() const
{ {
return "Might seem idiotic, but why not?"; return "Might seem idiotic, but why not?";
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class U, class V, class W> template<class U, class V, class W>
struct TemplateWithThreeParameters struct TemplateWithThreeParameters
{ {
int size() const int size() const
{ {
return -99; return -99;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
NonTemplateClass non_template_class; NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters; TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize1(non_template_class); PrintSize1(non_template_class);
PrintSize1(template_with_three_parameters); PrintSize1(template_with_three_parameters);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We see here with my rather dumb example that `PrintSize1()` also works for my own defined types, that are not following the expected prototype of a STL container (class with two template parameters). We see here with my rather dumb example that `PrintSize1()` also works for my own defined types, that are not following the expected prototype of a STL container (class with two template parameters).
It may seem pointless in this example, but the worst is that the method might be used to represent something entirely different from what we expect when we call `size()` upon a STL container. It may seem pointless in this example, but the worst is that the method might be used to represent something entirely different from what we expect when we call `size()` upon a STL container.
A possibility to limit the risk is to use a **template template parameter** in the function definition: A possibility to limit the risk is to use a **template template parameter** in the function definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<template <class, class> class ContainerT, class TypeT, class AllocatorT> template<template <class, class> class ContainerT, class TypeT, class AllocatorT>
void PrintSize2(const ContainerT<TypeT, AllocatorT>& container) void PrintSize2(const ContainerT<TypeT, AllocatorT>& container)
{ {
std::cout << container.size() << std::endl; std::cout << container.size() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
By doing so, we impose that the type of the argument is an instantiation of a class with two arguments. With that, STL containers work: By doing so, we impose that the type of the argument is an instantiation of a class with two arguments. With that, STL containers work:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
#include <list> #include <list>
#include <deque> #include <deque>
{ {
std::vector<double> vector { 3.54, -73.1, 1004. }; std::vector<double> vector { 3.54, -73.1, 1004. };
std::list<int> list { 15, -87, 12, 12, 0, -445 }; std::list<int> list { 15, -87, 12, 12, 0, -445 };
std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 }; std::deque<unsigned int> deque { 2, 87, 95, 14, 451, 10, 100, 1000 };
// At call site, you don't have to specify the template arguments that are inferred. // At call site, you don't have to specify the template arguments that are inferred.
PrintSize2(vector); PrintSize2(vector);
PrintSize2(list); PrintSize2(list);
PrintSize2(deque); PrintSize2(deque);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
whereas my own defined types don't: whereas my own defined types don't:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
NonTemplateClass non_template_class; NonTemplateClass non_template_class;
TemplateWithThreeParameters<int, float, double> template_with_three_parameters; TemplateWithThreeParameters<int, float, double> template_with_three_parameters;
PrintSize2(non_template_class); PrintSize2(non_template_class);
PrintSize2(template_with_three_parameters); PrintSize2(template_with_three_parameters);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In practice you shouldn't need to use that too often, but in the context of this notebook it is worth knowing that the possibility exists (it may help you understand an error message should you use a library using them). I had to resort to them a couple of times, especially along policies. In practice you shouldn't need to use that too often, but in the context of this notebook it is worth knowing that the possibility exists (it may help you understand an error message should you use a library using them). I had to resort to them a couple of times, especially along policies.
If you want to learn more about them, you should really read [Modern C++ design](../bibliography.ipynb#Modern-C++-Design). If you want to learn more about them, you should really read [Modern C++ design](../bibliography.ipynb#Modern-C++-Design).
Of course, `concept` introduced in C++ 20 are a much more refined tool to check template parameter type fulfills some constraints, but template template parameter are a much older feature that worked way back to pre-C++ 11 versions of the language. Of course, `concept` introduced in C++ 20 are a much more refined tool to check template parameter type fulfills some constraints, but template template parameter are a much older feature that worked way back to pre-C++ 11 versions of the language.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [Error handling](./1-ErrorHandling.ipynb) # [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [Error handling](./1-ErrorHandling.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
It is very important of course to be able to track and manage as nicely as possible when something goes south in your code. We will see in this chapter the main ways to provide such insurance. It is very important of course to be able to track and manage as nicely as possible when something goes south in your code. We will see in this chapter the main ways to provide such insurance.
## Compiler warnings and errors ## Compiler warnings and errors
The first way to find out possible errors are during compilation time: you may ensure your code is correct by making its compilation fails if not (that's exactly the spirit of the example we provided for [template template parameter](../4-Templates/5-MoreAdvanced.ipynb#Template-template-parameters-(not-a-mistake...))). There are many ways to do so, even more so if templates are involved; here are few of them we have already seen: The first way to find out possible errors are during compilation time: you may ensure your code is correct by making its compilation fails if not (that's exactly the spirit of the example we provided for [template template parameter](../4-Templates/5-MoreAdvanced.ipynb#Template-template-parameters-(not-a-mistake...))). There are many ways to do so, even more so if templates are involved; here are few of them we have already seen:
* `static_assert` we saw in [template introduction](../4-Templates/1-Intro.ipynb#static_assert) * `static_assert` we saw in [template introduction](../4-Templates/1-Intro.ipynb#static_assert)
* Duck typing failure: if a template argument used somewhere doesn't comply with the expected API. If you're using C++ 20, consider using `concept` to restrain what is provided as template argument. * Duck typing failure: if a template argument used somewhere doesn't comply with the expected API. If you're using C++ 20, consider using `concept` to restrain what is provided as template argument.
* Locality of reference: use heavily blocks so that a variable is freed as soon as possible. This way, you will avoid mistakes of using a variable that is in fact no longer up-to-date. * Locality of reference: use heavily blocks so that a variable is freed as soon as possible. This way, you will avoid mistakes of using a variable that is in fact no longer up-to-date.
* Strive to make your code without any compiler warning: if there are even as less as 10 warnings, you might not see an eleventh that might sneak its way at some point. Activate as many types of warnings as possible for your compiler, and deactivate those unwanted with care (see [this notebook](../6-InRealEnvironment/4-ThirdParty.ipynb) to see how to manage third party warnings.). * Strive to make your code without any compiler warning: if there are even as less as 10 warnings, you might not see an eleventh that might sneak its way at some point. Activate as many types of warnings as possible for your compiler, and deactivate those unwanted with care (see [this notebook](../6-InRealEnvironment/4-ThirdParty.ipynb) to see how to manage third party warnings.).
## Assert ## Assert
`assert` is a very handy tool that your code behaves exactly as expected. `assert` takes one argument; if this argument is resolved to `false` the code aborts with an error message: `assert` is a very handy tool that your code behaves exactly as expected. `assert` takes one argument; if this argument is resolved to `false` the code aborts with an error message:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
{ {
double* ptr = nullptr; double* ptr = nullptr;
assert(ptr != nullptr && "Pointer should be initialized first!"); assert(ptr != nullptr && "Pointer should be initialized first!");
//< See the `&&`trick above: you can't provide a message like in `static_assert`, //< See the `&&`trick above: you can't provide a message like in `static_assert`,
// but you may use a AND condition and a string to detail the issue // but you may use a AND condition and a string to detail the issue
// (it works because the string is evaluated as `true`). // (it works because the string is evaluated as `true`).
std::cout << *ptr << std::endl; std::cout << *ptr << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Note:** we hacked a bit our kernel to mimic `assert` behaviour in notebooks; below is the same code handled by a "true" compiler:
%% Cell type:code id: tags:
``` c++
%%cpptoolbox clang
#include <cassert>
#include <iostream>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
double* ptr = nullptr;
assert(ptr != nullptr && "Pointer should be initialized first!");
// < See the `&&`trick above: you can't provide a message like in `static_assert`,
// but you may use a AND condition and a string to detail the issue
// (it works because the string is evaluated as `true`).
std::cout << *ptr << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
The perk of `assert` is that it checks the condition is `true` *only in debug mode*! The perk of `assert` is that it checks the condition is `true` *only in debug mode*!
So you may get extensive tests in debug mode that are ignored once your code has been thoroughly checked and is ready for production use (_debug_ and _release_ mode will be explained in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb#Debug-and-release-flags)). So you may get extensive tests in debug mode that are ignored once your code has been thoroughly checked and is ready for production use (_debug_ and _release_ mode will be explained in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb#Debug-and-release-flags)).
The example above is a very useful use: before dereferencing a pointer checks it is not `nullptr` (hence the good practice to always initialize a pointer to `nullptr`...) The example above is a very useful use: before dereferencing a pointer checks it is not `nullptr` (hence the good practice to always initialize a pointer to `nullptr`...)
In **release mode**, the macro `NDEBUG` should be defined and all the `assert` declarations will be ignored by the compiler. In **release mode**, the macro `NDEBUG` should be defined and all the `assert` declarations will be ignored by the compiler.
I recommend to use `assert` extensively in your code: I recommend to use `assert` extensively in your code:
* You're using a pointer? Check it is not `nullptr`. * You're using a pointer? Check it is not `nullptr`.
* You get in a function a `std::vector` and you know it should be exactly 3 elements long? Fire up an assert to check this... * You get in a function a `std::vector` and you know it should be exactly 3 elements long? Fire up an assert to check this...
* A `std::vector` is expected to be sorted a given way? Check it through an `assert`... (yes it's a O(n) operation, but if your contract is broken you need to know it!) * A `std::vector` is expected to be sorted a given way? Check it through an `assert`... (yes it's a O(n) operation, but if your contract is broken you need to know it!)
Of course, your debug mode will be much slower; but its role is anyway to make sure your code is correct, not to be the fastest possible (release mode is there for that!) Of course, your debug mode will be much slower; but its role is anyway to make sure your code is correct, not to be the fastest possible (release mode is there for that!)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Exceptions ## Exceptions
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Asserts are clearly a **developer** tool: they are there to signal something does not behave as intended and therefore that there is a bug somewhere... Asserts are clearly a **developer** tool: they are there to signal something does not behave as intended and therefore that there is a bug somewhere...
However, they are clearly not appropriate to handle an error of your program end-user: for instance if he specifies an invalid input file, you do not want an `abort` which is moreover handled only in debug mode! However, they are clearly not appropriate to handle an error of your program end-user: for instance if he specifies an invalid input file, you do not want an `abort` which is moreover handled only in debug mode!
### `throw` ### `throw`
There is an **exception** mechanism that is appropriate to deal with this; this mechanism is activated with the keyword `throw`. There is an **exception** mechanism that is appropriate to deal with this; this mechanism is activated with the keyword `throw`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Kernel yields a weird output so we're better off using compiler directly // Kernel yields a weird output so we're better off using compiler directly
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber(int n); void FunctionThatExpectsSingleDigitNumber(int n);
void FunctionThatExpectsSingleDigitNumber(int n) void FunctionThatExpectsSingleDigitNumber(int n)
{ {
if (n < -9) if (n < -9)
throw -1; throw -1;
if (n > 9) if (n > 9)
throw 1; throw 1;
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
FunctionThatExpectsSingleDigitNumber(5); FunctionThatExpectsSingleDigitNumber(5);
std::cout << "After call 5" << std::endl; std::cout << "After call 5" << std::endl;
FunctionThatExpectsSingleDigitNumber(15); FunctionThatExpectsSingleDigitNumber(15);
std::cout << "After call 15" << std::endl; std::cout << "After call 15" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you can see, an exception provokes an early exit of the function: the lines after the exception is thrown are not run, and unless it is caught it will stop at the abortion of the program. As you can see, an exception provokes an early exit of the function: the lines after the exception is thrown are not run, and unless it is caught it will stop at the abortion of the program.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `try`/`catch` ### `try`/`catch`
`throw` expects an object which might be intercepted by the `catch` command if the exception occurred in a `try` block; `catch` is followed by a block in which something may be attempted (or not!) `throw` expects an object which might be intercepted by the `catch` command if the exception occurred in a `try` block; `catch` is followed by a block in which something may be attempted (or not!)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Kernel yields a weird output so we're better off using compiler directly // Kernel yields a weird output so we're better off using compiler directly
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber(int n); void FunctionThatExpectsSingleDigitNumber(int n);
void FunctionThatExpectsSingleDigitNumber(int n) void FunctionThatExpectsSingleDigitNumber(int n)
{ {
if (n < -9) if (n < -9)
throw -1; throw -1;
if (n > 9) if (n > 9)
throw 1; throw 1;
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber(15); FunctionThatExpectsSingleDigitNumber(15);
} }
catch(int n) catch(int n)
{ {
if (n == 1) if (n == 1)
std::cerr << "Error: value is bigger than 9!" << std::endl; std::cerr << "Error: value is bigger than 9!" << std::endl;
if (n == -1) if (n == -1)
std::cerr << "Error: value is less than -9!" << std::endl; std::cerr << "Error: value is less than -9!" << std::endl;
} }
std::cout << "End" << std::endl; std::cout << "End" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If the type doesn't match, the exception is not caught; if you want to be sure to catch everything you may use the `...` syntax. The drawback with this syntax is you can't use the thrown object information: If the type doesn't match, the exception is not caught; if you want to be sure to catch everything you may use the `...` syntax. The drawback with this syntax is you can't use the thrown object information:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Kernel yields a weird output so we're better off using compiler directly // Kernel yields a weird output so we're better off using compiler directly
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber(int n); void FunctionThatExpectsSingleDigitNumber(int n);
void FunctionThatExpectsSingleDigitNumber(int n) void FunctionThatExpectsSingleDigitNumber(int n)
{ {
if (n < -9) if (n < -9)
throw -1; throw -1;
if (n > 9) if (n > 9)
throw 1; throw 1;
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber(15); FunctionThatExpectsSingleDigitNumber(15);
} }
catch(float n) // doesn't catch your `int` exception! catch(float n) // doesn't catch your `int` exception!
{ {
std::cerr << "Float case: " << n << " was provided and is not an integer" << std::endl; std::cerr << "Float case: " << n << " was provided and is not an integer" << std::endl;
} }
catch(...) catch(...)
{ {
std::cerr << "Gluttony case... but no object to manipulate to extract more information!" << std::endl; std::cerr << "Gluttony case... but no object to manipulate to extract more information!" << std::endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Re-throw ### Re-throw
Once an exception has been caught by a `catch` block, it is considered to be handled; the code will therefore go on to what is immediately after the block. If you want to throw the exception again (for instance after logging a message) you may just type `throw`: Once an exception has been caught by a `catch` block, it is considered to be handled; the code will therefore go on to what is immediately after the block. If you want to throw the exception again (for instance after logging a message) you may just type `throw`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Kernel yields a weird output so we're better off using compiler directly // Kernel yields a weird output so we're better off using compiler directly
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber(int n); void FunctionThatExpectsSingleDigitNumber(int n);
void FunctionThatExpectsSingleDigitNumber(int n) void FunctionThatExpectsSingleDigitNumber(int n)
{ {
if (n < -9) if (n < -9)
throw -1; throw -1;
if (n > 9) if (n > 9)
throw 1; throw 1;
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber(15); FunctionThatExpectsSingleDigitNumber(15);
} }
catch(int n) catch(int n)
{ {
std::cerr << "Int case: " << n << " not a single digit number" << std::endl; std::cerr << "Int case: " << n << " not a single digit number" << std::endl;
} }
std::cout << "After catch" << std::endl; std::cout << "After catch" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
// Kernel yields a weird output so we're better off using compiler directly // Kernel yields a weird output so we're better off using compiler directly
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber(int n); void FunctionThatExpectsSingleDigitNumber(int n);
void FunctionThatExpectsSingleDigitNumber(int n) void FunctionThatExpectsSingleDigitNumber(int n)
{ {
if (n < -9) if (n < -9)
throw -1; throw -1;
if (n > 9) if (n > 9)
throw 1; throw 1;
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber(15); FunctionThatExpectsSingleDigitNumber(15);
} }
catch(int n) catch(int n)
{ {
std::cerr << "Int case: " << n << " not a single digit number" << std::endl; std::cerr << "Int case: " << n << " not a single digit number" << std::endl;
throw; // the only difference with previous cell! Note `n` is not needed here. throw; // the only difference with previous cell! Note `n` is not needed here.
} }
std::cout << "After catch" << std::endl; std::cout << "After catch" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: use as much as possible exceptions that derive from `std::exception` ### Good practice: use as much as possible exceptions that derive from `std::exception`
Using the _catch all_ case is not recommended in most cases... In fact even the `int`/`float` case is not that smart: it is better to use an object with information about why the exception was raised in the first place. Using the _catch all_ case is not recommended in most cases... In fact even the `int`/`float` case is not that smart: it is better to use an object with information about why the exception was raised in the first place.
It is advised to use exception classes derived from the `std::exception` one; this way you provide a catch all without the drawback mentioned earlier. This class provides a virtual `what()` method which gives away more intel about the issue: It is advised to use exception classes derived from the `std::exception` one; this way you provide a catch all without the drawback mentioned earlier. This class provides a virtual `what()` method which gives away more intel about the issue:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <exception> #include <exception>
struct TooSmallError : public std::exception struct TooSmallError : public std::exception
{ {
virtual const char* what() const noexcept override // we'll go back to `noexcept` later... virtual const char* what() const noexcept override // we'll go back to `noexcept` later...
{ {
return "Value is less than -9!"; return "Value is less than -9!";
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct TooBigError : public std::exception struct TooBigError : public std::exception
{ {
virtual const char* what() const noexcept override virtual const char* what() const noexcept override
{ {
return "Value is more than 9!"; return "Value is more than 9!";
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber2(int n) void FunctionThatExpectsSingleDigitNumber2(int n)
{ {
if (n < -9) if (n < -9)
throw TooSmallError(); throw TooSmallError();
if (n > 9) if (n > 9)
throw TooBigError(); throw TooBigError();
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber2(15); FunctionThatExpectsSingleDigitNumber2(15);
} }
catch(const std::exception& e) catch(const std::exception& e)
{ {
std::cerr << "Properly caught: " << e.what() << std::endl; std::cerr << "Properly caught: " << e.what() << std::endl;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The information comes now with the exception object, which is much better... The information comes now with the exception object, which is much better...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Unfortunately, you are not always privy to the choice of deriving from `std::exception`: if for instance you're using [Boost library](https://www.boost.org) the exception class they use don't inherit from `std::exception` (but some derived ones such as `boost::filesystem::error` do...). In this case, make sure to foresee to catch them with a dedicated block: Unfortunately, you are not always privy to the choice of deriving from `std::exception`: if for instance you're using [Boost library](https://www.boost.org) the exception class they use don't inherit from `std::exception` (but some derived ones such as `boost::filesystem::error` do...). In this case, make sure to foresee to catch them with a dedicated block:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
// Pseudo-code - Do not run in notebook! // Pseudo-code - Do not run in notebook!
try try
{ {
... ...
} }
catch(const std::exception& e) catch(const std::exception& e)
{ {
... ...
} }
catch(const boost::exception& e) catch(const boost::exception& e)
{ {
... ...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Storing more information in the class... and avoiding the `char*` pitfall! ### Storing more information in the class... and avoiding the `char*` pitfall!
In fact we could have gone even further and personnalize the exception message, for instance by printing for which value of `n` the issue arose: In fact we could have gone even further and personnalize the exception message, for instance by printing for which value of `n` the issue arose:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct TooBigErrorWithMessage : public std::exception struct TooBigErrorWithMessage : public std::exception
{ {
TooBigErrorWithMessage(int n); TooBigErrorWithMessage(int n);
virtual const char* what() const noexcept override virtual const char* what() const noexcept override
{ {
return msg_.c_str(); // c_str() as we need a const char*, not a std::string! return msg_.c_str(); // c_str() as we need a const char*, not a std::string!
} }
private: private:
std::string msg_; std::string msg_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> #include <sstream>
TooBigErrorWithMessage::TooBigErrorWithMessage(int n) TooBigErrorWithMessage::TooBigErrorWithMessage(int n)
{ {
std::ostringstream oconv; std::ostringstream oconv;
oconv << "Value '" << n << "' is more than 9!"; oconv << "Value '" << n << "' is more than 9!";
msg_ = oconv.str(); msg_ = oconv.str();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void FunctionThatExpectsSingleDigitNumber3(int n) void FunctionThatExpectsSingleDigitNumber3(int n)
{ {
if (n < -9) if (n < -9)
throw TooSmallError(); throw TooSmallError();
if (n > 9) if (n > 9)
throw TooBigErrorWithMessage(n); throw TooBigErrorWithMessage(n);
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumber3(15); FunctionThatExpectsSingleDigitNumber3(15);
} }
catch(const std::exception& e) catch(const std::exception& e)
{ {
std::cerr << "Properly caught: " << e.what() << std::endl; std::cerr << "Properly caught: " << e.what() << std::endl;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This might seem trivial here, but in real code it is really handy to get all relevant information from your exception. This might seem trivial here, but in real code it is really handy to get all relevant information from your exception.
However, I avoided silently a common pitfall when dabbling with `std::exception`: one of its cardinal sin is to use C string as return type for its `what()` method. It might seem innocuous enough, but is absolutely not if you do not use a `std::string` as a data attribute to encapsulate the message. However, I avoided silently a common pitfall when dabbling with `std::exception`: one of its cardinal sin is to use C string as return type for its `what()` method. It might seem innocuous enough, but is absolutely not if you do not use a `std::string` as a data attribute to encapsulate the message.
Let's write it without the `msg_` data attribute: Let's write it without the `msg_` data attribute:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox write_into_file %%file /tmp/cell.cpp
#include <cstdlib> #include <cstdlib>
#include <exception> #include <exception>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
// =========================== // ===========================
// Declarations // Declarations
// =========================== // ===========================
struct TooBigErrorWithMessagePoorlyImplemented : public std::exception struct TooBigErrorWithMessagePoorlyImplemented : public std::exception
{ {
TooBigErrorWithMessagePoorlyImplemented(int n); TooBigErrorWithMessagePoorlyImplemented(int n);
virtual const char* what() const noexcept override; virtual const char* what() const noexcept override;
private: private:
int n_; int n_;
}; };
// We skip negative case for readability's sake // We skip negative case for readability's sake
void FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(int n); void FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(int n);
// =========================== // ===========================
// Definitions // Definitions
// =========================== // ===========================
TooBigErrorWithMessagePoorlyImplemented::TooBigErrorWithMessagePoorlyImplemented(int n) TooBigErrorWithMessagePoorlyImplemented::TooBigErrorWithMessagePoorlyImplemented(int n)
: n_{n} : n_{n}
{ } { }
const char* TooBigErrorWithMessagePoorlyImplemented::what() const noexcept const char* TooBigErrorWithMessagePoorlyImplemented::what() const noexcept
{ {
std::ostringstream oconv; std::ostringstream oconv;
oconv << "Value '" << n_ << "' is more than 9!"; oconv << "Value '" << n_ << "' is more than 9!";
std::string msg = oconv.str(); std::string msg = oconv.str();
std::cout << "\nCheck: message is |" << msg << "|" << std::endl; std::cout << "\nCheck: message is |" << msg << "|" << std::endl;
return msg.c_str(); return msg.c_str();
} }
void FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(int n) void FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(int n)
{ {
if (n > 9) if (n > 9)
throw TooBigErrorWithMessagePoorlyImplemented(n); throw TooBigErrorWithMessagePoorlyImplemented(n);
std::cout << "Valid digit is " << n << std::endl; std::cout << "Valid digit is " << n << std::endl;
} }
// =========================== // ===========================
// Main // Main
// =========================== // ===========================
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
try try
{ {
FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(15); FunctionThatExpectsSingleDigitNumberWithPoorlyImplementedException(15);
} }
catch(const std::exception& e) catch(const std::exception& e)
{ {
std::cerr << "Properly caught: " << e.what() << std::endl; std::cerr << "Properly caught: " << e.what() << std::endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You might wonder why we have just chosen to write the content in a file rather than executing the cell? You might wonder why we have just chosen to write the content in a file rather than executing the cell?
The reason is that both our usual ways to run the cell (either using Cppyy kernel directly or defering to the local compiler) yield very obscure error messages that completely hide what goes wrong (even if there is a very helpful warning which pinpoints the issue). The reason is that both our usual ways to run the cell (either using Cppyy kernel directly or defering to the local compiler) yield very obscure error messages that completely hide what goes wrong (even if there is a very helpful warning which pinpoints the issue).
For once, let's go to the terminal and compile and run from there the program (please don't bother about the `-W` options - we'll get you covered in part 6): For once, let's go to the terminal and compile and run from there the program (please don't bother about the `-W` options - we'll get you covered in part 6):
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**In a terminal** **In a terminal**
*Compile the program* *Compile the program*
```shell ```shell
clang++ -std=c++20 -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-newline-eof -Wno-padded /tmp/cell.cpp -o poorly_implemented_exception clang++ -std=c++20 -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-newline-eof -Wno-padded /tmp/cell.cpp -o poorly_implemented_exception
``` ```
*Run the program* *Run the program*
```shell ```shell
./poorly_implemented_exception ./poorly_implemented_exception
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
You should get something like: You should get something like:
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
```shell ```shell
Properly caught: Properly caught:
Check: message is |Value '15' is more than 9!| Check: message is |Value '15' is more than 9!|
``` ```
followed by gibberish characters (which probably vary from one call to another) that may be followed by gibberish characters that vary from one call to another (or not - sometimes the program will work seemingly fine...)|
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So what happens here? So what happens here?
The deal is that `c_str()` returns the pointer to the underlying data used in the `std::string` object... which got destroyed at the end of `what()` function... The deal is that `c_str()` returns the pointer to the underlying data used in the `std::string` object... which got destroyed at the end of `what()` function...
So we're directly in the realm of undefined behaviour (sometimes it might be kernel crash, sometimes it might be partial or complete gibberish printed instead of the expected string). So we're directly in the realm of undefined behaviour (sometimes it might be kernel crash, sometimes it might be partial or complete gibberish printed instead of the expected string).
The ordeal would of course be the same with another type (for instance if instead of using `std::string` you allocate manually a `char*` variable): as soon as you get out of scope the variable is destroyed and behaviour is erratic. The ordeal would of course be the same with another type (for instance if instead of using `std::string` you allocate manually a `char*` variable): as soon as you get out of scope the variable is destroyed and behaviour is erratic.
So when you define an exception class you can't define the return of `what()` method inside the implementation of the method itself; you **must** use a data attribute to store it. The most common choice is to use a `std::string`. So when you define an exception class you can't define the return of `what()` method inside the implementation of the method itself; you **must** use a data attribute to store it. The most common choice is to use a `std::string`.
And please notice that your compiler was very helpful with its warning message: And please notice that your compiler was very helpful with its warning message:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
```shell ```shell
/tmp/cell.cpp:41:12: warning: address of stack memory associated with local variable 'msg' returned [-Wreturn-stack-address] /tmp/cell.cpp:41:12: warning: address of stack memory associated with local variable 'msg' returned [-Wreturn-stack-address]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: be wary of a forest of exception classes ### Good practice: be wary of a forest of exception classes
At first sight, it might be tempting to provide a specific exception whenever you want to throw one: this way, you are able to catch only this one later on. At first sight, it might be tempting to provide a specific exception whenever you want to throw one: this way, you are able to catch only this one later on.
In practice, it's not necessarily such a good idea: In practice, it's not necessarily such a good idea:
* When the code becomes huge, you (and even more importantly a new developer) may be lost in all the possible exceptions. * When the code becomes huge, you (and even more importantly a new developer) may be lost in all the possible exceptions.
* It is rather time consuming: defining a specific exception means a bit of boilerplate to write, and those minutes might have been spent more efficiently, as... * It is rather time consuming: defining a specific exception means a bit of boilerplate to write, and those minutes might have been spent more efficiently, as...
* Most of the time, you don't even need the filtering capacity; in my code for instance if an exception is thrown it is 99 % of the time to be caught in the `main()` function to terminate properly the execution. * Most of the time, you don't even need the filtering capacity; in my code for instance if an exception is thrown it is 99 % of the time to be caught in the `main()` function to terminate properly the execution.
The only case in which it might be very valuable to use a tailored exception is for your integration tests: if you are writing a test in which an exception is expected, it is better to check the exception you caught is exactly the one that was expected and not a completely unrelated exception which was thrown for another reason. The only case in which it might be very valuable to use a tailored exception is for your integration tests: if you are writing a test in which an exception is expected, it is better to check the exception you caught is exactly the one that was expected and not a completely unrelated exception which was thrown for another reason.
STL provides many derived class from `std::exception` which you might use directly or as base of your own class; see [cppreference](https://en.cppreference.com/w/cpp/error/exception) for more details. [OpenClassrooms](https://openclassrooms.com/fr/courses/7137751-programmez-en-oriente-objet-avec-c/7532931-gerez-des-erreurs-avec-les-exceptions) (in french) sorted out the go-to exceptions for lazy developers which cover most of the cases (don't get me wrong: laziness is often an asset for a software developer!): STL provides many derived class from `std::exception` which you might use directly or as base of your own class; see [cppreference](https://en.cppreference.com/w/cpp/error/exception) for more details. [OpenClassrooms](https://openclassrooms.com/fr/courses/7137751-programmez-en-oriente-objet-avec-c/7532931-gerez-des-erreurs-avec-les-exceptions) (in french) sorted out the go-to exceptions for lazy developers which cover most of the cases (don't get me wrong: laziness is often an asset for a software developer!):
* `std::domain_error` * `std::domain_error`
* `std::invalid_argument` * `std::invalid_argument`
* `std::length_error` * `std::length_error`
* `std::out_of_range` * `std::out_of_range`
* `std::logic_error` * `std::logic_error`
* `std::range_error` * `std::range_error`
* `std::overflow_error` * `std::overflow_error`
* `std::underflow_error` * `std::underflow_error`
* `std::runtime_error` * `std::runtime_error`
with the latter being the default choice if no other fit your issue. Most of those classes provide a `std::string` argument in its constructor so that you may explain exactly what went wrong. with the latter being the default choice if no other fit your issue. Most of those classes provide a `std::string` argument in its constructor so that you may explain exactly what went wrong.
### `noexcept` ### `noexcept`
Exceptions are in fact very subtle to use; see for instance [Herb Sutter's books](../bibliography.ipynb#Exceptional-C++-/-More-Exceptional-C++) that deal with them extensively (hence their title!). Exceptions are in fact very subtle to use; see for instance [Herb Sutter's books](../bibliography.ipynb#Exceptional-C++-/-More-Exceptional-C++) that deal with them extensively (hence their title!).
In C++03, it was possible to specify a method or a function wouldn't throw, but the underlying mechanism with keywords `throw` and `nothrow` was such a mess many C++ gurus warned against using them. In C++03, it was possible to specify a method or a function wouldn't throw, but the underlying mechanism with keywords `throw` and `nothrow` was such a mess many C++ gurus warned against using them.
In C++11, they tried to rationalize it and a new keyword to replace them was introduced: `noexcept`. In C++11, they tried to rationalize it and a new keyword to replace them was introduced: `noexcept`.
In short, if you have a method you're 100 % percent sure can't throw an exception, add this suffix and the compiler may optimize even further. However, do not put it if an exception can be thrown: it would result in a ugly runtime crash should an exception be raised there... (and up to now compilers are completely oblivious to that: no associated warning is displayed). In short, if you have a method you're 100 % percent sure can't throw an exception, add this suffix and the compiler may optimize even further. However, do not put it if an exception can be thrown: it would result in a ugly runtime crash should an exception be raised there... (and up to now compilers are completely oblivious to that: no associated warning is displayed).
As you saw, in recent C++ `what()` is to be a `noexcept` method. It is therefore a bad idea to try to allocate there the string to be returned: allocation and string manipulation could lead to an exception from the STL functions used. As you saw, in recent C++ `what()` is to be a `noexcept` method. It is therefore a bad idea to try to allocate there the string to be returned: allocation and string manipulation could lead to an exception from the STL functions used.
FYI, currently the error messages provided by compilers when your runtime crash due to poorly placed `noexcept` may look like: FYI, currently the error messages provided by compilers when your runtime crash due to poorly placed `noexcept` may look like:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
clang++: clang++:
```shell ```shell
libc++abi: terminating due to uncaught exception of type **your exception** libc++abi: terminating due to uncaught exception of type **your exception**
``` ```
g++: g++:
```shell ```shell
terminate called after throwing an instance of **your exception** terminate called after throwing an instance of **your exception**
154: what(): Exception found 154: what(): Exception found
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
They're not great: it's not obvious the issue stems from a call happening where it shouldn't, and they do not give a lot of information to where you should look to fix it. The best is therefore to be extremely cautious before marking a function as `noexcept`. However, use it when you can (see item 14 of [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) for incentives to use it). They're not great: it's not obvious the issue stems from a call happening where it shouldn't, and they do not give a lot of information to where you should look to fix it. The best is therefore to be extremely cautious before marking a function as `noexcept`. However, use it when you can (see item 14 of [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) for incentives to use it).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: never throw an exception from a destructor ### Good practice: never throw an exception from a destructor
The explanation is quite subtle and explained in detail in item 8 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++); however just know you should never throw an exception there. If you need to deal with an error there, use something else (`std::abort` for instance). The explanation is quite subtle and explained in detail in item 8 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++); however just know you should never throw an exception there. If you need to deal with an error there, use something else (`std::abort` for instance).
### The exception class I use ### The exception class I use
I (Sébastien) provide in [appendix](../7-Appendix/HomemadeException.ipynb) my own exception class (which of course derives from `std::exception`) which provides additionally: I (Sébastien) provide in [appendix](../7-Appendix/HomemadeException.ipynb) my own exception class (which of course derives from `std::exception`) which provides additionally:
* A constructor with a string, to avoid defining a verbosy dedicated exception class for each case. * A constructor with a string, to avoid defining a verbosy dedicated exception class for each case.
* Better management of the string display, with an underlying `std::string` object. * Better management of the string display, with an underlying `std::string` object.
* Information about the location from where the exception was thrown. * Information about the location from where the exception was thrown.
Vincent uses the STL exceptions described in [the previous section](#Good-practice:-be-wary-of-a-forest-of-exception-classes). Vincent uses the STL exceptions described in [the previous section](#Good-practice:-be-wary-of-a-forest-of-exception-classes).
## Error codes ## Error codes
A quick word about C-style error management which you might find in use in some libraries: **error codes**. A quick word about C-style error management which you might find in use in some libraries: **error codes**.
The principe of the error codes is that your functions and methods should return an `int` which provides an indication of the success or not of the call; the eventual values sought are returned from reference. For instance: The principe of the error codes is that your functions and methods should return an `int` which provides an indication of the success or not of the call; the eventual values sought are returned from reference. For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <type_traits> #include <type_traits>
constexpr auto INVALID_TYPE = -1; constexpr auto INVALID_TYPE = -1;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
int AbsoluteValue(T value, T& result) int AbsoluteValue(T value, T& result)
{ {
if constexpr (!std::is_arithmetic<T>()) if constexpr (!std::is_arithmetic<T>())
return INVALID_TYPE; return INVALID_TYPE;
else else
{ {
if (value < 0) if (value < 0)
result = -value; result = -value;
else else
result = value; result = value;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
I don't like these error codes much, because: I don't like these error codes much, because:
* The result can't be naturally given in return value and must be provided in argument. * The result can't be naturally given in return value and must be provided in argument.
* You have to bookkeep the possible error codes somewhere, and a user must know this somewhere to go consult them if something happens (usually a header file: see for instance one for [PETSc library](https://www.mcs.anl.gov/petsc/petsc-master/include/petscerror.h.html)). * You have to bookkeep the possible error codes somewhere, and a user must know this somewhere to go consult them if something happens (usually a header file: see for instance one for [PETSc library](https://www.mcs.anl.gov/petsc/petsc-master/include/petscerror.h.html)).
* In the libraries that use them, more often than not some are not self descriptive and you have to figure out what the hell the issue is. * In the libraries that use them, more often than not some are not self descriptive and you have to figure out what the hell the issue is.
* And more importantly, this relies on the end-user thinking to check the error value: * And more importantly, this relies on the end-user thinking to check the error value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
#include <iostream> #include <iostream>
{ {
std::string hello { "Hello world" }; std::string hello { "Hello world" };
std::string absolute_str { "not modified at all by function call..." }; std::string absolute_str { "not modified at all by function call..." };
int negative { -5 }; int negative { -5 };
int absolute_int { }; int absolute_int { };
AbsoluteValue(negative, absolute_int); AbsoluteValue(negative, absolute_int);
std::cout << "Absolute value for integer is " << absolute_int << std::endl; std::cout << "Absolute value for integer is " << absolute_int << std::endl;
AbsoluteValue(hello, absolute_str); // No compilation or runtime error (or even warning)! AbsoluteValue(hello, absolute_str); // No compilation or runtime error (or even warning)!
std::cout << "Absolute value for string is " << absolute_str << std::endl; std::cout << "Absolute value for string is " << absolute_str << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It should be noticed C++ 11 introduced a dedicated class to handle more gracefully error codes: [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code). I have no direct experience with it but it looks promising as illustrated by this [blog post](https://akrzemi1.wordpress.com/2017/07/12/your-own-error-code/). It should be noticed C++ 11 introduced a dedicated class to handle more gracefully error codes: [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code). I have no direct experience with it but it looks promising as illustrated by this [blog post](https://akrzemi1.wordpress.com/2017/07/12/your-own-error-code/).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### nodiscard ### nodiscard
The point about forgetting to check the value may however be mitigated since C++17 with the attribute [``nodiscard``](https://en.cppreference.com/w/cpp/language/attributes/nodiscard), which helps your compiler figure out the return value should have been checked. The point about forgetting to check the value may however be mitigated since C++17 with the attribute [``nodiscard``](https://en.cppreference.com/w/cpp/language/attributes/nodiscard), which helps your compiler figure out the return value should have been checked.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox clang %%cppmagics clang
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
constexpr auto INVALID_TYPE = -1; constexpr auto INVALID_TYPE = -1;
template<class T> template<class T>
[[nodiscard]] int AbsoluteValueNoDiscard(T value, T& result) [[nodiscard]] int AbsoluteValueNoDiscard(T value, T& result)
{ {
if constexpr (!std::is_arithmetic<T>()) if constexpr (!std::is_arithmetic<T>())
return INVALID_TYPE; return INVALID_TYPE;
else else
{ {
if (value < 0) if (value < 0)
result = -value; result = -value;
else else
result = value; result = value;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::string hello("Hello world"); std::string hello("Hello world");
std::string absolute_value = ""; std::string absolute_value = "";
AbsoluteValueNoDiscard(hello, absolute_value); // Now there is a warning! But only available after C++ 17... AbsoluteValueNoDiscard(hello, absolute_value); // Now there is a warning! But only available after C++ 17...
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [RAII idiom](./2-RAII.ipynb) # [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [RAII idiom](./2-RAII.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
This chapter is one of the most important in this tutorial: it is an idiom without which the most common critic against C++ is totally justified! This chapter is one of the most important in this tutorial: it is an idiom without which the most common critic against C++ is totally justified!
Often, people who criticizes the language say C++ is really tricky and that is extremely easy to leak memory all over the place, and that it sorely misses a [**garbage collector**](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) which does the job of cleaning-up and freeing the memory when the data are no longer used. Often, people who criticizes the language say C++ is really tricky and that is extremely easy to leak memory all over the place, and that it sorely misses a [**garbage collector**](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) which does the job of cleaning-up and freeing the memory when the data are no longer used.
However, garbage collection, used for instance in Python and Java, is not without issues itself: the memory is not always freed as swiftly as possible, and the bookkeeping of references is not free performance-wise. However, garbage collection, used for instance in Python and Java, is not without issues itself: the memory is not always freed as swiftly as possible, and the bookkeeping of references is not free performance-wise.
C++ provides in fact the best of both worlds: a way to provide safe freeing of memory as soon as possible... provided you know how to adequately use it. C++ provides in fact the best of both worlds: a way to provide safe freeing of memory as soon as possible... provided you know how to adequately use it.
The **Resource Acquisition Is Initialization** or **RAII** idiom is the key mechanism for this; the idea is just to use an object with: The **Resource Acquisition Is Initialization** or **RAII** idiom is the key mechanism for this; the idea is just to use an object with:
* The constructor in charge of allocating the resources (memory, mutexes, etc...) * The constructor in charge of allocating the resources (memory, mutexes, etc...)
* The destructor in charge of freeing all that as soon as the object becomes out-of-scope. * The destructor in charge of freeing all that as soon as the object becomes out-of-scope.
And that's it! And that's it!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Example: dynamic array ## Example: dynamic array
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
#include <iostream> #include <iostream>
class Array class Array
{ {
public: public:
Array(std::string name, std::size_t dimension); Array(std::string name, std::size_t dimension);
~Array(); ~Array();
private: private:
std::string name_; std::string name_;
double* underlying_array_ { nullptr }; double* underlying_array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cpptoolbox cppyy/cppdef %%cppmagics cppyy/cppdef
Array::Array(std::string name, std::size_t dimension) Array::Array(std::string name, std::size_t dimension)
: name_{name} : name_{name}
{ {
std::cout << "Acquire resources for " << name_ << std::endl; std::cout << "Acquire resources for " << name_ << std::endl;
underlying_array_ = new double[dimension]; underlying_array_ = new double[dimension];
for (auto i = 0ul; i < dimension; ++i) for (auto i = 0ul; i < dimension; ++i)
underlying_array_[i] = 0.; underlying_array_[i] = 0.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
Array::~Array() Array::~Array()
{ {
std::cout << "Release resources for " << name_ << std::endl; std::cout << "Release resources for " << name_ << std::endl;
delete[] underlying_array_; delete[] underlying_array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Array array1("Array 1", 5); Array array1("Array 1", 5);
{ {
Array array2("Array 2", 2); Array array2("Array 2", 2);
{ {
Array array3("Array 3", 2); Array array3("Array 3", 2);
} }
Array array4("Array 4", 4); Array array4("Array 4", 4);
} }
Array array5("Array 5", 19); Array array5("Array 5", 19);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, don't use such a class: STL `std::vector` and `std::array` are already there for that (and use up RAII principle under the hood!) and provide also more complicated mechanisms such as the copy. Of course, don't use such a class: STL `std::vector` and `std::array` are already there for that (and use up RAII principle under the hood!) and provide also more complicated mechanisms such as the copy.
The resource itself needs not be memory; for instance `std::ofstream` also use up RAII: its destructor calls `close()` if not done manually before, ensuring the file on disk features properly the changes you might have done on it during the run of your program. The resource itself needs not be memory; for instance `std::ofstream` also use up RAII: its destructor calls `close()` if not done manually before, ensuring the file on disk features properly the changes you might have done on it during the run of your program.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......