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 (77)
Showing
with 837 additions and 436 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:
## About the choice of a Jupyter notebook ## Jupyter notebook
This notebook uses up [xeus-cling](https://xeus-cling.readthedocs.io/en/latest/), a special instance of Jupyter able to run C++ code based upon xeus (tool to build Jupyter kernels for any language) and cling (a creation from CERN to be able to run C++ as an interpreted language). ### About the choice of a Jupyter notebook
The reasons for these choices is to really access directly to handle C++ code without the hassle of explaining how to compile and run stuff, which is an especially cumbersome way to start with this (or any really...) language. We made the choice to use a Jupyter notebook for conveniency:
This is not to say this tutorial will ignore entirely these topics (see the dedicated [chapter](./6-InRealEnvironment/0-main.ipynb)), just that we will first focus on C++ code. However keep in mind that this notebook's fancy interpreter is not a typical C++ environment. - 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)
- Notebooks are a cool tool to meddle explanations and actual code that is directly executable.
## When the notebook is not enough... 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).
As we shall see repeatedly, Xeus-cling notebooks are far from being full-proof: some stuff that are perfectly acceptable C++ aren't accepted in them, and some others required work-arounds. When such an issue appears: 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.
* It will be indicated explicitly in the notebook if a specific work around is used. We do not want you to take Jupyter work-arounds as a legit advice on how to write proper C++. 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.
* If Jupyter can't deal with the code, we will use [Coliru](https://coliru.stacked-crooked.com/). Coliru is a C++ online compiler; others are listed [here]([GitHub page](https://arnemertz.github.io/online-compilers/)) ([Wandbox](https://wandbox.org/) deserves a shout out as it enables testing the same code with a great variety of compiler versions).
We're not sure we'll keep using Jupyter notebooks in the future: development of Xeus-cling has a bit stalled in the recent years and support of more recent versions of C++ (20 and more) is still unclear. 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.
### 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.
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
%%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).
The most common such magics are:
- `%%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.
- `%%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:
%% Cell type:code id: tags:
``` c++
%%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++17 ``` c++
{ {
int foo = 5 // COMPILATION ERROR! int foo = 5 // COMPILATION ERROR!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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)
......
...@@ -35,16 +35,15 @@ ...@@ -35,16 +35,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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++17 ``` 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!
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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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! (and compilers usually warn against this, even if here Xeus-cling does not): **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++17 ``` 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:
%% Cell type:code id: tags:
``` c++
%%cppmagics clang
// < 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.
#include <cstddef>
#include <iostream>
int main(int argc, char** argv)
{
int a = 5;
{
int a = 10;
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;
}
a = a + 5; // this is the inner most 'a' that is modified
} // inner most a becomes out if scope
std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' "
"is the initial one: " << a << std::endl;
return EXIT_SUCCESS;
}
```
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
pi = 5.; // COMPILATION ERROR! pi = 5.; // COMPILATION ERROR!
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
p = &b; // COMPILATION ERROR p = &b; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` c++
*p = 5; // COMPILATION ERROR *p = 5; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
const int n { 3 }; const int n { 3 };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int* p { &n }; // COMPILATION ERROR int* p { &n }; // COMPILATION ERROR
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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) - [Conditions and loops](./2-Conditions-and-loops.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Conditions and loops](./2-Conditions-and-loops.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Conditions ## Conditions
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `if` condition followed by a single statement ### `if` condition followed by a single statement
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C++, a condition is the `if` command followed by a condition in parenthesis `()`. The single instruction (that ends at the next `;`) _or_ the block (in braces `{}`) that follows is then executed only if the condition is true. In C++, a condition is the `if` command followed by a condition in parenthesis `()`. The single instruction (that ends at the next `;`) _or_ the block (in braces `{}`) that follows is then executed only if the condition is true.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { 2 }; int a { 2 };
if (a > 0) if (a > 0)
std::cout << a << " is greater than 0" << std::endl; std::cout << a << " is greater than 0" << std::endl;
if (a < 0) if (a < 0)
std::cout << "This line won't be executed so nothing will be printed." << std::endl; std::cout << "This line won't be executed so nothing will be printed." << std::endl;
std::cout << "But this line will be printed: without braces `{}` only the first instruction depends " std::cout << "But this line will be printed: without braces `{}` only the first instruction depends "
"on the condition!" << std::endl; "on the condition!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, the precedent code is embarrassing but is due to the poor indenting used: you have to remember that in C++ indenting is just for the programmer: it is much easier to read if a program is properly indented, but it doesn't matter from the compiler standpoint! Of course, the precedent code is embarrassing but is due to the poor indenting used: you have to remember that in C++ indenting is just for the programmer: it is much easier to read if a program is properly indented, but it doesn't matter from the compiler standpoint!
Our case above would be much clearer however with a more logical indenting and spacing: Our case above would be much clearer however with a more logical indenting and spacing:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { 2 }; int a { 2 };
if (a < 0) if (a < 0)
std::cout << "This line won't be executed so nothing will be printed." << std::endl; std::cout << "This line won't be executed so nothing will be printed." << std::endl;
std::cout << "It's much clearer now that this line is not encompassed by the condition!" << std::endl; std::cout << "It's much clearer now that this line is not encompassed by the condition!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `if` condition followed by a block ### `if` condition followed by a block
And if you need several lines to be encompassed by the condition, just use braces! And if you need several lines to be encompassed by the condition, just use braces!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { 2 }; int a { 2 };
if (a < 0) if (a < 0)
{ {
std::cout << "Should not be printed: the condition is false!" << std::endl; std::cout << "Should not be printed: the condition is false!" << std::endl;
++a; // won't be executed: the condition is false ++a; // won't be executed: the condition is false
} }
std::cout << "a was not modified and is still 2: " << a << std::endl; std::cout << "a was not modified and is still 2: " << a << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### No semicolon at the end of the `if` condition ### No semicolon at the end of the `if` condition
__BEWARE__: do not put a `;` at the end of an `if` statement! If you do so, the statement executed if the condition is true is the empty statement `;` which does nothing... The risk is rather mitigated: any compiler worth its salt will warn you if you do this mistake. __BEWARE__: do not put a `;` at the end of an `if` statement! If you do so, the statement executed if the condition is true is the empty statement `;` which does nothing... The risk is rather mitigated: any compiler worth its salt will warn you if you do this mistake.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int a { 2 }; int a { 2 };
if (a == 0); if (a == 0);
std::cout << "Will be printed: the statement after the condition is ';', which does nothing..." << std::endl; std::cout << "Will be printed: the statement after the condition is ';', which does nothing..." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int a { 2 }; int a { 2 };
if (a == 2) if (a == 2)
; // putting the semicolon in a different line silences the warning; there are legitimate cases in ; // putting the semicolon in a different line silences the warning; there are legitimate cases in
// which it's useful to do so and the risk is // which it's useful to do so and the risk is
// slim that this was written by mistake. // slim that this was written by mistake.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### if ... else if ... else ### if ... else if ... else
It is rather usual to foreseen two (or more...) courses of action depending on the results of one or several conditions. The syntax in this case is the following: It is rather usual to foreseen two (or more...) courses of action depending on the results of one or several conditions. The syntax in this case is the following:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { 2 }; int a { 2 };
if (a < 0) if (a < 0)
std::cout << a << " is negative." << std::endl; std::cout << a << " is negative." << std::endl;
else if (a == 0) else if (a == 0)
{ {
std::cout << a << " is zero." << std::endl; std::cout << a << " is zero." << std::endl;
} }
else if (a < 10) else if (a < 10)
std::cout << a << " is positive but lower than 10." << std::endl; std::cout << a << " is positive but lower than 10." << std::endl;
else if (a < 20) else if (a < 20)
std::cout << a << " is in the interval [10, 20[." << std::endl; std::cout << a << " is in the interval [10, 20[." << std::endl;
else else
{ {
std::cout << a << "is greater than 20." << std::endl; std::cout << a << "is greater than 20." << std::endl;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice that: Please notice that:
* `else if` and `else` syntax is the same as the one for `if` and you may choose a single statement or a block in each branch of the condition. * `else if` and `else` syntax is the same as the one for `if` and you may choose a single statement or a block in each branch of the condition.
* As soon as one condition is fulfilled, the execution of the condition blocks ends. I therefore didn't have to spell out in the (`a < 20`) case that `a` had to be greater than 10. * As soon as one condition is fulfilled, the execution of the condition blocks ends. I therefore didn't have to spell out in the (`a < 20`) case that `a` had to be greater than 10.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The ternary operator ### The ternary operator
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ has inherited from C the so-called ternary operator, which is basically an if/else case packed in one line: C++ has inherited from C the so-called ternary operator, which is basically an if/else case packed in one line:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int i { 5 }; int i { 5 };
std::cout << "Is " << i << " even? -> " << (i % 2 == 0 ? "true" : "false") << std::endl; std::cout << "Is " << i << " even? -> " << (i % 2 == 0 ? "true" : "false") << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is really the same as: It is really the same as:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int i { 5 }; int i { 5 };
std::cout << "Is " << i << " even? -> "; std::cout << "Is " << i << " even? -> ";
if (i % 2 == 0) if (i % 2 == 0)
std::cout << "true" << std::endl; std::cout << "true" << std::endl;
else else
std::cout << "false" << std::endl; std::cout << "false" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are nonetheless some cases in which a ternary operator performs a task that is not otherwise reachable by an if/else statement; please consider for instance the initialisation of a `const` variable: There are nonetheless some cases in which a ternary operator performs a task that is not otherwise reachable by an if/else statement; please consider for instance the initialisation of a `const` variable:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int i { 5 }; int i { 5 };
const int is_strictly_positive = (i > 0 ? 1 : 0); const int is_strictly_positive = (i > 0 ? 1 : 0);
std::cout << is_strictly_positive << std::endl; std::cout << is_strictly_positive << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With if/else this doesn't work: both ways to do so that might come to mind are flawed: With if/else this doesn't work: both ways to do so that might come to mind are flawed:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int i { 5 }; int i { 5 };
const int is_strictly_positive; const int is_strictly_positive;
if (i > 0) if (i > 0)
is_strictly_positive = 1; // COMPILATION ERROR: can't assign to const value! is_strictly_positive = 1; // COMPILATION ERROR: can't assign to const value!
// ... // ...
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int i { 5 }; int i { 5 };
if (i > 0) if (i > 0)
const int is_strictly_positive = 1; const int is_strictly_positive = 1;
else else
const int is_strictly_positive = 0; const int is_strictly_positive = 0;
std::cout << is_strictly_positive << std::endl; // COMPILATION ERROR: is_strictly_positive not std::cout << is_strictly_positive << std::endl; // COMPILATION ERROR: is_strictly_positive not
// in the current scope. // in the current scope.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `switch` statement ### `switch` statement
Very briefly: there is also a `switch` statement that can be used when: Very briefly: there is also a `switch` statement that can be used when:
* The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those. * The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those.
* The relationship considered is an equality. * The relationship considered is an equality.
I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` present in other languages...) I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` present in other languages...)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Logical operators ## Logical operators
A condition might be an amalgation of several conditions. The way to glue it is to use logical operators. A condition might be an amalgation of several conditions. The way to glue it is to use logical operators.
The operators are: The operators are:
* `&&` for the **and** operator. * `&&` for the **and** operator.
* `||` for the **or** operator. * `||` for the **or** operator.
* `!` for the **not** operator. * `!` for the **not** operator.
Actually there are so-called __alternative representations__ using the english name directly for each of them but their usage is not widely spread and not advised from a stylistic standpoint (many StackOverflow threads debate on this topic...). Actually there are so-called __alternative representations__ using the english name directly for each of them but their usage is not widely spread and not advised from a stylistic standpoint (many StackOverflow threads debate on this topic...).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
int a { 2 }; int a { 2 };
int b { 3 }; int b { 3 };
if (a == 2 || b == 5) if (a == 2 || b == 5)
std::cout << "Ok: first condition is true." << std::endl; std::cout << "Ok: first condition is true." << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if (a == 2 or b == 5) if (a == 2 or b == 5)
std::cout << "Same as above illustrating the alternative representation." << std::endl; std::cout << "Same as above illustrating the alternative representation." << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if (a == 2 && b == 5) if (a == 2 && b == 5)
std::cout << "Not printed: one condition is false." << std::endl; std::cout << "Not printed: one condition is false." << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if (!(a < 0)) if (!(a < 0))
std::cout << "Ok: a < 0 is false so the `not` operator returns true." << std::endl; std::cout << "Ok: a < 0 is false so the `not` operator returns true." << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may combine several of them in a more complex condition. `and` takes precedence over `or`, but anyway it's usually better to disambiguate using parenthesis: You may combine several of them in a more complex condition. `and` takes precedence over `or`, but anyway it's usually better to disambiguate using parenthesis:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if (a == 5 || a == 2 && b == 3) if (a == 5 || a == 2 && b == 3)
std::cout << "(a == 5) and (a == 2 && b == 3) are evaluated separately and the latter is true so this text is printed" << std::endl; std::cout << "(a == 5) and (a == 2 && b == 3) are evaluated separately and the latter is true so this text is printed" << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if ((a == 5) || (a == 2 && b == 3)) if ((a == 5) || (a == 2 && b == 3))
std::cout << "Same but easier to grasp." << std::endl; std::cout << "Same but easier to grasp." << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first. Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first.
Please notice this means these operators are **not commutative**! Please notice this means these operators are **not commutative**!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
int a { 2 }; int a { 2 };
int b { 3 }; int b { 3 };
if (a < 0 && b++ == 3) if (a < 0 && b++ == 3)
; ;
std::cout << "b was not incremented!: " << b << std::endl; std::cout << "b was not incremented!: " << b << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The story is different if the ordering of the conditions to test is inverted: The story is different if the ordering of the conditions to test is inverted:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
if (b++ == 3 && a < 0) if (b++ == 3 && a < 0)
; ;
std::cout << "b was incremented: " << b << std::cout << "b was incremented: " << b <<
" (as first operation is true second one is evaluated)." << std::endl; " (as first operation is true second one is evaluated)." << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands. Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Loops ## Loops
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `while` loop ### `while` loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `while` instruction allows you to execute a block of instructions in a loop The `while` instruction allows you to execute a block of instructions in a loop
as long as a condition is true. The condition is checked **before** each as long as a condition is true. The condition is checked **before** each
iteration. iteration.
The rules about the instructions belonging to the loop are exactly the same as the ones described for `if` statements. The rules about the instructions belonging to the loop are exactly the same as the ones described for `if` statements.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { }; int a { };
while (a++ < 5) while (a++ < 5)
std::cout << a << std::endl; std::cout << a << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If the condition is never true in the first place, we never go inside the loop: If the condition is never true in the first place, we never go inside the loop:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { }; int a { };
while (a-- < 0) while (a-- < 0)
std::cout << a << std::endl; std::cout << a << std::endl;
std::cout << "Done!" << std::endl; std::cout << "Done!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `do`...`while` loop ### `do`...`while` loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `do` instruction allows you to execute a block of instructions in a loop as long as The `do` instruction allows you to execute a block of instructions in a loop as long as
that a condition is true. The condition is verified **after** each that a condition is true. The condition is verified **after** each
iteration. iteration.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int a { }; int a { };
do do
{ {
std::cout << a << std::endl; std::cout << a << std::endl;
} while (a++ < 0); // there is a semicolon here. } while (a++ < 0); // there is a semicolon here.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `for` loop ### `for` loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Historical `for` loop #### Historical `for` loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The historical `for` instruction allows you to execute a block of instructions as long as a The historical `for` instruction allows you to execute a block of instructions as long as a
condition is true; the difference with the `while` loop is that there are fields explicitly detailing: condition is true; the difference with the `while` loop is that there are fields explicitly detailing:
* The initial situation. * The initial situation.
* The condition to check at the end of each loop. * The condition to check at the end of each loop.
* What should be changed in next loop if one is called for. * What should be changed in next loop if one is called for.
Syntax is: Syntax is:
for (_initial situation_ ; _end loop condition_ ; *evolution for next loop*) for (_initial situation_ ; _end loop condition_ ; *evolution for next loop*)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
std::cout << i << std::endl; std::cout << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Any of these fields might be left empty: Any of these fields might be left empty:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int i { }; // i might also be declared and initialized outside the block int i { }; // i might also be declared and initialized outside the block
for ( ; i < 5; ) // completely equivalent to `while(i < 5)` for ( ; i < 5; ) // completely equivalent to `while(i < 5)`
{ {
++i; // i might also be modified within the block ++i; // i might also be modified within the block
std::cout << i << std::endl; std::cout << i << std::endl;
} }
std::cout << "`for` loop stopped for i = " << i << std::endl; std::cout << "`for` loop stopped for i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### New `for` loop #### New `for` loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `for`syntax we just saw is still very useful, but C++ 11 introduced a new syntax when for instance you want to iterate through all the items in a container, clearly taking inspiration from syntax present in languages such as Python: The `for`syntax we just saw is still very useful, but C++ 11 introduced a new syntax when for instance you want to iterate through all the items in a container, clearly taking inspiration from syntax present in languages such as Python:
for (_type_ _element_ : *container*) for (_type_ _element_ : *container*)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
#include <vector> // we'll present this one more in detail later #include <vector> // we'll present this one more in detail later
{ {
std::vector<int> v { 2, 3, 5, 7 }; std::vector<int> v { 2, 3, 5, 7 };
for (int item : v) for (int item : v)
std::cout << item << std::endl; std::cout << item << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It might not seem much, but just for the record doing the same before C++ 11 was not for the faint of heart...: It might not seem much, but just for the record doing the same before C++ 11 was not for the faint of heart...:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
#include <vector> // we'll present this one more in detail later #include <vector> // we'll present this one more in detail later
{ {
std::vector<int> v { 2, 3, 5, 7 }; // and we're cheating here: C++ 03 syntax was here much worse as well... std::vector<int> v { 2, 3, 5, 7 }; // and we're cheating here: C++ 03 syntax was here much worse as well...
for (std::vector<int>::const_iterator it = v.cbegin(), for (std::vector<int>::const_iterator it = v.cbegin(),
end = v.cend(); end = v.cend();
it != end; it != end;
++it) ++it)
{ {
std::cout << *it << std::endl; std::cout << *it << std::endl;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### continue, break and infinite loop ### continue, break and infinite loop
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A danger with a loop is to make it infinite: you have to make sure an exit way is foreseen: A danger with a loop is to make it infinite: you have to make sure an exit way is foreseen:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
// WARNING: if you run this you will have to restart your kernel! (in Kernel menu) // WARNING: if you run this you will have to restart your kernel! (in Kernel menu)
{ {
int i { 2 }; int i { 2 };
while (i > 0) // condition that is true for quite a long time... while (i > 0) // condition that is true for quite a long time...
std::cout << ++i << " "; std::cout << ++i << " ";
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The best is to write a palatable condition to end it, but when some loops become increasingly complex you may have to resort to `break`. Be aware it is slightly frowned upon by some programmers, for the very same reasons [goto](https://en.wikipedia.org/wiki/Goto) instructions are avoided. The best is to write a palatable condition to end it, but when some loops become increasingly complex you may have to resort to `break`. Be aware it is slightly frowned upon by some programmers, for the very same reasons [goto](https://en.wikipedia.org/wiki/Goto) instructions are avoided.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int i { 2 }; int i { 2 };
while (i > 0) while (i > 0)
{ {
std::cout << i++ << " "; std::cout << i++ << " ";
if (i > 20) if (i > 20)
break; break;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this trivial case writing the condition more properly would be of course much better: In this trivial case writing the condition more properly would be of course much better:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int i { 2 }; int i { 2 };
while (i > 0 && i <= 20) while (i > 0 && i <= 20)
std::cout << i++ << " "; std::cout << i++ << " ";
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
but honestly in more complex cases `break` can help keep the code more readable. but honestly in more complex cases `break` can help keep the code more readable.
`continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next: `continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
for (int i = 2; i < 20; ++i) for (int i = 2; i < 20; ++i)
{ {
if (i == 2) if (i == 2)
{ {
std::cout << i << " is even and prime (hello 2!)." << std::endl; std::cout << i << " is even and prime (hello 2!)." << std::endl;
continue; continue;
} }
if (i % 2 == 0) if (i % 2 == 0)
{ {
std::cout << i << " is even." << std::endl; std::cout << i << " is even." << std::endl;
continue; // goes directly at the condition checking step in the loop, continue; // goes directly at the condition checking step in the loop,
// skipping the remaining code below. // skipping the remaining code below.
} }
std::cout << i << " is odd"; std::cout << i << " is odd";
bool is_prime = true; bool is_prime = true;
for (int j = 2; j < i / 2; ++j) for (int j = 2; j < i / 2; ++j)
{ {
if (i % j == 0) // % returns the remainder of the division if (i % j == 0) // % returns the remainder of the division
{ {
is_prime = false; is_prime = false;
break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)' break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)'
} }
} }
std::cout << (is_prime ? " and prime." : ".") << std::endl; // the ternary operator std::cout << (is_prime ? " and prime." : ".") << std::endl; // the ternary operator
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, in a trivial example like this one we could have written it much more cleanly without any `continue`, but in more complex cases it is really handful to use it: not using it could lead to code much more complicated to understand, and you really should always strive for code that is the most expressive for a reader. Of course, in a trivial example like this one we could have written it much more cleanly without any `continue`, but in more complex cases it is really handful to use it: not using it could lead to code much more complicated to understand, and you really should always strive for code that is the most expressive for a reader.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### So which loop should I use? ### So which loop should I use?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Whichever you want in fact! Whichever you want in fact!
They are mostly interchangeable: They are mostly interchangeable:
* `while` and (historical) `for` are completely interchangeable, as: * `while` and (historical) `for` are completely interchangeable, as:
- A `while` loop is exactly like a `for ` loop with only the middle term. - A `while` loop is exactly like a `for ` loop with only the middle term.
- You can transform a `for` loop into a `while` one: putting the first term before the loop and the third one inside the loop to do so. - You can transform a `for` loop into a `while` one: putting the first term before the loop and the third one inside the loop to do so.
* `do..while` behaves slightly differently, but you can always mimic the behaviour with another type of loop. * `do..while` behaves slightly differently, but you can always mimic the behaviour with another type of loop.
Lots of programming language define these guys (at least `for` and `while`) so it's useful to know about them, but you can choose one and stick with it as well. Lots of programming language define these guys (at least `for` and `while`) so it's useful to know about them, but you can choose one and stick with it as well.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -79,16 +79,15 @@ ...@@ -79,16 +79,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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 `float` 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++17 ``` c++
{ {
double a = static_cast<float>(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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17
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++17 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
int ComputeMinimum(int a, int b); int ComputeMinimum(int a, int b);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` c++
int ReturnFive(); // providing a parameter is also optional... int ReturnFive(); // providing a parameter is also optional...
// Don't bother Xeus-cling 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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:code id: tags: %% Cell type:markdown id: tags:
``` C++17 In the above example, the lonely parameter of the function `IncrementAndPrint` was named `value`, whereas at call site the variable was simply named `i`.
#include <iostream>
void IncrementAndPrint2(int local_argument) 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:
``` c++
void IncrementAndPrintUseI(int i)
{ {
++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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
auto Sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment %%cppmagics cppyy/cppdef
// < 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
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The return type is optional (and was the reason of Xeus-cling failure): The return type is optional:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
auto Sum(int a, int b) // compiles just fine in Xeus-cling auto SumWithoutExplicitReturnType(int a, int b)
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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;
``` ```
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
%%cppmagics cppyy/cppdef
// < 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++17 ``` c++
%%cppmagics cppyy/cppdef
// < 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++17 ``` c++
// WARNING: Xeus-cling issue!
{ {
FunctionWithOptional(3., 5., 6.); // ok FunctionWithOptional(3., 5., 6.);
FunctionWithOptional(3.); // should be ok, but Xeus-cling issue. 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
#define SQUARE(x) ((x) * (x)) // macro #define SQUARE(x) ((x) * (x)) // macro
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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) - [Hands-on 2](./4b-hands-on.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 2](./4b-hands-on.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 2: Adding a function__ ### __EXERCISE 2: Adding a function__
Introduce in the previous program a function called `DisplayPowerOf2Approx()` which takes as argument the actual value to be approximated. Modify the main program to call this function with values 0.65 and 0.35. The main program must be: Introduce in the previous program a function called `DisplayPowerOf2Approx()` which takes as argument the actual value to be approximated. Modify the main program to call this function with values 0.65 and 0.35. The main program must be:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
DisplayPowerOf2Approx(.65); DisplayPowerOf2Approx(.65);
DisplayPowerOf2Approx(.35); DisplayPowerOf2Approx(.35);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
*Expected result*: *Expected result*:
0.65 ~ 1 / 2^1 0.65 ~ 1 / 2^1
0.65 ~ 3 / 2^2 0.65 ~ 3 / 2^2
0.65 ~ 5 / 2^3 0.65 ~ 5 / 2^3
0.65 ~ 10 / 2^4 0.65 ~ 10 / 2^4
0.65 ~ 21 / 2^5 0.65 ~ 21 / 2^5
0.65 ~ 42 / 2^6 0.65 ~ 42 / 2^6
0.65 ~ 83 / 2^7 0.65 ~ 83 / 2^7
0.65 ~ 166 / 2^8 0.65 ~ 166 / 2^8
0.35 ~ 1 / 2^1 0.35 ~ 1 / 2^1
0.35 ~ 1 / 2^2 0.35 ~ 1 / 2^2
0.35 ~ 3 / 2^3 0.35 ~ 3 / 2^3
0.35 ~ 6 / 2^4 0.35 ~ 6 / 2^4
0.35 ~ 11 / 2^5 0.35 ~ 11 / 2^5
0.35 ~ 22 / 2^6 0.35 ~ 22 / 2^6
0.35 ~ 45 / 2^7 0.35 ~ 45 / 2^7
0.35 ~ 90 / 2^8 0.35 ~ 90 / 2^8
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 3: Compute the approximation__ ### __EXERCISE 3: Compute the approximation__
Add in the `DisplayPowerOf2Approx()` function the display of the approximate value, which is calculated by dividing each numerator by the power of two associated. Be careful, dividing an integer by an integer returns an integer: for instance 3 / 4 is 0. A division returns a real if one of the terms is real: 3. / 4 or `static_cast<double>`(3) / 4 is 0.75. Add in the `DisplayPowerOf2Approx()` function the display of the approximate value, which is calculated by dividing each numerator by the power of two associated. Be careful, dividing an integer by an integer returns an integer: for instance 3 / 4 is 0. A division returns a real if one of the terms is real: 3. / 4 or `static_cast<double>`(3) / 4 is 0.75.
We will modify the display slightly so that the output looks like: We will modify the display slightly so that the output looks like:
``` ```
0.65 ~ 0.5 (1 / 2^1) 0.65 ~ 0.5 (1 / 2^1)
0.65 ~ 0.75 (3 / 2^2) 0.65 ~ 0.75 (3 / 2^2)
0.65 ~ 0.625 (5 / 2^3) 0.65 ~ 0.625 (5 / 2^3)
0.65 ~ 0.625 (10 / 2^4) 0.65 ~ 0.625 (10 / 2^4)
0.65 ~ 0.65625 (21 / 2^5) 0.65 ~ 0.65625 (21 / 2^5)
0.65 ~ 0.65625 (42 / 2^6) 0.65 ~ 0.65625 (42 / 2^6)
0.65 ~ 0.648438 (83 / 2^7) 0.65 ~ 0.648438 (83 / 2^7)
0.65 ~ 0.648438 (166 / 2^8) 0.65 ~ 0.648438 (166 / 2^8)
0.35 ~ 0.5 (1 / 2^1) 0.35 ~ 0.5 (1 / 2^1)
0.35 ~ 0.25 (1 / 2^2) 0.35 ~ 0.25 (1 / 2^2)
0.35 ~ 0.375 (3 / 2^3) 0.35 ~ 0.375 (3 / 2^3)
0.35 ~ 0.375 (6 / 2^4) 0.35 ~ 0.375 (6 / 2^4)
0.35 ~ 0.34375 (11 / 2^5) 0.35 ~ 0.34375 (11 / 2^5)
0.35 ~ 0.34375 (22 / 2^6) 0.35 ~ 0.34375 (22 / 2^6)
0.35 ~ 0.351562 (45 / 2^7) 0.35 ~ 0.351562 (45 / 2^7)
0.35 ~ 0.351562 (90 / 2^8) 0.35 ~ 0.351562 (90 / 2^8)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 4: Search for the best approximation for a given maximum numerator.__ ### __EXERCISE 4: Search for the best approximation for a given maximum numerator.__
The larger the numerator and the exponent to the denominator, the more accurate the approximation. The larger the numerator and the exponent to the denominator, the more accurate the approximation.
In `DisplayPowerOf2Approx()`, modify the loop so that it looks for the best numerator / exponent pair, without the numerator exceeding a certain maximum value, passed as an argument to the function. In `DisplayPowerOf2Approx()`, modify the loop so that it looks for the best numerator / exponent pair, without the numerator exceeding a certain maximum value, passed as an argument to the function.
Keep the display only for this best solution, and add in this display the value of the maximum allowed numerator. Keep the display only for this best solution, and add in this display the value of the maximum allowed numerator.
Use the following `main()` function to check it works: Use the following `main()` function to check it works:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
DisplayPowerOf2Approx(15, 0.65); DisplayPowerOf2Approx(15, 0.65);
DisplayPowerOf2Approx(255, 0.65); DisplayPowerOf2Approx(255, 0.65);
DisplayPowerOf2Approx(15, 0.35); DisplayPowerOf2Approx(15, 0.35);
DisplayPowerOf2Approx(255, 0.35); DisplayPowerOf2Approx(255, 0.35);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Expected result is something like: Expected result is something like:
``` ```
[With numerator < 15]: 0.65 ~ 0.625 (10 / 2^4) [With numerator < 15]: 0.65 ~ 0.625 (10 / 2^4)
[With numerator < 255]: 0.65 ~ 0.648438 (166 / 2^8) [With numerator < 255]: 0.65 ~ 0.648438 (166 / 2^8)
[With numerator < 15]: 0.35 ~ 0.34375 (11 / 2^5) [With numerator < 15]: 0.35 ~ 0.34375 (11 / 2^5)
[With numerator < 255]: 0.35 ~ 0.349609 (179 / 2^9) [With numerator < 255]: 0.35 ~ 0.349609 (179 / 2^9)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 5: Computation of the maximum numerator as a function of the number of bits__ ### __EXERCISE 5: Computation of the maximum numerator as a function of the number of bits__
The highest usable value for the numerator depends on the number of bits used to represent this integer. The highest usable value for the numerator depends on the number of bits used to represent this integer.
In `DisplayPowerOf2Approx()` arguments, replace the argument designating the maximum numerator with an argument designating the maximum number of bits and correct the body of the function accordingly, using the `MaxInt()` function given below. In `DisplayPowerOf2Approx()` arguments, replace the argument designating the maximum numerator with an argument designating the maximum number of bits and correct the body of the function accordingly, using the `MaxInt()` function given below.
On display, replace the maximum numerator with the number of bits. On display, replace the maximum numerator with the number of bits.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Declaration // Declaration
/*! /*!
* \brief Maximum integer that might be represented with `nbits` bits. * \brief Maximum integer that might be represented with `nbits` bits.
* *
* \param[in] nbits Number of bits available. * \param[in] nbits Number of bits available.
* *
* \return Biggest integer that may be represented. * \return Biggest integer that may be represented.
*/ */
int MaxInt(int nbits); int MaxInt(int nbits);
// Definition // Definition
int MaxInt(int nbits) int MaxInt(int nbits)
{ {
return (TimesPowerOf2(1, nbits) - 1); return (TimesPowerOf2(1, nbits) - 1);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**WARNING:** If you do not separate declaration and definition explicitly, `MaxInt()` must be located _before_ `DisplayPowerOf2Approx()`. **WARNING:** If you do not separate declaration and definition explicitly, `MaxInt()` must be located _before_ `DisplayPowerOf2Approx()`.
Use the following `main()` function: Use the following `main()` function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65); DisplayPowerOf2Approx(nbits, 0.65);
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35); DisplayPowerOf2Approx(nbits, 0.35);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
*Expected result*: *Expected result*:
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2)
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4)
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6)
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8)
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3)
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5)
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7)
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 6: moving display to a new intermediate function__ ### __EXERCISE 6: moving display to a new intermediate function__
Currently, `DisplayPowerOf2Approx()` is in charge of two different operations: Currently, `DisplayPowerOf2Approx()` is in charge of two different operations:
* Computing the values. * Computing the values.
* Displaying them on screen. * Displaying them on screen.
It is often advisable to give one main functionality to a given function; we will therefore separate here the computation from the display. It is often advisable to give one main functionality to a given function; we will therefore separate here the computation from the display.
Write a new function named `ComputePowerOf2Approx()` that will be called in `DisplayPowerOf2Approx()`. Write a new function named `ComputePowerOf2Approx()` that will be called in `DisplayPowerOf2Approx()`.
This new function should: This new function should:
* Return the floating point approximation. * Return the floating point approximation.
* Return (with reference parameters) the numerator and the exponent (that are displayed on screen). * Return (with reference parameters) the numerator and the exponent (that are displayed on screen).
Output should remain the same! Output should remain the same!
**WARNING:** If you do not separate declaration and definition explicitly, `ComputePowerOf2Approx()` must be located _before_ `DisplayPowerOf2Approx()`. **WARNING:** If you do not separate declaration and definition explicitly, `ComputePowerOf2Approx()` must be located _before_ `DisplayPowerOf2Approx()`.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 7: adding error in display__ ### __EXERCISE 7: adding error in display__
Add in `DisplayPowerOf2Approx()` the relative error of the approximation, computed by Add in `DisplayPowerOf2Approx()` the relative error of the approximation, computed by
``` ```
|number - approx| / number |number - approx| / number
``` ```
presented as a percentage and rounded to the nearest integer. presented as a percentage and rounded to the nearest integer.
Absolute value may be computed with `std::fabs`, which is in header file `cmath`. Absolute value may be computed with `std::fabs`, which is in header file `cmath`.
*Expected result*: *Expected result*:
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
``` ```
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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).
**WARNING** This works only with a recent version of Xeus-cling.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <random> %%cppmagics std::cin/clang
// 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.
#include <cstddef>
#include <iostream> #include <iostream>
#include <random>
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;
} }
``` ```
%% 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++17 ``` c++
#include <random> %%cppmagics std::cin/clang
#include <iostream> // 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.
#include <cstddef>
#include <iostream>
#include <random>
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;
} }
``` ```
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
PrintOnStream(std::cout); PrintOnStream(std::cout);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 3](./7b-hands-on.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 3](./7b-hands-on.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 8: Multiplication by an integer__ ### __EXERCISE 8: Multiplication by an integer__
Write the `Multiply()` function that calculates the product of an approximated real by an integer coefficient and returns an integer. Write the `Multiply()` function that calculates the product of an approximated real by an integer coefficient and returns an integer.
*Reminder*: a real is approximated by `numerator / 2^exponent`; your function should rely upon `ComputePowerOf2Approx()` and `TimesPowerOf2()` functions. *Reminder*: a real is approximated by `numerator / 2^exponent`; your function should rely upon `ComputePowerOf2Approx()` and `TimesPowerOf2()` functions.
The arguments of `Multiply()` are the maximum number of bits for approximation, the real and the integer coefficient. The arguments of `Multiply()` are the maximum number of bits for approximation, the real and the integer coefficient.
The new main will be: The new main will be:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65) ; DisplayPowerOf2Approx(nbits, 0.65) ;
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35) ; DisplayPowerOf2Approx(nbits, 0.35) ;
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
{ {
double exact = 0.65 * 3515; double exact = 0.65 * 3515;
int rounded = RoundAsInt(exact); int rounded = RoundAsInt(exact);
int approx = Multiply(nbits, 0.65, 3515); int approx = Multiply(nbits, 0.65, 3515);
std::cout << "[With " << nbits << " bits]: 0.65 * 3515 = " std::cout << "[With " << nbits << " bits]: 0.65 * 3515 = "
<< rounded << " ~ " << approx << std::endl; << rounded << " ~ " << approx << std::endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
_Expected result:_ _Expected result:_
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 = 2285 ~ 1757 [With 1 bits]: 0.65 * 3515 = 2285 ~ 1757
[With 2 bits]: 0.65 * 3515 = 2285 ~ 2636 [With 2 bits]: 0.65 * 3515 = 2285 ~ 2636
[With 3 bits]: 0.65 * 3515 = 2285 ~ 2196 [With 3 bits]: 0.65 * 3515 = 2285 ~ 2196
[With 4 bits]: 0.65 * 3515 = 2285 ~ 2196 [With 4 bits]: 0.65 * 3515 = 2285 ~ 2196
[With 5 bits]: 0.65 * 3515 = 2285 ~ 2306 [With 5 bits]: 0.65 * 3515 = 2285 ~ 2306
[With 6 bits]: 0.65 * 3515 = 2285 ~ 2306 [With 6 bits]: 0.65 * 3515 = 2285 ~ 2306
[With 7 bits]: 0.65 * 3515 = 2285 ~ 2279 [With 7 bits]: 0.65 * 3515 = 2285 ~ 2279
[With 8 bits]: 0.65 * 3515 = 2285 ~ 2279 [With 8 bits]: 0.65 * 3515 = 2285 ~ 2279
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 9: display sum of two Multiply__ ### __EXERCISE 9: display sum of two Multiply__
Write a `DisplaySumOfMultiply` function which will write the computation of the sum of two approximated real numbers that have each been multiplied by a different integer coefficient. Write a `DisplaySumOfMultiply` function which will write the computation of the sum of two approximated real numbers that have each been multiplied by a different integer coefficient.
The function is expected to compute and display the following: The function is expected to compute and display the following:
$$ $$
\displaystyle r_1 \cdot c_1 + r_2 \cdot c_2 \simeq (a_1 \cdot 2^{-b_1}) \cdot c_1 + (a_2 \cdot 2^{-b_2}) \cdot c_2 \displaystyle r_1 \cdot c_1 + r_2 \cdot c_2 \simeq (a_1 \cdot 2^{-b_1}) \cdot c_1 + (a_2 \cdot 2^{-b_2}) \cdot c_2
$$ $$
This function will take 5 arguments: This function will take 5 arguments:
* The number of bits to use. * The number of bits to use.
* Two real values. * Two real values.
* Their associated coefficients. * Their associated coefficients.
New main will look like: New main will look like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65); DisplayPowerOf2Approx(nbits, 0.65);
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35); DisplayPowerOf2Approx(nbits, 0.35);
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
*Expected result*: *Expected result*:
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### __EXERCISE 10: print error in `DisplaySumOfMultiply()`__ ### __EXERCISE 10: print error in `DisplaySumOfMultiply()`__
Modify slightly the function defined above to add display of the error; we will express it over 1000 (see exercise 7 which was roughly the same!) Modify slightly the function defined above to add display of the error; we will express it over 1000 (see exercise 7 which was roughly the same!)
*Expected result*: *Expected result*:
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [optional] __EXERCISE 11: write in output file__ ### [optional] __EXERCISE 11: write in output file__
Modify the program so that the `DisplayPowerOf2Approx` and `DisplaySumOfMultiply` functions take an additional argument: the output stream to which the content should be written. Modify the program so that the `DisplayPowerOf2Approx` and `DisplaySumOfMultiply` functions take an additional argument: the output stream to which the content should be written.
The following `main()` which writes part of the outputs in a file should work: The following `main()` which writes part of the outputs in a file should work:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::ofstream out("/tmp/approx_0.65.txt"); std::ofstream out("/tmp/approx_0.65.txt");
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(out, nbits, 0.65); DisplayPowerOf2Approx(out, nbits, 0.65);
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(std::cout, nbits, 0.35); DisplayPowerOf2Approx(std::cout, nbits, 0.35);
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
DisplaySumOfMultiply(std::cout, nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 DisplaySumOfMultiply(std::cout, nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
*Expected result*: *Expected result*:
``` ```
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and `cat /tmp/approx_0.65.txt` should return: and `cat /tmp/approx_0.65.txt` should return:
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [optional] __EXERCISE 12: function pointers__ ### [optional] __EXERCISE 12: function pointers__
Create a `Loop()` function that takes as an argument : Create a `Loop()` function that takes as an argument :
* The output stream * The output stream
* An initial number of bits * An initial number of bits
* A final number of bits * A final number of bits
* An increment to be applied to the number of bits * An increment to be applied to the number of bits
* A pointer to a function to be executed for each number of bits * A pointer to a function to be executed for each number of bits
You will need the following intermediate function to be able to use them in `Loop()` (as a specific signature is expected): You will need the following intermediate function to be able to use them in `Loop()` (as a specific signature is expected):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Declarations // Declarations
void Display_065(std::ostream& out, int nbits); void Display_065(std::ostream& out, int nbits);
void Display_035(std::ostream& out, int nbits); void Display_035(std::ostream& out, int nbits);
void Display_065_3515_035_4832(std::ostream& out, int nbits); void Display_065_3515_035_4832(std::ostream& out, int nbits);
// Definitions // Definitions
void Display_065(std::ostream& out, int nbits) void Display_065(std::ostream& out, int nbits)
{ {
DisplayPowerOf2Approx(out, nbits, 0.65); DisplayPowerOf2Approx(out, nbits, 0.65);
} }
void Display_035(std::ostream& out, int nbits) void Display_035(std::ostream& out, int nbits)
{ {
DisplayPowerOf2Approx(out, nbits, 0.35); DisplayPowerOf2Approx(out, nbits, 0.35);
} }
void Display_065_3515_035_4832(std::ostream& out, int nbits) void Display_065_3515_035_4832(std::ostream& out, int nbits)
{ {
DisplaySumOfMultiply(out, nbits, 0.65, 3515, 0.35, 4832); DisplaySumOfMultiply(out, nbits, 0.65, 3515, 0.35, 4832);
} }
// Main // Main
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::ofstream out("/tmp/approx_0.65.txt"); std::ofstream out("/tmp/approx_0.65.txt");
Loop(out, 2, 8, 2, Display_065); Loop(out, 2, 8, 2, Display_065);
Loop(std::cout, 2, 8, 2, Display_035); Loop(std::cout, 2, 8, 2, Display_035);
std::cout << std::endl; std::cout << std::endl;
Loop(std::cout, 1, 8, 1, Display_065_3515_035_4832); Loop(std::cout, 1, 8, 1, Display_065_3515_035_4832);
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++](/) - [Procedural programming](./0-main.ipynb) - [Static and constexpr](./7-StaticAndConstexpr.ipynb) # [Getting started in C++](/) - [Procedural programming](./0-main.ipynb) - [Static and constexpr](./7-StaticAndConstexpr.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static keyword ## Static keyword
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_. `static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.
A reply to this [StackOverflow question](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) gives a rather good summary of what `static` means in C (we shall see [later](../2-ObjectProgramming/5-static.ipynb) a C++-only keyword that unfortunately share the same moniker): A reply to this [StackOverflow question](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) gives a rather good summary of what `static` means in C (we shall see [later](../2-ObjectProgramming/5-static.ipynb) a C++-only keyword that unfortunately share the same moniker):
* Static defined local variables do not lose their value between function calls. In other words they are global variables, but scoped to the local function they are defined in. * Static defined local variables do not lose their value between function calls. In other words they are global variables, but scoped to the local function they are defined in.
* Static global variables are not visible outside of the C file they are defined in. * Static global variables are not visible outside of the C file they are defined in.
* Static functions are not visible outside of the C file they are defined in. * Static functions are not visible outside of the C file they are defined in.
Only the first one is really relevant in C++, as for variables that should be accessible only in the current file C++ provides a concept of his own: the [**unnamed namespace**](../6-InRealEnvironment/5-Namespace.ipynb#Unnamed-namespace). Only the first one is really relevant in C++, as for variables that should be accessible only in the current file C++ provides a concept of his own: the [**unnamed namespace**](../6-InRealEnvironment/5-Namespace.ipynb#Unnamed-namespace).
Let's see this first case in action: Let's see this first case in action:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void FunctionWithStatic() void FunctionWithStatic()
{ {
static int n = 0; // This initialisation occurs only at first call static int n = 0; // This initialisation occurs only at first call
// But `n` is not destroyed when the end bracket is reached and remains available // But `n` is not destroyed when the end bracket is reached and remains available
// in subsequent calls. However, `n` is available only inside this function. // in subsequent calls. However, `n` is available only inside this function.
std::cout << "The function has been called " << ++n << " times." << std::endl; std::cout << "The function has been called " << ++n << " times." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
FunctionWithStatic(); FunctionWithStatic();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It might be used for instance if you need to initialize something on the very first call of a function: It might be used for instance if you need to initialize something on the very first call of a function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Pseudo-code // Pseudo-code
void FunctionWithStatic() void FunctionWithStatic()
{ {
static bool is_first_call = true; static bool is_first_call = true;
if (is_first_call) if (is_first_call)
{ {
// Init stuff here on first call only, for instance something that requires heavy computation. // Init stuff here on first call only, for instance something that requires heavy computation.
// ... // ...
is_first_call = false; is_first_call = false;
} }
// ... code executed at each call // ... code executed at each call
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Constexpr ## Constexpr
We've seen the allocation of an array on the stack follows this syntax: We've seen the allocation of an array on the stack follows this syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int array[5ul]; int array[5ul];
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
where the size of the array is available at compile time (if not you have to use an array allocated on the heap at runtime). where the size of the array is available at compile time (if not you have to use an array allocated on the heap at runtime).
Now imagine we want to init such an array with a size that results from a computation; let's say a Fibonacci series: Now imagine we want to init such an array with a size that results from a computation; let's say a Fibonacci series:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Recursive function - Xeus cling may not appreciate if you call this cell several times. // Recursive function - kernel may not appreciate if you call this cell several times.
auto Fibonacci (std::size_t n) auto Fibonacci (std::size_t n)
{ {
if (n == 0) if (n == 0)
return 0; return 0;
if (n == 1) if (n == 1)
return 1; return 1;
return Fibonacci(n-1) + Fibonacci(n-2); return Fibonacci(n-1) + Fibonacci(n-2);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
std::cout << Fibonacci(5) << std::endl; std::cout << Fibonacci(5) << std::endl;
std::cout << Fibonacci(10) << std::endl; std::cout << Fibonacci(10) << std::endl;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double array[Fibonacci(5)]; // COMPILATION ERROR! double array[Fibonacci(5)]; // COMPILATION ERROR!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This doesn't seem outlandish: a computation is involved and this computation happens at runtime - even if in fact all the required elements to perform it were available at compile time (there were for instance no argument read from command line involved). This doesn't seem outlandish: a computation is involved and this computation happens at runtime - even if in fact all the required elements to perform it were available at compile time (there were for instance no argument read from command line involved).
In C++ 03, you had two choices to resolve this In C++ 03, you had two choices to resolve this
- Using a macro... - Using a macro...
- Or using [metaprogramming](../4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code). - Or using [metaprogramming](../4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).
C++ introduced the keyword `constexpr`, which indicates something happens at compile time. It might be used for just a variable, or for more complicated construct such as functions or classes. The only requirement is that all the subparts used are themselves `constexpr`. C++ introduced the keyword `constexpr`, which indicates something happens at compile time. It might be used for just a variable, or for more complicated construct such as functions or classes. The only requirement is that all the subparts used are themselves `constexpr`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
constexpr auto FibonacciConstexpr (std::size_t n) constexpr auto FibonacciConstexpr (std::size_t n)
{ {
if (n == 0) if (n == 0)
return 0; return 0;
if (n == 1) if (n == 1)
return 1; return 1;
return FibonacciConstexpr(n-1) + FibonacciConstexpr(n-2); return FibonacciConstexpr(n-1) + FibonacciConstexpr(n-2);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double array[FibonacciConstexpr(5)]; // Ok! double array[FibonacciConstexpr(5)]; // Ok!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`constexpr` function may also be used as runtime function, but in this case their results can't of course be used at compile time. `constexpr` function may also be used as runtime function, but in this case their results can't of course be used at compile time.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
int i = 7; int i = 7;
++i; // i is by no stretch a compile time variable! ++i; // i is by no stretch a compile time variable!
std::cout << FibonacciConstexpr(i) << std::endl; std::cout << FibonacciConstexpr(i) << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`constexpr` becomes increasingly powerful over time: `constexpr` becomes increasingly powerful over time:
- The function `FibonacciConstexpr` above does not in fact work before C++ 14: this version of the standard introduced the possibility to provide several `return` in a `constexpr` function. - The function `FibonacciConstexpr` above does not in fact work before C++ 14: this version of the standard introduced the possibility to provide several `return` in a `constexpr` function.
- `constexpr` is added wherever possible in the STL. This is still a work in progress: if you look for instance the [CppReference page](https://en.cppreference.com/w/cpp/algorithm/count) for `std::count` algorithm, you will see this algorithm becomes `constexpr` in C++ 20. - `constexpr` is added wherever possible in the STL. This is still a work in progress: if you look for instance the [CppReference page](https://en.cppreference.com/w/cpp/algorithm/count) for `std::count` algorithm, you will see this algorithm becomes `constexpr` in C++ 20.
We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr). We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -36,16 +36,15 @@ ...@@ -36,16 +36,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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 (or should - Xeus-cling doesn't manage it... As usual you may check a full-fledged compiler accepts it [@Coliru](http://coliru.stacked-crooked.com/a/3b77606ea8082485)): 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++17 ``` c++
// Xeus-cling issue (at least circa September 2022 and still there in February 2024) %%cppmagics clang
// < Following code isn't supported by our kernel, so we're using native compiler instead.
#include <cstdlib>
int main()
{
struct VectorAndInstantiate
{
double x;
double y;
double z;
} v1; // Here the struct is declared and at the same time an object v1 is created
struct VectorAndInstantiate v1.x = 1.;
{ v1.y = 5.;
double x; v1.z = -2.;
double y;
double z;
} v1; // Here the struct is declared and at the same time an object v1 is created
v1.x = 1.; return EXIT_SUCCESS;
v1.y = 5.; }
v1.z = -2.;
``` ```
%% 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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)
......
...@@ -60,16 +60,15 @@ ...@@ -60,16 +60,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Member functions ## Member functions
The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers). The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers).
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...) But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...)
One of the idea that was missing with original C `struct` was the possibility to add as well member functions: One of the idea that was missing with original C `struct` was the possibility to add as well member functions:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <cmath> #include <cmath>
struct Vector struct Vector
{ {
double x; double x;
double y; double y;
double z; double z;
void Init(double x, double y, double z) void Init(double x, double y, double z)
{ {
this->x = x; this->x = x;
this->y = y; this->y = y;
this->z = z; this->z = z;
} }
double Norm() double Norm()
{ {
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++17 ``` c++
#include <iostream> #include <iostream>
{ {
Vector v; Vector v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Let's do a bit of taxonomy here: Let's do a bit of taxonomy here:
- `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here. - `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here.
- **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method. - **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method.
- **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes. - **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes.
**WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data attributes have to be declared within the `struct` brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding. **WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data attributes have to be declared within the `struct` brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## The `this` keyword ## The `this` keyword
The `this->` may have puzzled you: it is a keyword to refer to the current object (very akin to Python `self`). The `this->` may have puzzled you: it is a keyword to refer to the current object (very akin to Python `self`).
In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely. In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely.
An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`: An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <cmath> #include <cmath>
struct Vector2 struct Vector2
{ {
double x_; double x_;
double y_; double y_;
double z_; double z_;
void Init(double x, double y, double z) void Init(double x, double y, double z)
{ {
x_ = x; // no need to `this` here as there is no ambiguity between data attribute name and parameter name x_ = x; // no need to `this` here as there is no ambiguity between data attribute name and parameter name
y_ = y; y_ = y;
z_ = z; z_ = z;
} }
double Norm() double Norm()
{ {
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++17 ``` c++
#include <iostream> #include <iostream>
{ {
Vector2 v; Vector2 v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...) That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...)
**Note:** another popular convention to name data attributes is to prefix with `m_` (e.g. `double m_x;` instead of `double x_` above). As most conventions, you should either use the one defined for the code on which you work or choose the one the more appropriate for you and stick with it if you're the decision maker on it. **Note:** another popular convention to name data attributes is to prefix with `m_` (e.g. `double m_x;` instead of `double x_` above). As most conventions, you should either use the one defined for the code on which you work or choose the one the more appropriate for you and stick with it if you're the decision maker on it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Separating declaration and definition ## Separating declaration and definition
We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have: We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have:
- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)): - On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Vector3 struct Vector3
{ {
double x_; double x_;
double y_; double y_;
double z_; double z_;
void Init(double x, double y, double z); void Init(double x, double y, double z);
double Norm(); double Norm();
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
- On another side the definition, usually in a source file which includes the header file: - On another side the definition, usually in a source file which includes the header file:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector3::Init(double x, double y, double z) void Vector3::Init(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++17 ``` c++
double Vector3::Norm() double Vector3::Norm()
{ {
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++17 ``` c++
#include <iostream> #include <iostream>
{ {
Vector3 v; Vector3 v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the `::` syntax which specifies the class for which the implementation is provided. Please notice the `::` syntax which specifies the class for which the implementation is provided.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Const methods ## Const methods
Are we happy here with what we have so far? Unfortunately, not quite... Are we happy here with what we have so far? Unfortunately, not quite...
If we define a simple free function that print the norm of a `Vector3`: If we define a simple free function that print the norm of a `Vector3`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PrintNorm(const Vector3& v) void PrintNorm(const Vector3& v)
{ {
std::cout << v.Norm() << std::endl; // COMPILATION ERROR std::cout << v.Norm() << std::endl; // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
... we see that doesn't compile. So what is happening? ... we see that doesn't compile. So what is happening?
The issue here is that the function `PrintNorm` takes as argument a constant reference to a `Vector` object, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const: The issue here is that the function `PrintNorm` takes as argument a constant reference to a `Vector` object, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PrintNormNoConst(Vector3& v) // BAD IDEA! void PrintNormNoConst(Vector3& v) // BAD IDEA!
{ {
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Vector3 v; Vector3 v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
PrintNormNoConst(v); PrintNormNoConst(v);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong. Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong.
The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned. The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned.
The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object: The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Vector4 struct Vector4
{ {
double x_; double x_;
double y_; double y_;
double z_; double z_;
void Init(double x, double y, double z); void Init(double x, double y, double z);
double Norm() const; // notice the additional keyword! double Norm() const; // notice the additional keyword!
void DontPutConstEverywhere() const; void DontPutConstEverywhere() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector4::Init(double x, double y, double z) void Vector4::Init(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++17 ``` c++
double Vector4::Norm() const double Vector4::Norm() const
{ {
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell. Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PrintNorm(const Vector4& v) void PrintNorm(const Vector4& v)
{ {
std::cout << v.Norm() << std::endl; std::cout << v.Norm() << std::endl;
} }
{ {
Vector4 v; Vector4 v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
PrintNorm(v); PrintNorm(v);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and **SHOULD BE** very good at this!): Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and **SHOULD BE** very good at this!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector4::DontPutConstEverywhere() const void Vector4::DontPutConstEverywhere() const
{ {
x_ = 0.; // ERROR! x_ = 0.; // ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `mutable` keyword ### `mutable` keyword
Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more specific attribute. You may in this case use the `mutable` keyword when defining this attribute: Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more specific attribute. You may in this case use the `mutable` keyword when defining this attribute:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Vector5 struct Vector5
{ {
double x_; double x_;
double y_; double y_;
double z_; double z_;
mutable unsigned int Nnorm_calls_; mutable unsigned int Nnorm_calls_;
void Init(double x, double y, double z); void Init(double x, double y, double z);
double Norm() const; double Norm() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector5::Init(double x, double y, double z) void Vector5::Init(double x, double y, double z)
{ {
x_ = x; x_ = x;
y_ = y; y_ = y;
z_ = z; z_ = z;
Nnorm_calls_ = 0u; Nnorm_calls_ = 0u;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Vector5::Norm() const double Vector5::Norm() const
{ {
++Nnorm_calls_; ++Nnorm_calls_;
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++17 ``` c++
#include <iostream> #include <iostream>
{ {
Vector5 v; Vector5 v;
v.Init(5., 6., -4.2); v.Init(5., 6., -4.2);
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
v.Norm(); v.Norm();
std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl; std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
I must stress again that you should use this in a **last resort**! I must stress again that you should use this in a **last resort**!
For my part, I have used this in only two contexts: For my part, I have used this in only two contexts:
* Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods. * Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods.
* For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial. * For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -53,16 +53,15 @@ ...@@ -53,16 +53,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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++17 ``` 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++17 ``` 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++17 ``` c++
%%cppmagics cppyy/cppdef
// < without this line the perfectly valid - and recommended! - braces in data
// attribute initialization are not accepted by our kernel.
Vector::Vector() Vector::Vector()
: x_(0.), // braces may also be used since C++ 11... but not supported here by Xeus-cling. : x_{0}, // braces may also be used since C++ 11, and are the recommended choice
y_(0.), y_{}, // the value inside may be skipped if it is 0
z_(0.) z_{}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
struct First struct First
{ { };
};
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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)). You may check the very example [in Coliru](https://coliru.stacked-crooked.com/a/f783f09ff4395aa1); both clang++ and g++ provide an adequate 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:
``` c++
%%cppmagics clang
#include <cstdlib>
struct PoorlyDefinedVector
{
double x_;
double y_;
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!
x_(x)
{ }
int main()
{
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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
struct ClassWithoutConstructor struct ClassWithoutConstructor
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
ClassWithoutConstructor my_object; ClassWithoutConstructor my_object;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct ClassWithConstructorWithArg struct ClassWithConstructorWithArg
{ {
ClassWithConstructorWithArg(int a); ClassWithConstructorWithArg(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a) ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` c++
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a) ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` c++
struct BadlyInitialized struct BadlyInitialized
{ {
int a_; int a_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` c++
struct ClassWithIntConstructor struct ClassWithIntConstructor
{ {
ClassWithIntConstructor(int a); ClassWithIntConstructor(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` c++
struct ClassWithExplicitIntConstructor struct ClassWithExplicitIntConstructor
{ {
explicit ClassWithExplicitIntConstructor(int a); explicit ClassWithExplicitIntConstructor(int a);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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++17 ``` 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)
......
...@@ -50,16 +50,15 @@ ...@@ -50,16 +50,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......