Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 18e97af9 authored by DIAZ Jerome's avatar DIAZ Jerome
Browse files

Apply review corrections.

parent d999971c
Branches
No related tags found
1 merge request!1092024 Jérôme reread
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb)
%% Cell type:markdown id: tags:
## Function declaration and definition
### Function declaration
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 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.
Few examples of function declarations:
%% Cell type:code id: tags:
``` C++17
int ComputeMinimum(int a, int b);
```
%% Cell type:code id: tags:
``` C++17
void DoStuff(double); // naming the parameter is optional
```
%% Cell type:code id: tags:
``` C++17
int ReturnFive(); // providing a parameter is also optional...
// Don't bother Xeus-cling warning: it IS here a function declaration!
```
%% Cell type:markdown id: tags:
Due to the use of notebooks we will not need to seperate 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:
### Function definition
%% 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.
In a function definition:
- No semicolon after the prototype
- 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.
For instance:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintDivision(int numerator, int denominator) // no semicolon here!
{
if (denominator == 0)
std::cout << "Failure: division by zero!" << std::endl;
else
{
int division ;
division = numerator / denominator ;
std::cout << numerator << " / " << denominator << " = " << division << std::endl ;
}
}
```
%% Cell type:markdown id: tags:
and when the function is invoked at some point, the implementation above is directly put in motion:
%% Cell type:code id: tags:
``` C++17
int num = 3;
int denom = 2;
PrintDivision(num, denom);
```
%% Cell type:markdown id: tags:
#### 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**.
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)
- **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).
%% Cell type:markdown id: tags:
#### Functions cannot be nested, or declared within blocks
%% Cell type:markdown id: tags:
Functions cannot be nested in C++, contrary to some other langages such as Python:
%% Cell type:code id: tags:
``` C++17
void Function1() // a function might have no arguments
{
void Subfunction() // COMPILATION ERROR!
{
}
}
```
%% 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.
%% Cell type:markdown id: tags:
## How to pass arguments
### 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void IncrementAndPrint(int i)
{
++i;
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!
IncrementAndPrint(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void IncrementAndPrint2(int local_argument)
{
++local_argument;
std::cout << "Inside the function: local_argument = " << local_argument << std::endl;
}
{
int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrint2(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
### 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void IncrementAndPrintByReference(int& i)
{
++i;
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!
IncrementAndPrintByReference(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% 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).
%% Cell type:code id: tags:
``` C++17
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.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int quotient, remainder;
if (ComputeDivision(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (ComputeDivision(5, 0, quotient, remainder) == 0)
std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
else
std::cerr << "Can't divide by 0!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### 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.
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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
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;
}
PrintDivision(8, 5);
PrintDivision(8, 0); // bug!
```
%% Cell type:markdown id: tags:
The developer made two important mistakes:
* 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.
%% Cell type:markdown id: tags:
### Passing arguments by pointers
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
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).
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void IncrementAndPrintByPointer(int* i)
{
*i += 1;
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!
IncrementAndPrintByPointer(&i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
## Function with return value
The value to return should come after the keyword `return`.
A C++ function may include several return values in its implementation:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
//! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative.
int Sign(int a)
{
if (a > 0)
return 1;
if (a == 0)
return 0;
return -1;
}
{
for (int a = -3; a < 4; ++a)
std::cout << "Sign of " << a << " = " << Sign(a) << std::endl;
}
```
%% Cell type:markdown id: tags:
## 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).
%% Cell type:code id: tags:
``` C++17
auto Sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment
{
return a + b;
}
```
%% Cell type:markdown id: tags:
The return type is optional (and was the reason of Xeus-cling failure):
%% Cell type:code id: tags:
``` C++17
auto Sum(int a, int b) // compiles just fine in Xeus-cling
{
return a + b;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
int a = 8;
int b = -3;
std::cout << a << " + " << b << " = " << Sum(a, b) << std::endl;
```
%% Cell type:markdown id: tags:
## Function overload
### 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:
%% Cell type:code id: tags:
``` C++17
#include <string>
void F();
void F(int); // Ok
double F(int, double); // Ok
auto F(char) -> int; // Ok (alternate function syntax)
std::string F(double, int, char*); // Ok
```
%% Cell type:markdown id: tags:
### **[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:
%% Cell type:code id: tags:
``` C++17
void G(int);
int G(int); // COMPILATION ERROR
```
%% 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.
%% Cell type:markdown id: tags:
### **[WARNING]** This is a C++ only feature and will not work in C!
%% Cell type:markdown id: tags:
In C you cannot do the following: if you run a simple program with overload:
```c
#include <stdio.h>
void f()
{
printf("No argument version");
}
void f(int a)
{
printf("Int argument version");
}
int main()
{
return 0;
}
```
you will get error messages such as:
```shell
prog.c:8:6: error: redefinition of 'f'
8 | void f(int a)
| ^
prog.c:3:6: note: previous definition of 'f' with type 'void()'
3 | void f()
```
(you may check this with [Wandbox](https://wandbox.org/) and select C instead of C++ as language).
%% 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).
%% Cell type:markdown id: tags:
### Good practice: do not make signature vary only by a reference or a pointer
%% 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void H(double a)
{
std::cout << "h(double) is called with a = " << a << '.' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void H(double* a) // Ok
{
std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
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;
a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
{
H(5); // Ok
double x = 1.;
H(&x); // Ok
}
```
%% Cell type:markdown id: tags:
However, there is a possible ambiguity between the pass-by-copy and pass-by-reference:
%% Cell type:code id: tags:
``` C++17
{
double x = 1.;
H(x); // COMPILATION ERROR: should it call h(double) or h(double& )?
}
```
%% Cell type:markdown id: tags:
You can lift the ambiguity for the pass-by-value:
%% Cell type:code id: tags:
``` C++17
{
double x = 1.;
H(static_cast<double>(x)); // Ok
}
```
%% 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.
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:
``` C++17
#include <iostream>
void H2(double a)
{
std::cout << "h2(double) is called with a = " << a << '.' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void H2(double* a)
{
std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
{
double x = 5.;
double* ptr = &x;
H2(x); // call h2(double)
H2(ptr); // call h2(double*)
H2(*ptr); // call h2(double)
H2(&x); // call h2(double*)
}
```
%% Cell type:markdown id: tags:
### 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!
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:
``` C++17
#include <iostream>
int Min(int a, int b)
{
std::cout << "int version called!" << std::endl;
return a < b ? a : b;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
double Min(double a, double b)
{
std::cout << "double version called!" << std::endl;
return a < b ? a : b;
}
```
%% Cell type:code id: tags:
``` C++17
{
int i1 { 5 }, i2 { -7 };
double d1 { 3.14}, d2 { -1.e24};
float f1 { 3.14f }, f2 { -4.2f};
short s1 { 5 }, s2 { 7 };
Min(5, 7); // no ambiguity
Min(i1, i2); // no ambiguity
Min(f1, f2); // conversion to closest one
Min(f1, d2); // conversion to closest one
Min(s1, s2); // conversion to closest one
}
```
%% 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:
%% Cell type:code id: tags:
``` C++17
{
unsigned int i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
}
```
%% Cell type:code id: tags:
``` C++17
{
long i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
}
```
%% Cell type:markdown id: tags:
Likewise, if best candidate is not the same for each argument:
%% Cell type:code id: tags:
``` C++17
{
float f1 { 5.f };
int i1 { 5 };
Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...
}
```
%% Cell type:markdown id: tags:
### Advice: use overload only when there is no ambiguity whatsoever
That is when:
- 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:
%% Cell type:code id: tags:
``` C++17
#include <string>
#include <iostream>
std::string GenerateString()
{
std::cout << "No argument!";
return "";
}
```
%% Cell type:code id: tags:
``` C++17
std::string GenerateString(char one_character)
{
std::cout << "One character: ";
return std::string(1, one_character);
}
```
%% Cell type:code id: tags:
``` C++17
std::string GenerateString(char value1, char value2)
{
std::cout << "Two characters: ";
std::string ret(1, value1);
ret += value2;
return ret;
}
```
%% Cell type:code id: tags:
``` C++17
std::string GenerateString(const std::string& string)
{
std::cout << "Std::string: ";
return string;
}
```
%% Cell type:code id: tags:
``` C++17
std::string GenerateString(const char* string)
{
std::cout << "Char*: ";
return std::string(string);
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << GenerateString() << std::endl;
std::cout << GenerateString('a') << std::endl;
std::cout << GenerateString('a', 'b') << std::endl;
std::cout << GenerateString("Hello world!") << std::endl;
std::string text("Hello!");
std::cout << GenerateString(text) << std::endl;
}
```
%% Cell type:markdown id: tags:
## Optional parameters
It is possible to provide optional parameters in the **declaration** of a function:
%% Cell type:code id: tags:
``` C++17
// Declaration.
void FunctionWithOptional(double x, double y = 0., double z = 0.);
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
// Definition
void FunctionWithOptional(double x, double y, double z) // notice the absence of default value!
{
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
// WARNING: Xeus-cling issue!
{
FunctionWithOptional(3., 5., 6.); // ok
FunctionWithOptional(3.); // should be ok, but Xeus-cling issue.
}
```
%% 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...
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:
``` C++17
#include <iostream>
// Definition which double acts as declaration
void FunctionWithOptional2(double x, double y = 0., double z = 0.)
{
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
FunctionWithOptional2(3., 5., 6.); // ok
FunctionWithOptional2(3.); // ok
}
```
%% 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.
Therefore:
* 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`.
%% Cell type:markdown id: tags:
## Lambda functions
C++ 11 introduced a shorthand to define functions called __lambda functions__.
An example is the best way to introduce them:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
// Locally defined function.
auto Square = [](double x) -> double
{
return x * x;
};
std::cout << Square(5.) << std::endl;
}
```
%% Cell type:markdown id: tags:
Several notes:
* 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.
* 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.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
// Locally defined function.
auto Square = [](double x)
{
return x * x;
};
auto Cube = [](double x)
{
return x * x * x;
};
std::cout << "Are the lambda prototypes the same type? "
<< (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl;
}
```
%% 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int a = 5;
auto APlusB = [](int b)
{
return a + b;
};
std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int a = 5;
auto APlusB = [a](int b) // Notice the `[a]` here!
{
return a + b;
};
std::cout << APlusB(3) << std::endl;
}
```
%% Cell type:markdown id: tags:
The values captured in the lambda might be transmitted by reference:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int a = 5;
auto AddToA = [&a](int b) // Notice the `[&a]` here!
{
a += b;
};
AddToA(3);
std::cout << a << std::endl;
}
```
%% 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!
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:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <iostream>
#include <algorithm> // for sort
{
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::cout << "Initial list = ";
for (int value : list)
std::cout << value << ' ';
// My very specific sort operation:
// Returns true if lhs is odd and rhs isn't or if lhs < rhs.
auto odd_first = [](auto lhs, auto rhs)
{
const bool is_lhs_odd = !(lhs % 2 == 0);
const bool is_rhs_odd = !(rhs % 2 == 0);
if (is_lhs_odd != is_rhs_odd)
return is_lhs_odd;
return lhs < rhs;
};
std::sort(list.begin(), list.end(), odd_first);
std::cout << std::endl << "Sorted list = ";
for (int value : list)
std::cout << value << ' ';
}
```
%% 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:
%% Cell type:code id: tags:
``` C++17
#include <algorithm>
#include <iostream>
{
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::vector<int> even_only;
// Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later!
std::copy_if(list.cbegin(),
list.cend(),
std::back_inserter(even_only),
[](int value)
{
return value % 2 == 0;
});
for (int value : even_only)
std::cout << value << ' ';
}
```
%% Cell type:markdown id: tags:
## 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).
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:
```c++
unsigned int (*f) (int, double)
```
where:
* `unsigned int` is the return type.
* `int, double` are the type of the parameters of the function given as argument.
* `f` is the name of the argument.
It will be clearer in an example:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintFunctionCall(int (*f) (int, int), int m, int n)
{
std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl;
};
```
%% Cell type:code id: tags:
``` C++17
int Multiply(int a, int b)
{
return a * b;
}
```
%% Cell type:code id: tags:
``` C++17
int Add(int a, int b)
{
return a + b;
}
```
%% Cell type:code id: tags:
``` C++17
PrintFunctionCall(Multiply, 5, 6);
PrintFunctionCall(Add, 5, 6);
```
%% Cell type:markdown id: tags:
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 [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).
%% Cell type:markdown id: tags:
## 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:
* __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.
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.
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.
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.
We will deal with main functions later when we will work in a true C++ environment.
%% Cell type:markdown id: tags:
## `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:
%% Cell type:code id: tags:
``` C++17
inline double Square(double x)
{
return x * x;
}
```
%% Cell type:markdown id: tags:
when this function is called somewhere, the compiler may replace directly the function by the code inside the definition:
%% Cell type:code id: tags:
``` C++17
{
Square(5.); // The compiler might substitute 5. * 5. to the actual function call here
}
```
%% 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:
* 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.
* `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.
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:
%% Cell type:code id: tags:
``` C++17
#define SQUARE(x) ((x) * (x)) // macro
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
double x = 5.;
std::cout << Square(++x) << std::endl;
}
{
double x = 5.;
std::cout << SQUARE(++x) << std::endl;
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 6](./3b-hands-on.ipynb)
%% Cell type:markdown id: tags:
### **EXERCISE 15: change interface of `PowerOfTwoApprox` struct**
* Transform `Compute()` into a constructor. As a constructor cannot return a value (it returns an object!), introduce a method `AsDouble()` which returns the approximation (that by definition may be computed from data attributes `numerator_` and `exponent_`).
* Transform `Compute()` into a constructor. As a constructor cannot return a value, introduce a method `AsDouble()` which returns the approximation (that by definition may be computed from data attributes `numerator_` and `exponent_`).
* Assign a default value of `0` to data attributes (if not already done previously!)
Expected output is the same as previously.
```
[With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100]
[With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100]
[With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100]
[With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100]
[With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100]
[With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100]
[With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100]
[With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100]
[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 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 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 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]
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Introduction](./1-Intro.ipynb)
%% Cell type:markdown id: tags:
## Motivation
The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL [provides it already](https://en.cppreference.com/w/cpp/algorithm/min)...)
%% Cell type:code id: tags:
``` C++17
int Min(int lhs, int rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
double Min(double lhs, double rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
float Min(float lhs, float rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << Min(5, -8) << std::endl;
std::cout << Min(5., -8.) << std::endl;
std::cout << Min(5.f, -8.f) << std::endl;
}
```
%% Cell type:markdown id: tags:
Already tired? Yet we have still to define the same for unsigned versions, other types such as `short` ou `long`... And it's not very [**DRY** (Don't Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself): you may have noticed the implementation is exactly the same!
## Function templates (or methods)
A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**:
%% Cell type:code id: tags:
``` C++17
template<class T>
T MinWithTemplate(T lhs, T rhs)
{
return lhs < rhs ? lhs : rhs;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << MinWithTemplate<int>(5, -8) << std::endl;
std::cout << MinWithTemplate(5, -8) << std::endl;
std::cout << MinWithTemplate(5., -8.) << std::endl;
std::cout << MinWithTemplate(5.f, -8.f) << std::endl;
}
```
%% Cell type:markdown id: tags:
As you can see:
* The type is replaced by a parameter (here called `T`)
* In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one or several of the parameters. In other words, for the following case it won't work:
%% Cell type:code id: tags:
``` C++17
template<class T>
T Convert(int value)
{
return static_cast<T>(value);
}
```
%% Cell type:code id: tags:
``` C++17
{
double x = Convert(5); // Error: can't figure out which type `T` to use!
}
```
%% Cell type:code id: tags:
``` C++17
{
float x = Convert<double>(5); // Ok
}
```
%% Cell type:markdown id: tags:
You may have noticed there are no constraints on `T` whatsoever. If you invoke a template and the implementation doesn't make sense for the chosen type the compiler will yell (no doubt you have already seen compilers are extremely talented to do so and it is even truer regarding templates...)
%% Cell type:code id: tags:
``` C++17
#include <string>
{
Convert<std::string>(5); // Doesn't make sense so compiler will yell!
}
```
%% Cell type:markdown id: tags:
### `static_assert`
Of course, it would be nicer to get a clearer error message when an impossible type is provided... C++ 20 will introduce the [concept](https://en.cppreference.com/w/cpp/language/constraints) to constraint properly which type is acceptable, but C++ 11 already introduced a way slightly better than C++ 03 with `static_assert`:
%% Cell type:code id: tags:
``` C++17
#include <type_traits> // for std::is_arithmetic
template<class T>
T Convert2(int value)
{
static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!");
return static_cast<T>(value);
}
```
%% Cell type:code id: tags:
``` C++17
#include <string>
{
Convert2<std::string>(5); // Doesn't make sense so compiler will yell!
// But first line is much clearer than previously...
}
```
%% Cell type:markdown id: tags:
`static_assert` evolved in C++ 17:
* In C++ 11, it takes two arguments: the test and a string which features an explanation on the topic at hand.
* In C++ 17 and above, you might just give the test; it is actually handy when the test is already crystal clear:
```c++
static_assert(std::is_same<T, int>(), "Check T is an integer");
```
is a tad overkill!
That being said, if the test is not that trivial you should really use the possibility to add an explanation.
## Class templates
We have seen templates in the case of functions, but classes can be templated as well:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue
{
public:
HoldAValue(T value);
T GetValue() const;
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
template<class T>
T HoldAValue<T>::GetValue() const
{
return value_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
{
HoldAValue integer {5};
std::cout << "Integer hold: " << integer.GetValue() << std::endl;
HoldAValue<std::string> string {"Hello world!"}; // If type not specified explicitly it would have been char*...
std::cout << "String hold: " << string.GetValue() << std::endl;
}
```
%% Cell type:markdown id: tags:
The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters.
Spot the template type is not defined for the `integer` variable. The type is automatically deduced from the `5` argument value as an `int`. This variable could have been defined as `HoldAValue<int> integer {5};` (cf. [types deduction](../1-ProceduralProgramming/3-Types.ipynb#decltype-and-auto))
### Template method of a template class
Notice a template class may provide template methods:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue2
{
public:
HoldAValue2(T value);
T GetValue() const;
template<class U>
U Convert() const;
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue2<T>::HoldAValue2(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
template<class T>
T HoldAValue2<T>::GetValue() const
{
return value_;
}
```
%% Cell type:markdown id: tags:
In this case there are two `template` keyword for the definition: one for the class and the other for the method:
%% Cell type:code id: tags:
``` C++17
template<class T> // template type for the class first
template<class U> // then template type for the method
U HoldAValue2<T>::Convert() const
{
return static_cast<U>(value_);
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue2 hold(9);
hold.Convert<double>();
}
```
%% Cell type:markdown id: tags:
### Friendship syntax
There is a weird and specific syntax that is expected if you want to declare a friendship to a function. Quite naturally you would probably write something like:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue3
{
public:
HoldAValue3(T value);
friend void Print(const HoldAValue3<T>& obj);
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue3<T>::HoldAValue3(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
template<class T>
void Print(const HoldAValue3<T>& obj)
{
// Friendship required to access private data.
// I wouldn't recommend friendship where an accessor would do the same task easily!
std::cout << "Underlying value is " << obj.value_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue3<int> hold(5);
Print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!
}
```
%% Cell type:markdown id: tags:
To make the friendship work, you have to use in the friendship declaration another label for the template parameter:
%% Cell type:code id: tags:
``` C++17
template<class T>
class HoldAValue4
{
public:
HoldAValue4(T value);
// 'Repeating' the list of template arguments and not using the ones from the class will fix the issue...
// T wouldn't have work here; the label MUST differ.
template<class U>
friend void Print(const HoldAValue4<U>& obj);
private:
T value_;
};
```
%% Cell type:code id: tags:
``` C++17
template<class T>
HoldAValue4<T>::HoldAValue4(T value)
: value_(value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions!
template<class T>
void Print(const HoldAValue4<T>& obj)
{
std::cout << "Underlying value is " << obj.value_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
HoldAValue4<int> hold(5);
Print(hold); // Ok!
}
```
%% Cell type:markdown id: tags:
This way of declaring friendship works but is not entirely fullproof: `Print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.archive.org/web/20211003194958/https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it.
%% Cell type:markdown id: tags:
## Type or non-type template parameter
The examples so far are using **type** parameters: the `T` in the example stands for a type and is deemed to be substituted by a type. Templates may also use **non type** parameters, which are in most cases `enum` or `integral constant` types (beware: floating point types parameters or `std::string` **can't** be used as template parameters!)
Both can be mixed in a given template declaration:
%% Cell type:code id: tags:
``` C++17
template<class TypeT, std::size_t Nelts>
class MyArray
{
public:
explicit MyArray(TypeT initial_value);
private:
TypeT content_[Nelts];
};
```
%% Cell type:code id: tags:
``` C++17
template<class TypeT, std::size_t Nelts>
MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)
{
for (std::size_t i = 0; i < Nelts; ++i)
content_[i] = initial_value;
}
```
%% Cell type:code id: tags:
``` C++17
{
MyArray<int, 5ul> array1(2);
MyArray<double, 2ul> array2(3.3);
}
```
%% Cell type:markdown id: tags:
However, you can't provide a type parameter where a non-type is expected (and vice-versa):
%% Cell type:code id: tags:
``` C++17
{
MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR!
}
```
%% Cell type:code id: tags:
``` C++17
{
MyArray<int, int> array1(2); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
## Few precisions about templates
* Template parameters must be known or computed at **compile time**. You can't therefore instantiate a template from a value that was entered by the user of the program or computed at runtime.
* In the template syntax, `class` might be replaced by `typename`:
%% Cell type:code id: tags:
``` C++17
template<typename T>
T Convert3(int value)
{
return static_cast<T>(value);
}
```
%% Cell type:markdown id: tags:
Since C++17, there are exactly zero differences between both keywords; some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions!
Since C++ 17, there are exactly zero differences between both keywords (before C++ 17, they were almost everywhere equivalent except in a very specific case - called template template parameter that we'll see shortly - in which typename did not work. You can check [this link](https://stackoverflow.com/questions/2023977/what-is-the-difference-between-typename-and-class-template-parameters) if you want to know more); some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions!
* The definition of the templates must be provided in header files, not in compiled files. The reason is that the compiler can't know all the types for which the template might be used: you may use `std::min` for your own defined types (provided they define a `<` operator...) and obviously STL writers can't foresee which type you will come up with!
* The compiler will generate the code for each type given to the template; for instance if `Convert<double>`, `Convert<unsigned int>` and `Convert<short>` are used in your code, the content of this template function will be instantiated thrice! This can lead to **code bloat**: lots of assembler code is generated, and compilation time may increase significatively.
* So templates are a _very_ nice tool, but make sure to use them only when they're relevant, and you may employ techniques to limit the amount of code actually defined within the template definitions - the more straightforward being defining in a non template function the parts of the implementation that do not depend on the template parameter type. This way, this part of the code is not duplicated for each different type.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [Move semantics](./5-MoveSemantics.ipynb)
%% Cell type:markdown id: tags:
## Motivation: eliminate unnecessary deep copies
In many situations, unnecessary deep copies are made.
In the example below, during the exchange between the two instances of the `Text` class, we have to make 3 memory deallocations, 3 allocations, 3 character copy loops... where 3 pointer copies would be sufficient.
%% Cell type:code id: tags:
``` C++17
#include <cstring>
#include <iostream>
class Text
{
public :
// For next section - don't bother yet
friend void Swap(Text& lhs, Text& rhs);
Text(const char* string);
// Copy constructor.
Text(const Text& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators
Text& operator=(const Text& t)
{
std::cout << "Operator= called" << std::endl;
if (this == &t)
return *this ; // standard idiom to deal with auto-recopy
delete [] data_;
size_ = t.size_ ;
data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_);
return *this ;
}
~Text();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text& t)
{
return stream << t.data_ ;
}
private :
unsigned int size_{0};
char* data_ { nullptr }; // to make our point - in a true code use an existing container!
} ;
```
%% Cell type:code id: tags:
``` C++17
Text::Text(const char* string)
{
std::cout << "Constructor called with argument '" << string << "'" << std::endl;
size_ = std::strlen(string) + 1;
data_ = new char[size_] ;
std::copy(string, string + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text::Text(const Text& t)
: size_(t.size_), data_(new char [t.size_])
{
std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text::~Text()
{
std::cout << "Destructor called" << std::endl;
delete[] data_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text t1("world!") ;
Text t2("Hello") ;
// Swap of values:
Text tmp = t1 ;
t1 = t2 ;
t2 = tmp ;
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
## A traditional answer: to allow the exchange of internal data
By allowing two `Text` objects to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time, by leveraging the fact we know the internal structure of the class:
%% Cell type:code id: tags:
``` C++17
void Swap(Text& lhs, Text& rhs)
{
unsigned int tmp_size = lhs.size_;
char* tmp_data = lhs.data_;
lhs.size_ = rhs.size_;
lhs.data_ = rhs.data_;
rhs.size_ = tmp_size;
rhs.data_ = tmp_data;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text t1("world!") ;
Text t2("Hello") ;
// Swap of values:
Swap(t1, t2);
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
There is even a `std::swap` in the STL that may be overloaded for your own types.
Now let's see how C++11 introduces new concepts to solve this (and many other) problems in a more elegant way.
%% Cell type:markdown id: tags:
## Reminder on references in C++03
C++ references allow you to attach a new name to an existing object in the stack or heap. All accesses and modifications made through the reference affect the original object:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int var = 42;
int& ref = var; // Create a reference to var
ref = 99;
std::cout << "And now var is also 99: " << var << std::endl;
}
```
%% Cell type:markdown id: tags:
A reference can only be attached to a stable value (left value or **l-value**), which may broadly be summarized as a value which address may be taken (see [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) on this topic - its reading is especially interesting concerning this topic that is not always explained properly elsewhere - especially on the Web).
By opposition a **r-value** is a temporary value such as a literal expression or a temporary object created by implicit conversion.
%% Cell type:code id: tags:
``` C++17
{
int& i = 42 ; // Compilation error: 42 is a r-value!
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Print(std::string& lvalue)
{
std::cout << "l-value is " << lvalue << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Print("hello") ; // Compilation error: "hello" is a r-value!
}
```
%% Cell type:markdown id: tags:
Look carefully at the error message: the issue is not between `const char[6]` and `std::string` (implicit conversion from `char*` to `std::string` exists) but due to the reference; same function with pass-by-copy works seamlessly:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
void PrintByCopy(std::string value) // no reference here!
{
std::cout << "l- or r- value is " << value << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintByCopy("hello") ; // Ok!
}
```
%% Cell type:markdown id: tags:
Noteworthy exception: a "constant" reference (language abuse designating a reference to a constant value) can be attached to a temporary value, in particular to facilitate implicit conversions:
%% Cell type:code id: tags:
``` C++17
void PrintByConstRef(const std::string& lvalue)
{
std::cout << "l-value is " << lvalue << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintByConstRef("hello") ; // Ok!
}
```
%% Cell type:markdown id: tags:
## C++11/14 : temporary references
To go further, C++11 introduces the concept of **r-value reference**, which can only refer to temporary values, and is declared using an `&&`.
%% Cell type:code id: tags:
``` C++17
{
int&& i = 42;
}
```
%% Cell type:code id: tags:
``` C++17
{
int j = 42;
int&& k = j; // Won’t compile: j is a l-value!
}
```
%% Cell type:markdown id: tags:
It is now possible to overload a function to differentiate the treatment to be performed according to whether it is provided with a stable value or a temporary value. Below, function `f` is provided in three variants:
```
void f(T&); // I : argument must be a l-value
void f(const T&) ; // II : argument may be l-value or r-value but can't be modified
void f(T&&); // III : argument must be a r-value
```
%% Cell type:markdown id: tags:
In case of a call of `f` with a temporary value, it is now form III that will be invoked, if it is defined. This is the cornerstone of the notion of **move semantic**.
%% Cell type:markdown id: tags:
## Function with r-value arguments
When we know that a value is temporary, we must be able to use it again, or "loot" its content without harmful consequences; _move_ it instead of _copying_ it. When handling large dynamic data structures, it can save many costly operations.
Let's take a function that receives a vector of integers and replicates it to modify it. The old way would be as follows:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
void PrintDouble(const std::vector<int>& vec)
{
std::cout << "PrintDouble for l-value" << std::endl;
std::vector<int> copy(vec);
for (auto& item : copy)
item *= 2;
for (auto item : copy)
std::cout << item << ' ';
std::cout << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
PrintDouble(primes);
}
```
%% Cell type:markdown id: tags:
If the original object is temporary, copying it is not necessary. This can be exploited through this overload of the function:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintDouble(std::vector<int>&& vec)
{
std::cout << "PrintDouble for r-value" << std::endl;
for (auto& item : vec)
item *= 2;
for (auto item : vec)
std::cout << item << ' ';
std::cout << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintDouble(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 });
}
```
%% Cell type:markdown id: tags:
We can check if the r-value did not supersede entirely the l-value one; first call is still resolved by the first overload:
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
PrintDouble(primes);
}
```
%% Cell type:markdown id: tags:
## `std::move`
Now, if we get a l-value and know we do not need it anymore in the current scope, we may choose to cast is as a r-value through a **static_cast**:
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
PrintDouble(static_cast<std::vector<int>&&>(primes));
}
```
%% Cell type:markdown id: tags:
And we see overload call is properly the one for r-values.
The syntax is a bit heavy to type, so a shorter one was introduced as well: **`std::move`**:
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
PrintDouble(std::move(primes)); // strictly equivalent to the static_cast in former cell!
}
```
%% Cell type:markdown id: tags:
Please notice that the call to `std::move` does not move `primes` per se. It only makes it a temporary value in the eyes of the compiler, so it is a "possibly" movable object if the context allows it; if for instance the object doesn't define a move constructor (see next section), no move will occur!
%% Cell type:markdown id: tags:
**[WARNING]** Do not use a local variable that has been moved! In our example the content of `primes` after the `std::move` call is undefined behaviour.
%% Cell type:markdown id: tags:
## Return value optimization (RVO) and copy elision
When you define a function which returns a (possibly large) object, you might be worried unneeded copy is performed:
%% Cell type:code id: tags:
``` C++17
#include <vector>
std::vector<unsigned int> FiveDigitsOfPi()
{
std::vector<unsigned int> ret { 3, 1, 4, 1, 5 };
return ret; // copy should be incurred here... Right? (No in fact!)
}
```
%% Cell type:markdown id: tags:
and attempt to circumvent it by a `std::move`:
%% Cell type:code id: tags:
``` C++17
#include <vector>
std::vector<unsigned int> FiveDigitsOfPi_WithMove()
{
std::vector<unsigned int> ret { 3, 1, 4, 1, 5 };
return std::move(ret); // Don't do that!
}
```
%% Cell type:markdown id: tags:
or even to avoid entirely returning a large object by using a reference:
%% Cell type:code id: tags:
``` C++17
#include <vector>
void FiveDigitsOfPi(std::vector<unsigned int>& result)
{
result = { 3, 1, 4, 1, 5 };
}
```
%% Cell type:markdown id: tags:
The second version works as you intend, but it way clunkier to use: do you prefer:
%% Cell type:code id: tags:
``` C++17
{
auto digits = FiveDigitsOfPi();
}
```
%% Cell type:markdown id: tags:
or:
%% Cell type:code id: tags:
``` C++17
{
std::vector<unsigned int> digits;
FiveDigitsOfPi(digits);
}
```
%% Cell type:markdown id: tags:
In fact, you shouldn't worry: all modern compilers provide a __return value optimization__ which guarantees never to copy the potentially large object created.
However, it does work only when the object is returned by value, so casting it as a rvalue reference with `std::move(ret)` actually prevents this optimization to kick up!
%% Cell type:markdown id: tags:
So to put in a nutshell, you should (almost) never use `std::move` on a return line (you may learn more about it in [this StackOverflow question](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization)).
The only exception is detailed in item 25 of [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) and is very specific: it is when you want to return a value that was passed by an rvalue argument, e.g.:
%% Cell type:code id: tags:
``` C++17
// Snippet not complete enough to work.
class Matrix; // forward declaration - don't bother yet!
Matrix Add(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return std::move(lhs); // ok in this case!
}
```
%% Cell type:markdown id: tags:
This case is very limited (never needed it myself so far) so I invite you to read the item in Scott Meyer's book in you want to learn more (items 23 to 30 are really enlightening about move semantics - very recommended reading!).
%% Cell type:markdown id: tags:
## Move constructors
In classes, C++ introduced with move semantics two additional elements in the canonical form of the class:
- A **move constructor**
- A **move assignment operator**
%% Cell type:code id: tags:
``` C++17
#include <cstring>
#include <iostream>
class Text2
{
public :
Text2(const char* string);
// Copy constructor.
Text2(const Text2& t);
// Move constructor
Text2(Text2&& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(const Text2& t)
{
std::cout << "Operator= called" << std::endl;
if (this == &t)
return *this ; // standard idiom to deal with auto-recopy
delete [] data_;
size_ = t.size_ ;
data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_);
return *this ;
}
// Move assignment operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(Text2&& t)
{
std::cout << "Operator= called for r-value" << std::endl;
if (this == &t)
return *this;
delete[] data_;
size_ = t.size_;
data_ = t.data_;
// Don't forget to properly invalidate `t` content:
t.size_ = 0 ;
t.data_ = nullptr ;
return *this ;
}
~Text2();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text2& t)
{
return stream << t.data_ ;
}
private :
unsigned int size_{0};
char* data_ = nullptr;
} ;
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(const char* string)
{
std::cout << "Constructor called" << std::endl;
size_ = std::strlen(string) + 1;
data_ = new char[size_] ;
std::copy(string, string + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(const Text2& t)
: size_(t.size_), data_(new char [t.size_])
{
std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(Text2&& t)
: size_(t.size_), data_(t.data_)
{
std::cout << "Move constructor called" << std::endl;
t.size_ = 0 ;
t.data_ = nullptr ;
}
```
%% Cell type:code id: tags:
``` C++17
Text2::~Text2()
{
std::cout << "Destructor called" << std::endl;
delete[] data_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text2 t1("world!") ;
Text2 t2("Hello") ;
// Swap of values:
Text2 tmp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(tmp);
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
With all this move semantics, the operations above are comparable to what we achieved with the `Swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values.
As already mentioned [there](../3-Operators/4-CanonicalForm.ipynb#[Advanced]-The-true-canonical-class), there are specific rules called __Rule of 0__, __Rule of 3__ and __Rule of 5__, which explains which constructor(s), destructor and assignment operator you ought to define for your class.
%% Cell type:markdown id: tags:
## Temporary reference argument within a function
A crucial point now: if a function receives a temporary reference argument (which can only be attached to a temporary value), within the function this argument is considered as l-value (we can perfectly put it to the left of an = and reassign a new value). If the function does not itself loot the content of the variable, and transmits it to another function (or constructor or operator), it can only reactivate its temporary character using a call to `std::move`.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
void DoStuff(std::string&& string)
{
std::cout << "Argument given by r-value is: " << string << std::endl;
string = "Bye!";
std::cout << "It was nonetheless modified as it is **inside the function** a l-value: " << string << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
DoStuff("Hello!");
}
```
%% Cell type:markdown id: tags:
## Move semantics in the STL
All containers in the standard library are now enhanced with move constructors and move assignment operators.
Moreover, the move semantics is not only about improving performance. There are classes (such as `std::unique_ptr` we'll see in [next notebook](./6-SmartPointers.ipynb#unique_ptr)) for which it makes no sense for objects to be copyable, but where it is necessary for them to be movable. In this case, the class has a constructor per move, and no constructor per copy.
An object that has been "emptied" as a result of a move is no longer supposed to be useful for anything. However if not destroyed it is still recommended, when you implement this type of class and move, to leave the emptied object in a "valid" state; that's why we put in our `Text2` class the `data_` pointer to `nullptr` and the `size_` to 0. The best is of course to ensure its destruction at short notice to avoid any mishap.
%% Cell type:markdown id: tags:
## Forwarding reference (or universal reference)
Just a quick warning (you should really read [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) for an extensive discussion on the topic; blog [FluentCpp](https://www.fluentcpp.com/2018/02/06/understanding-lvalues-rvalues-and-their-references) provides some intel about it... and tells you as well to really Scott Meyer's book to learn more!): seeing `&&` doesn't automatically mean it is a r-value reference.
Just a quick warning (you should really read [Effective modern C++](../bibliography.ipynb#Effective-Modern-C++) for an extensive discussion on the topic; blog [FluentCpp](https://www.fluentcpp.com/2018/02/06/understanding-lvalues-rvalues-and-their-references) provides some intel about it... and tells you as well to read Scott Meyer's book to learn more!): seeing `&&` doesn't automatically mean it is a r-value reference.
There is a very specific case when:
- The argument is template
- The parameter is **exactly** `T&&` (not `std::vector<T&&>` for instance)
in which the syntax stands for either case (l-value or r-value)
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
template<class T>
void PrintUniversalRef(T&& value)
{
std::cout << value << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintUniversalRef("r-value call!"); // will call a specialisation of the template for r-value
std::string hello("l-value call!"); // will call a specialisation of the template for l-value
PrintUniversalRef(hello);
}
```
%% Cell type:markdown id: tags:
Unfortunately, C++ 11 committee didn't give immediately a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
......@@ -11,8 +11,8 @@
struct PowerOfTwoApprox
{
int numerator_ {};
int exponent_ {};
int numerator {};
int exponent {};
};
......@@ -102,8 +102,8 @@ void DisplayPowerOf2Approx(int Nbits, double value)
const double error = std::fabs(value - double_quotient) / value;
std::cout << "[With " << Nbits << " bits]: " << value << " ~ " << double_quotient << " (" << approximation.numerator_ <<
" / 2^" << approximation.exponent_ << ") [error = " << RoundAsInt(100. * error) << "/100]" << std::endl;
std::cout << "[With " << Nbits << " bits]: " << value << " ~ " << double_quotient << " (" << approximation.numerator <<
" / 2^" << approximation.exponent << ") [error = " << RoundAsInt(100. * error) << "/100]" << std::endl;
}
......@@ -117,8 +117,8 @@ double ComputePowerOf2Approx(int Nbits, double value, PowerOfTwoApprox& approxim
{
int max_numerator = MaxInt(Nbits);
auto& numerator = approximation.numerator_; // alias!
auto& exponent = approximation.exponent_; // alias!
auto& numerator = approximation.numerator; // alias!
auto& exponent = approximation.exponent; // alias!
int denominator {};
......@@ -144,7 +144,7 @@ int Multiply(int Nbits, double value, int coefficient)
ComputePowerOf2Approx(Nbits, value, approximation);
return TimesPowerOf2(approximation.numerator_ * coefficient, -approximation.exponent_);
return TimesPowerOf2(approximation.numerator * coefficient, -approximation.exponent);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment