Commit faab357a authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Add functor in the alternatives to pointer to functions.

parent af840dd8
......@@ -82,7 +82,7 @@
"{\n",
" void subfunction() // COMPILATION ERROR!\n",
" {\n",
" ...\n",
" \n",
" } \n",
"}"
]
......@@ -182,7 +182,7 @@
"metadata": {},
"outputs": [],
"source": [
"[[nodiscard]] int compute_division(int arg1, int arg2, int& quotient, int& remainder)\n",
"int compute_division(int arg1, int arg2, int& quotient, int& remainder)\n",
"{\n",
" if (arg2 == 0)\n",
" return -1; // error code.\n",
......@@ -506,9 +506,18 @@
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Initial list = 3 5 2 -4 8 -17 99 15 125447 0 -1246 \n",
"Sorted list = -17 3 5 15 99 125447 -1246 -4 0 2 8 "
]
}
],
"source": [
"#include <vector> \n",
"#include <iostream>\n",
......@@ -569,7 +578,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
......@@ -583,7 +592,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
......@@ -595,7 +604,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
......@@ -607,9 +616,18 @@
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"f(5, 6) = 30\n",
"f(5, 6) = 11\n"
]
}
],
"source": [
"PrintFunctionCall(multiply, 5, 6);\n",
"PrintFunctionCall(add, 5, 6);"
......@@ -622,6 +640,7 @@
"There are other ways to do this task:\n",
"\n",
"* Using a template parameter. Templates will be reached [later in this tutorial](http://localhost:8888/notebooks/4-Templates/0-main.ipynb), but for me it's usually the way to go.\n",
"* Using [functors]()\n",
"* 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:
# [Getting started in C++](/) - [Procedural programming](/notebooks/1-ProceduralProgramming/0-main.ipynb) - [Functions](/notebooks/1-ProceduralProgramming/4-Functions.ipynb)
%% Cell type:markdown id: tags:
<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="#Lambda-functions" data-toc-modified-id="Lambda-functions-3">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-4">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-5">A very special function: <strong>main</strong></a></span></li><li><span><a href="#inline-functions" data-toc-modified-id="inline-functions-6"><code>inline</code> functions</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## 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.
`void` is a special type to indicate a function doesn't return any value.
__BEWARE__: Functions can't be defined in blocks, so my trick to circumvent cling limitation doesn't work. Please execute only once the cells with function definitions! (restart the kernel if you need to modify the function content).
%% Cell type:code id: tags:
``` C++17
// Please execute it only once!
#include <iostream>
void PrintDivision(int arg1, int arg2)
{
if (arg2 == 0)
std::cerr << "Failure: division by zero!" << std::endl;
else
{
int division ;
division = arg1/arg2 ;
std::cout << arg1 << " / " << arg2 << " = " << division << std::endl ;
}
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintDivision(12, 3);
PrintDivision(6, 0);
PrintDivision(15, 6);
}
```
%% Cell type:markdown id: tags:
Functions can't be nested in C++, contrary to some other langages such as Python:
%% Cell type:code id: tags:
``` C++17
void function_1() // 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](/notebooks/6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule.
%% Cell type:markdown id: tags:
### 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
// Please execute it only once!
#include <iostream>
void increment_and_print(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!
increment_and_print(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 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:
### Passing arguments 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:
``` C++17
// Please execute it only once!
#include <iostream>
void increment_and_print_by_reference(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!
increment_and_print_by_reference(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 work around 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
[[nodiscard]] 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)
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 (compute_division(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (compute_division(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 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](/notebooks/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:
%% Cell type:code id: tags:
``` C++17
// Please execute it only once!
#include <iostream>
void print_division(int arg1, int arg2)
{
int quotient, remainder;
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 "
<< quotient << " and a remainder of " << remainder << std::endl;
}
print_division(8, 5);
print_division(8, 0); // bug!
```
%% Cell type:markdown id: tags:
The developer made two important mistakes:
* 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.
%% 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.
%% 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 with `*i` is not completely costless).
%% Cell type:code id: tags:
``` C++17
// Please execute it only once!
#include <iostream>
void increment_and_print_by_pointer(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!
increment_and_print_by_pointer(&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:
## 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.
%% 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 a_plus_b = [](int b)
{
return a + b;
};
std::cout << a_plus_b(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 a_plus_b = [a](int b) // Notice the `[a]` here!
{
return a + b;
};
std::cout << a_plus_b(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 add_to_a = [&a](int b) // Notice the `[&a]` here!
{
a += b;
return a;
};
add_to_a(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 sort function provided by the standard library, in which for some reasons we want to sort integers but in two blocks: first the odd numbers properly ordered and then the even numbers:
%% Cell type:code id: tags:
``` C++17
// Nice Jupyter Xeus-cling feature that doesn't work for the Conda version of cling in macOS.
?std::sort
```
%% 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 is 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 << ' ';
}
```
%%%% Output: stream
Initial list = 3 5 2 -4 8 -17 99 15 125447 0 -1246
Sorted list = -17 3 5 15 99 125447 -1246 -4 0 2 8
%% 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 is:
`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);
```
%%%% Output: stream
f(5, 6) = 30
f(5, 6) = 11
%% 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](http://localhost:8888/notebooks/4-Templates/0-main.ipynb), but for me it's usually the way to go.
* Using [functors]()
* 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 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](/notebooks/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.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment