Commit a3e5930f authored by ROUVREAU Vincent's avatar ROUVREAU Vincent Committed by GILLES Sebastien
Browse files

Replace hard coded notebook links with relative links

parent c8fd884c
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Function-definition" data-toc-modified-id="Function-definition-1">Function definition</a></span><ul class="toc-item"><li><span><a href="#Passing-arguments-by-value" data-toc-modified-id="Passing-arguments-by-value-1.1">Passing arguments by value</a></span></li><li><span><a href="#Passing-arguments-by-reference" data-toc-modified-id="Passing-arguments-by-reference-1.2">Passing arguments by reference</a></span></li><li><span><a href="#A-bit-of-wandering:-using-C-like-error-codes" data-toc-modified-id="A-bit-of-wandering:-using-C-like-error-codes-1.3">A bit of wandering: using C-like error codes</a></span></li><li><span><a href="#Passing-arguments-by-pointers" data-toc-modified-id="Passing-arguments-by-pointers-1.4">Passing arguments by pointers</a></span></li></ul></li><li><span><a href="#Function-with-return-value" data-toc-modified-id="Function-with-return-value-2">Function with return value</a></span></li><li><span><a href="#Alternate-function-syntax" data-toc-modified-id="Alternate-function-syntax-3">Alternate function syntax</a></span></li><li><span><a href="#Function-overload" data-toc-modified-id="Function-overload-4">Function overload</a></span><ul class="toc-item"><li><span><a href="#The-easy-cases:-arguments-without-ambiguity" data-toc-modified-id="The-easy-cases:-arguments-without-ambiguity-4.1">The easy cases: arguments without ambiguity</a></span></li><li><span><a href="#[WARNING]-Return-type-doesn't-count!" data-toc-modified-id="[WARNING]-Return-type-doesn't-count!-4.2">[WARNING] Return type doesn't count!</a></span></li><li><span><a href="#Good-practice:-don't-make-signature-vary-only-by-a-reference-or-a-pointer" data-toc-modified-id="Good-practice:-don't-make-signature-vary-only-by-a-reference-or-a-pointer-4.3">Good practice: don't make signature vary only by a reference or a pointer</a></span></li><li><span><a href="#Best-viable-function" data-toc-modified-id="Best-viable-function-4.4">Best viable function</a></span></li><li><span><a href="#Advice:-use-overload-only-when-there-is-no-ambiguity-whatsoever" data-toc-modified-id="Advice:-use-overload-only-when-there-is-no-ambiguity-whatsoever-4.5">Advice: use overload only when there is no ambiguity whatsoever</a></span></li></ul></li><li><span><a href="#Optional-parameters" data-toc-modified-id="Optional-parameters-5">Optional parameters</a></span></li><li><span><a href="#Lambda-functions" data-toc-modified-id="Lambda-functions-6">Lambda functions</a></span></li><li><span><a href="#Passing-a-function-as-a-an-argument" data-toc-modified-id="Passing-a-function-as-a-an-argument-7">Passing a function as a an argument</a></span></li><li><span><a href="#A-very-special-function:-main" data-toc-modified-id="A-very-special-function:-main-8">A very special function: <strong>main</strong></a></span></li><li><span><a href="#inline-functions" data-toc-modified-id="inline-functions-9"><code>inline</code> functions</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Function-definition" data-toc-modified-id="Function-definition-1">Function definition</a></span><ul class="toc-item"><li><span><a href="#Passing-arguments-by-value" data-toc-modified-id="Passing-arguments-by-value-1.1">Passing arguments by value</a></span></li><li><span><a href="#Passing-arguments-by-reference" data-toc-modified-id="Passing-arguments-by-reference-1.2">Passing arguments by reference</a></span></li><li><span><a href="#A-bit-of-wandering:-using-C-like-error-codes" data-toc-modified-id="A-bit-of-wandering:-using-C-like-error-codes-1.3">A bit of wandering: using C-like error codes</a></span></li><li><span><a href="#Passing-arguments-by-pointers" data-toc-modified-id="Passing-arguments-by-pointers-1.4">Passing arguments by pointers</a></span></li></ul></li><li><span><a href="#Function-with-return-value" data-toc-modified-id="Function-with-return-value-2">Function with return value</a></span></li><li><span><a href="#Alternate-function-syntax" data-toc-modified-id="Alternate-function-syntax-3">Alternate function syntax</a></span></li><li><span><a href="#Function-overload" data-toc-modified-id="Function-overload-4">Function overload</a></span><ul class="toc-item"><li><span><a href="#The-easy-cases:-arguments-without-ambiguity" data-toc-modified-id="The-easy-cases:-arguments-without-ambiguity-4.1">The easy cases: arguments without ambiguity</a></span></li><li><span><a href="#[WARNING]-Return-type-doesn't-count!" data-toc-modified-id="[WARNING]-Return-type-doesn't-count!-4.2">[WARNING] Return type doesn't count!</a></span></li><li><span><a href="#Good-practice:-don't-make-signature-vary-only-by-a-reference-or-a-pointer" data-toc-modified-id="Good-practice:-don't-make-signature-vary-only-by-a-reference-or-a-pointer-4.3">Good practice: don't make signature vary only by a reference or a pointer</a></span></li><li><span><a href="#Best-viable-function" data-toc-modified-id="Best-viable-function-4.4">Best viable function</a></span></li><li><span><a href="#Advice:-use-overload-only-when-there-is-no-ambiguity-whatsoever" data-toc-modified-id="Advice:-use-overload-only-when-there-is-no-ambiguity-whatsoever-4.5">Advice: use overload only when there is no ambiguity whatsoever</a></span></li></ul></li><li><span><a href="#Optional-parameters" data-toc-modified-id="Optional-parameters-5">Optional parameters</a></span></li><li><span><a href="#Lambda-functions" data-toc-modified-id="Lambda-functions-6">Lambda functions</a></span></li><li><span><a href="#Passing-a-function-as-a-an-argument" data-toc-modified-id="Passing-a-function-as-a-an-argument-7">Passing a function as a an argument</a></span></li><li><span><a href="#A-very-special-function:-main" data-toc-modified-id="A-very-special-function:-main-8">A very special function: <strong>main</strong></a></span></li><li><span><a href="#inline-functions" data-toc-modified-id="inline-functions-9"><code>inline</code> functions</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function definition ## Function definition
To be usable in a C++ instruction, a function must be __defined__ beforehand. This definition includes the name and type of the arguments, the type of the return value and the instruction block that make up the function. To be usable in a C++ instruction, a function must be __defined__ beforehand. This definition includes the name and type of the arguments, the type of the return value and the instruction block that make up the function.
`void` is a special type to indicate a function doesn't return any value. `void` is a special type to indicate a function doesn't return any value.
__BEWARE__: Functions can't be defined in blocks. __BEWARE__: Functions can't be defined in blocks.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void PrintDivision(int arg1, int arg2) void PrintDivision(int arg1, int arg2)
{ {
if (arg2 == 0) if (arg2 == 0)
std::cerr << "Failure: division by zero!" << std::endl; std::cerr << "Failure: division by zero!" << std::endl;
else else
{ {
int division ; int division ;
division = arg1/arg2 ; division = arg1/arg2 ;
std::cout << arg1 << " / " << arg2 << " = " << division << std::endl ; std::cout << arg1 << " / " << arg2 << " = " << division << std::endl ;
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
PrintDivision(12, 3); PrintDivision(12, 3);
PrintDivision(6, 0); PrintDivision(6, 0);
PrintDivision(15, 6); PrintDivision(15, 6);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Functions can't be nested in C++, contrary to some other langages such as Python: Functions can't be nested in C++, contrary to some other langages such as Python:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void function_1() // a function might have no arguments void function_1() // a function might have no arguments
{ {
void subfunction() // COMPILATION ERROR! void subfunction() // COMPILATION ERROR!
{ {
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule. To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Passing arguments by value ### Passing arguments by value
In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function: In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void increment_and_print(int i) void increment_and_print(int i)
{ {
++i; ++i;
std::cout << "Inside the function: i = " << i << std::endl; std::cout << "Inside the function: i = " << i << std::endl;
} }
{ {
int j = 5; // I could have named it differently - it doesn't matter as the scope is different! int j = 5; // I could have named it differently - it doesn't matter as the scope is different!
increment_and_print(j); increment_and_print(j);
std::cout << "Outside the function: i = " << j << std::endl; std::cout << "Outside the function: i = " << j << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `i` in the block body and in the function definition is not the same: one or the other could have been named differently and the result would have been the same. The `i` in the block body and in the function definition is not the same: one or the other could have been named differently and the result would have been the same.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Passing arguments by reference ### Passing arguments by reference
If we intended to modify the value of `i` outside the function, we should have passed it by reference: If we intended to modify the value of `i` outside the function, we should have passed it by reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void increment_and_print_by_reference(int& i) void increment_and_print_by_reference(int& i)
{ {
++i; ++i;
std::cout << "Inside the function: i = " << i << std::endl; std::cout << "Inside the function: i = " << i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
increment_and_print_by_reference(i); increment_and_print_by_reference(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As in C++ you 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 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).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int compute_division(int arg1, int arg2, int& quotient, int& remainder) int compute_division(int arg1, int arg2, int& quotient, int& remainder)
{ {
if (arg2 == 0) if (arg2 == 0)
return -1; // error code. return -1; // error code.
quotient = arg1 / arg2; quotient = arg1 / arg2;
remainder = arg1 % arg2; remainder = arg1 % arg2;
return 0; // code when everything is alright. return 0; // code when everything is alright.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int quotient, remainder; int quotient, remainder;
if (compute_division(5, 3, quotient, remainder) == 0) if (compute_division(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (compute_division(5, 0, quotient, remainder) == 0) if (compute_division(5, 0, quotient, remainder) == 0)
std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
else else
std::cerr << "Can't divide by 0!" << std::endl; std::cerr << "Can't divide by 0!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### A bit of wandering: using C-like error codes ### A bit of wandering: using C-like error codes
The function above gets two outputs: the quotient and the remainder of the euclidian division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider. The function above gets two outputs: the quotient and the remainder of the euclidian 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 `compute_division()` 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. 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 `compute_division()` 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.
Below is an example where things go awry due to the lack of check: Below is an example where things go awry due to the lack of check:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void print_division(int arg1, int arg2) void print_division(int arg1, int arg2)
{ {
int quotient, remainder; int quotient, remainder;
compute_division(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code. compute_division(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidian division of " << arg1 << " by " << arg2 << " yields a quotient of " std::cout << "Euclidian division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl; << quotient << " and a remainder of " << remainder << std::endl;
} }
print_division(8, 5); print_division(8, 5);
print_division(8, 0); // bug! print_division(8, 0); // bug!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The developer made two important mistakes: The developer made two important mistakes:
* The return value of `compute_division` is not checked, so something is printed on screen. * The return value of `compute_division` is not checked, so something is printed on screen.
* This something is 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 something is 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.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**UPDATE:** I still do not advise to use error codes, but the [nodiscard](https://en.cppreference.com/w/cpp/language/attributes/nodiscard) attribute introduced in C++ 17 helps your compiler to warn you when the return value was left unused. **UPDATE:** I still do not advise to use error codes, but the [nodiscard](https://en.cppreference.com/w/cpp/language/attributes/nodiscard) attribute introduced in C++ 17 helps your compiler to warn you when the return value was left unused.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Passing arguments by pointers ### Passing arguments by pointers
When the argument of a function is a pointer, each function call When the argument of a function is a pointer, each function call
results in the creation of a temporary pointer which is given the address provided as argument. Then using the `*` operator, you can access the results in the creation of a temporary pointer which is given the address provided as argument. Then using the `*` operator, you can access the
original variable, not a copy. original variable, not a copy.
Except in the case of interaction with a C library or some _very_ specific cases, I wouldn't advise using passing arguments by pointers: by reference does the job as neatly and in fact more efficiently (dereferencing a pointer with `*i` is not completely costless). 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 with `*i` is not completely costless).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void increment_and_print_by_pointer(int* i) void increment_and_print_by_pointer(int* i)
{ {
*i += 1; *i += 1;
std::cout << "Inside the function: i = " << *i << std::endl; std::cout << "Inside the function: i = " << *i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
increment_and_print_by_pointer(&i); increment_and_print_by_pointer(&i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function with return value ## Function with return value
The value to return should come after the keyword `return`. The value to return should come after the keyword `return`.
A C++ function may include several return values in its implementation: A C++ function may include several return values in its implementation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
//! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative. //! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative.
int sign(int a) int sign(int a)
{ {
if (a > 0) if (a > 0)
return 1; return 1;
if (a == 0) if (a == 0)
return 0; return 0;
return -1; return -1;
} }
{ {
for (int a = -3; a < 4; ++a) for (int a = -3; a < 4; ++a)
std::cout << "Sign of " << a << " = " << sign(a) << std::endl; std::cout << "Sign of " << a << " = " << sign(a) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Alternate function syntax ## Alternate function syntax
There is now since C++ 11 another way to declare a function; 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; it is not widespread but is advised by some developers (see for instance [this blog post](https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/) which lists pros and cons of both syntaxes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
auto sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment auto sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The return value is optional (and was the reason of Xeus-cling failure): The return value is optional (and was the reason of Xeus-cling failure):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
auto sum(int a, int b) // compiles just fine in Xeus-cling auto sum(int a, int b) // compiles just fine in Xeus-cling
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
int a = 8; int a = 8;
int b = -3; int b = -3;
std::cout << a << " + " << b << " = " << sum(a, b) << std::endl; std::cout << a << " + " << b << " = " << sum(a, b) << std::endl;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function overload ## Function overload
### The easy cases: arguments without ambiguity ### The easy cases: arguments without ambiguity
It is possible to define several different functions with the exact same name, provided the type of the argument differ: It is possible to define several different functions with the exact same name, provided the type of the argument differ:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
void f(); void f();
void f(int); // Ok void f(int); // Ok
double f(int, double); // Ok double f(int, double); // Ok
auto f(char) -> int; // Ok (alternate function syntax) auto f(char) -> int; // Ok (alternate function syntax)
std::string f(double, int, char*); // Ok std::string f(double, int, char*); // Ok
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [WARNING] Return type doesn't count! ### [WARNING] Return type doesn't 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'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:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void g(int); void g(int);
int g(int); // COMPILATION ERROR int g(int); // COMPILATION ERROR
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If we think about it, it is rather logical: in C++ we are not required to use the return type of a function (it's not the case in all languages: Go follows a different path on that topic for instance). The issue then is that the compiler has no way to know which `g(int)` is supposed to be called with `g(5)` for instance. If we think about it, it is rather logical: in C++ we are not required to use the return type of a function (it's not the case in all languages: Go follows a different path on that topic for instance). The issue then is that the compiler has no way to know which `g(int)` is supposed to be called with `g(5)` for instance.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Good practice: don't make signature vary only by a reference or a pointer ### Good practice: don't make signature vary only by a reference or a pointer
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On the other hand, compiler is completely able to accept signatures that differs only by a reference or a pointer on one of the argument: On the other hand, compiler is completely able to accept signatures that differs only by a reference or a pointer on one of the argument: