Mentions légales du service

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

Added a reference to C++ weekly regarding type deduction and updated the bibliography accordingly.

parent 93f575d9
Branches
No related tags found
No related merge requests found
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./3-Types.ipynb)
%% Cell type:markdown id: tags:
## Boolean
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.
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:
``` C++17
#include <iostream>
bool undefined; // UNDEFINED !!
if (undefined)
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;
```
%% Cell type:code id: tags:
``` C++17
bool defined { true };
if (defined)
std::cout << "Defined!" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
int n = -5;
if (n)
std::cout << "Boolean value of " << n << " is true." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
int n = 0;
if (!n) // ! is the not operator: the condition is true if n is false.
std::cout << "Boolean value of " << n << " is false." << std::endl;
```
%% Cell type:markdown id: tags:
## 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.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
enum color { red, green, blue } ;
std::cout << red << " " << green << " " << blue << " (expected: 0, 1, 2)" << std::endl;
enum shape { circle=10, square, triangle=20 };
std::cout << circle << " " << square << " " << triangle << " (expected: 10, 11, 20)"<< std::endl; // 10 11 20
}
```
%% Cell type:markdown id: tags:
These `enum` are placeholders for integers and might be used as such:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
enum color { red, green, blue } ;
int a { 5 };
color c = green;
int b = a + c;
std::cout << "b = " << b << " (expected: 6)" << std::endl;
enum shape { circle=10, square, triangle=20 };
shape s = triangle;
int d = s + c;
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:
A (huge) shortcoming of historical `enum ` is that the same word can't be used in two different `enum`:
%% Cell type:code id: tags:
``` C++17
{
enum is_positive { yes, no };
enum is_colored { yes, no }; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
### 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.
%% Cell type:code id: tags:
``` C++17
enum class is_positive { yes, no };
enum class is_colored { yes, no }; // OK
```
%% Cell type:code id: tags:
``` C++17
yes; // COMPILATION ERROR: `enum class ` value must be prefixed! (see below)
```
%% Cell type:code id: tags:
``` C++17
is_positive p = is_positive::yes; // OK
```
%% Cell type:code id: tags:
``` C++17
int a = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer
```
%% Cell type:code id: tags:
``` C++17
is_positive::yes + is_colored::no; // COMPILATION ERROR: addition of two unrelated types
```
%% Cell type:code id: tags:
``` C++17
{
enum class color { red, green, blue } ;
color c = color::green;
bool is_more_than_red = (c > color::red); // Both belong to the same type and therefore might be compared
}
```
%% Cell type:markdown id: tags:
These enum types are really handy to make code more expressive, especially in function calls:
```c++
f(print::yes, perform_checks::no);
```
is much more expressive (and less error-prone) than:
```c++
f(true, false);
```
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.
%% Cell type:markdown id: tags:
## Numerical types
#### List of numerical types
The FORTRAN correspondences below are given as examples. 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
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
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 |
|:------------- |:---------:|:-------------------:|:----------:|
| `short` | INTEGER*2 | At least on 16 bits | None |
| `int` | INTEGER*4 | At least on 16 bits | 0 |
| `long` | INTEGER*8 | At least on 32 bits | 0l |
| `long long` | INTEGER*16| At least on 64 bits | 0ll |
| `float` | REAL*4 | - | 0.f |
| `double` | REAL*8 | - | 0. |
| `long double` | REAL*16 | - | 0.l |
All integer types (`short`, `int` and `long`) also have an unsigned variant, for example
`unsigned int`, which only takes positive values.
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
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)).
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.
#### 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:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <limits> // for std::numeric_limits
{
std::cout << "int [min, max] = [" << std::numeric_limits<int>::lowest() << ", "
<< std::numeric_limits<int>::max() << "]" << std::endl;
std::cout << "unsigned int [min, max] = [" << std::numeric_limits<unsigned int>::lowest() << ", "
<< std::numeric_limits<unsigned int>::max() << "]" << std::endl;
std::cout << "short [min, max] = [" << std::numeric_limits<short>::lowest() << ", "
<< std::numeric_limits<short>::max() << "]" << std::endl;
std::cout << "long [min, max] = [" << std::numeric_limits<long>::lowest() << ", "
<< std::numeric_limits<long>::max() << "]" << std::endl;
std::cout << "float [min, max] = [" << std::numeric_limits<float>::lowest() << ", "
<< std::numeric_limits<float>::max() << "]" << std::endl;
std::cout << "double [min, max] = [" << std::numeric_limits<double>::lowest() << ", "
<< std::numeric_limits<double>::max() << "]" << std::endl;
std::cout << "long double [min, max] = [" << std::numeric_limits<long double>::lowest() << ", "
<< std::numeric_limits<long double>::max() << "]" << std::endl;
}
```
%% 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`).
##### Integral types
If an initial value is not in the range, the compiler will yell:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
short s = -33010; // triggers a warning: outside the range
std::cout << s << std::endl;
}
```
%% Cell type:markdown id: tags:
However, if you go beyond the numeric limit during a computation you're on your own:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <limits> // for std::numeric_limits
{
unsigned int max = std::numeric_limits<unsigned int>::max();
std::cout << "Max = " << max << std::endl;
std::cout << "Max + 1 = " << max + 1 << " // !" << std::endl;
}
```
%% 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!
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`.
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:
##### 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).
%% Cell type:code id: tags:
``` C++17
#include <cmath>
#include <iostream>
float max_float {std::numeric_limits<float>::max()};
max_float += 1.e+32; // Add something significant enough to max value
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
max_float -= 1.e+32;
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
double nan = 0. / 0.;
std::cout << "Is " << nan << " infinite ? " << std::isinf(nan) << std::endl;
std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl;
nan = nan + 1.e5;
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;
```
%% 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)).
%% Cell type:markdown id: tags:
#### 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.
The difference is related to implicit conversion: both historical initialization methods are ok with implicit conversion __with accuracy loss__:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <iomanip>
{
float f = 1.1234567890123;
double d = 2.1234567890123;
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 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;
}
```
%% Cell type:markdown id: tags:
whereas C++ 11 introduced initialization with braces isn't:
%% Cell type:code id: tags:
``` C++17
{
double d = 2.12345678901234567890;
float f_d{d}; // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
This is really related to **accuracy loss**: initialization with braces is ok if there are none:
%% Cell type:code id: tags:
``` C++17
{
float f = 1.12345678901234567890;
double d_f { f }; // OK
}
```
%% Cell type:markdown id: tags:
Accuracy losses are detected during conversion:
* 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 `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 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:
### Explicit conversions inherited from C
In the case of an explicit conversion, the programmer explicitly says which conversion to use.
C++ inherits the forcing mechanism of the C type:
%% Cell type:code id: tags:
``` C++17
{
unsigned short i = 42000 ;
short j = short(i) ;
unsigned short k = (unsigned short)(j) ;
}
```
%% 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.
%% Cell type:markdown id: tags:
### Explicit conversions by static_cast
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`:
%% Cell type:code id: tags:
``` C++17
{
unsigned short i = 42000;
short j = static_cast<short>(i);
unsigned short k = static_cast<unsigned short>(j);
}
```
%% 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.
%% Cell type:markdown id: tags:
### Other explicit 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!)
* `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).
%% Cell type:markdown id: tags:
### 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.
%% Cell type:code id: tags:
``` C++17
{
double a = 2 / 3;
std::cout << a << std::endl; // a == 0
}
```
%% Cell type:markdown id: tags:
If you really want a `float` division, you have to cast at least one of the value:
%% Cell type:code id: tags:
``` C++17
{
double a = static_cast<float>(2) / 3;
std::cout << a << std::endl;
// this is also valid
double b = 2. / 3;
std::cout << b << std::endl;
}
```
%% Cell type:markdown id: tags:
Please note that the best would be to explicitly cast both values, to avoid useless implicit conversion.
%% Cell type:markdown id: tags:
## Characters and 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`.
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 `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:
``` C++17
#include <iostream>
#include <cstring> // For strlen, strcpy, strncpy
char hello[] = {'h','e','l','l','o', '\0'};
char copy[6] = {}; // = {'\0','\0','\0','\0','\0','\0' };
strcpy(copy, hello);
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
const char* hi = "hi"; // Not putting the const here triggers a warning.
strncpy(copy, hi, strlen(hi));
copy[strlen(hi)] = '\0'; // Don't forget to terminate the string!
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
```
%% 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/).
%% Cell type:markdown id: tags:
### std::string
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
through the standard language library, that provides a much simpler syntax:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <cstring> // For strlen
#include <string> // For std::string
const char* hello_str = "hello";
std::string hello = hello_str;
std::string hi("hi");
std::string copy {};
```
%% Cell type:code id: tags:
``` C++17
copy = hello; // please notice assignment is much more straightforward
std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
const char* copy_str = copy.data(); // Returns a classic C-string (from C++11 onward)
std::cout << "String '" << copy_str << "' is " << strlen(copy_str) << " 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
std::string dynamic {"dynamic std::string"};
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
dynamic = "std::string is dynamical and flexible";
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
```
%% 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):
%% Cell type:code id: tags:
``` C++17
#include <string>
{
std::string cplusplus_string("C++ string!");
const char* c_string = cplusplus_string.c_str();
const char* c_string_2 = cplusplus_string.data();
}
```
%% 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.
%% 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).
%% Cell type:markdown id: tags:
## 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`:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <iomanip> // For std::setprecision
{
typedef double real; // notice the ordering: new typename comes after its value
real radius {1.};
real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
}
```
%% 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...):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <iomanip> // For std::setprecision
{
using real = float; // notice the ordering: more in line with was we're accustomed to when
// initialising variables.
real radius {1.};
real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
}
```
%% Cell type:markdown id: tags:
## `decltype` and `auto`
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.
* `auto` which determines automatically **at compile time** the type of an expression.
%% Cell type:code id: tags:
``` C++17
#include <vector>
{
auto i = 5; // i is here an int.
auto j = 5u; // j is an unsigned int
decltype(j) k; // decltype(j) is interpreted by the compiler as an unsigned int.
}
```
%% 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)):
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 03 way of iterating over the content of a vector.
for (std::vector<unsigned int>::const_iterator it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
It's very verbosy; we could of course use alias:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
using iterator = std::vector<unsigned int>::const_iterator;
// C++ 03 way of iterating over the content of a vector - with an alias
for (iterator it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
But with `decltype` we may write instead:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 decltype
for (decltype(primes.cbegin()) it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
or even better:
%% Cell type:code id: tags:
``` C++17
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 auto
for (auto it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% 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)).
C++ 14 introduced a new one (poorly) called `decltype(auto)` which usefulness will be explained below:
%% Cell type:code id: tags:
``` C++17
#include <algorithm>
#include <iostream>
int i = 5;
int& j = i;
auto k = j;
if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl;
else
std::cout << "j and k are of different type." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
if (std::is_same<decltype(i), decltype(k)>())
std::cout << "i and k are of the same type." << std::endl;
else
std::cout << "i and k are of different type." << std::endl;
```
%% 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...
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:
%% Cell type:code id: tags:
``` C++17
#include <algorithm>
#include <iostream>
{
int i = 5;
int& j = i;
decltype(auto) k = j;
if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl;
else
std::cout << "j and k are of different type." << std::endl;
}
```
%% 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.
%% Cell type:markdown id: tags:
### `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`:
%% Cell type:code id: tags:
``` C++17
#include <algorithm>
#include <string>
#include <iostream>
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 std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl;
```
%% 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)).
%% Cell type:code id: tags:
``` C++17
#include <string>
using namespace std::string_literals;
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
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_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;
```
%% 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.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% 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 won't need much proper function declaration, 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 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.
%% 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 don't guarantee 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:
#### Functions can't be nested, or declared within blocks
#### Functions cannot be nested, or declared within blocks
%% Cell type:markdown id: tags:
Functions can't be nested in C++, contrary to some other languages such as Python:
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 can't 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:
``` 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 don't 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:
### 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; 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:
``` 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 doesn't count!
### **[WARNING]** Return type does not count!
It's not the entire signature of the function that is taken into account when possible ambiguity is sought by the compiler: the return type isn't taken into account. So the following cases won't be accepted:
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 won't work in C!
### **[WARNING]** This is a C++ only feature and will not work in C!
%% Cell type:markdown id: tags:
In C you can't do that: if you run a simple program with overload:
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: don't 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:
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++](./) - [Bibliography](./bibliography.ipynb)
%% Cell type:markdown id: tags:
## Books
### Effective C++ / More Effective C++
Albeit being now quite old (and therefore pre-C++ 11), these books are still to my mind highly recommended reading: Scott Meyers explain several items in few pages each with a very precious insight on why some idioms should be employed. The style is rather enjoyable: the reading is easy, which is not a small feat for a programming book!
Scott Meyers present _Effective C++_ as the second book you should read about C++, the first one being the one explaing the basics to you. Hopefully this formation will play the role of the first book for you, allowing you to jump on the bandwagon and reading these directly.
- [Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma99587823606533&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,scott%20meyers&offset=0) by Scott Meyers.
- [More Effective C++: 35 New Ways to Improve Your Programs and Designs](https://search.library.ucla.edu/discovery/fulldisplay?vid=01UCS_LAL:UCLA&search_scope=ArticlesBooksMore&tab=Articles_books_more_slot&docid=alma99587833606533&lang=en&context=L&adaptor=Local%20Search%20Engine&query=any,contains,scott%20meyers&offset=0&virtualBrowse=true) by Scott Meyers.
%% Cell type:markdown id: tags:
### Effective Modern C++
Scott Meyers's most recent C++ book, and probably his last (he announced his retirement from C++ few years ago). Very helpful to grasp many new C++ 11/14 concepts (its explanation of move semantics is really clear and corrected for me many unexact stuff read on the Web on the topic) but a bit steeper for a beginner. So also a very recommended reading, but probably once you've read the two precedent entries before.
- [Effective modern C++ : 42 specific ways to improve your use of C++11 and C++14](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma9914814323506531&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,effective%20modern%20C%2B%2B&offset=0) by Scott Meyers.
%% Cell type:markdown id: tags:
### The C++ Standard Library: A Tutorial and Reference
A very helpful reference guide to know what is offered in the C++ standard library. The second edition has been updated to add many new features introduced in C++ 11.
- [The C++ Standard Library: A Tutorial and Reference, Second Edition](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma9914810872606531&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,nicolai%20josuttis&offset=0) by Nicolai M. Josuttis.
%% Cell type:markdown id: tags:
### Modern C++ Design
A very brilliant book to understand the reasoning behind metaprogramming, by one of the father of this branch of software development. Clearly not for the faint of heart, but a really enlightening reading; I especially recommend the chapter about policies (the first one is also very interesting but some of the stuff there is now directly available in the standard library).
- [ Modern C++ design : generic programming and design patterns applied](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma9943936983606533&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,andrei%20alexandrescu&offset=0) by Andrei Alexandrescu.
%% Cell type:markdown id: tags:
### Exceptional C++ / More Exceptional C++
These books are questions and answers that provide very helpful insight; the reading is however a bit terser than Scott Meyers' books. The content is a bit more advanced - in introduction Herb Sutter tells his books should be the third reading after Meyers' Effective C++ one...
- [Exceptional C++ : 47 engineering puzzles, programming problems, and solutions](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma99588013606533&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,exceptional%20C%2B%2B&offset=0) by Herb Sutter.
- [More exceptional C++ : 40 new engineering puzzles, programming problems, and solutions](https://search.library.ucla.edu/discovery/fulldisplay?vid=01UCS_LAL:UCLA&search_scope=ArticlesBooksMore&tab=Articles_books_more_slot&docid=alma99588043606533&lang=en&context=L&adaptor=Local%20Search%20Engine&query=any,contains,exceptional%20C%2B%2B&offset=0&virtualBrowse=true) by Herb Sutter.
%% Cell type:markdown id: tags:
### Effective STL
The only Scott Meyers book I would not recommend entirely heartily: as always it offers interesting insight, but it tries a bit too hard to sell the horrendous syntax required by many algorithms prior to the C++ 11. Still a good reading, but don't start by this one.
- [Effective STL : 50 specific ways to improve your use of the standard template library](https://search.library.ucla.edu/discovery/fulldisplay?vid=01UCS_LAL:UCLA&search_scope=ArticlesBooksMore&tab=Articles_books_more_slot&docid=alma99587823606533&lang=en&context=L&adaptor=Local%20Search%20Engine&query=any,contains,scott%20meyers&offset=0&virtualBrowse=true) by Scott Meyers.
%% Cell type:markdown id: tags:
### Functional Programming in C++
A very interesting book arguing C++ may be a rather good fit for the functional paradigm, especially in its later versions.
I (Sébastien) learnt a lot reading it; however the difficulty threshold is a wild ride here (some chapters are rather trivial and others... not so much...)
- [Functional programming in C++](https://search.library.ucla.edu/discovery/fulldisplay?docid=alma9914812800006531&context=L&vid=01UCS_LAL:UCLA&lang=en&search_scope=ArticlesBooksMore&adaptor=Local%20Search%20Engine&tab=Articles_books_more_slot&query=any,contains,functional%20programming%20in%20C%2B%2B&offset=0) by Ivan Čukić.
%% Cell type:markdown id: tags:
## Online
%% Cell type:markdown id: tags:
### [CppReference](https://en.cppreference.com)
Provides a dictionary entry with the API explained for every functions/algorithms/class/you name it from the language and the standard library. Usually you will find them upfront if you Google a term from C++ (e.g. `std::vector`). Explanation might be a bit terse, but there is now in most cases an example powered by Coliru which present a concrete (albeit simple - but you're accustomed to that if you followed this tutorial!) use case.
### [isocpp](https://isocpp.org/faq)
A gigantic FAQ that covers many aspects of C++; this site is in fact a merge from two previously independent FAQ, one of which was maintained by Bjarne Stroustrup, creator of the language.
### [FluentCpp](https://www.fluentcpp.com)
A very interesting blog with lots of interesting content, unfortunately not updated since June 2022 (but it's still a gold mine)
### [SED Saclay technology watch](https://sed.saclay.inria.fr/kw_c++.html)
In the experimentation and development departement ([SED](http://sed.saclay.inria.fr/)) at [Inria Saclay](https://www.inria.fr/centre/saclay), we publish about each week few links we found interesting. C++ is rather often mentioned; the link above provides all such links related to C++ since we started doing so.
### [C++ Weekly With Jason Turner](https://www.youtube.com/@cppweekly)
A youtube channel covering various aspects of very modern C++, with a new episode uploaded weekly. Each episode focuses on one specific topic and keeps it short (5 to 10mn videos).
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment