Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 04db67c4 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Merge branch '14_introspection' into 'master'

#14 Function notebook: remove Xeus cling introspection and add instead links

Closes #14

See merge request !51
parents 56c0191a eb939372
No related branches found
No related tags found
1 merge request!51#14 Function notebook: remove Xeus cling introspection and add instead links
%% 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-declaration-and-definition" data-toc-modified-id="Function-declaration-and-definition-1">Function declaration and definition</a></span><ul class="toc-item"><li><span><a href="#Function-declaration" data-toc-modified-id="Function-declaration-1.1">Function declaration</a></span></li><li><span><a href="#Function-definition" data-toc-modified-id="Function-definition-1.2">Function definition</a></span><ul class="toc-item"><li><span><a href="#A-terminology-note:-parameter-and-argument" data-toc-modified-id="A-terminology-note:-parameter-and-argument-1.2.1">A terminology note: <em>parameter</em> and <em>argument</em></a></span></li><li><span><a href="#Functions-can't-be-nested,-or-declared-within-blocks" data-toc-modified-id="Functions-can't-be-nested,-or-declared-within-blocks-1.2.2">Functions can't be nested, or declared within blocks</a></span></li></ul></li></ul></li><li><span><a href="#How-to-pass-arguments" data-toc-modified-id="How-to-pass-arguments-2">How to pass arguments</a></span><ul class="toc-item"><li><span><a href="#Passing-arguments-by-value" data-toc-modified-id="Passing-arguments-by-value-2.1">Passing arguments by value</a></span></li><li><span><a href="#Passing-arguments-by-reference" data-toc-modified-id="Passing-arguments-by-reference-2.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-2.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-2.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-3">Function with return value</a></span></li><li><span><a href="#Alternate-function-syntax" data-toc-modified-id="Alternate-function-syntax-4">Alternate function syntax</a></span></li><li><span><a href="#Function-overload" data-toc-modified-id="Function-overload-5">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-5.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!-5.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-5.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-5.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-5.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-6">Optional parameters</a></span></li><li><span><a href="#Lambda-functions" data-toc-modified-id="Lambda-functions-7">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-8">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-9">A very special function: <strong>main</strong></a></span></li><li><span><a href="#inline-functions" data-toc-modified-id="inline-functions-10"><code>inline</code> functions</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Function-declaration-and-definition" data-toc-modified-id="Function-declaration-and-definition-1">Function declaration and definition</a></span><ul class="toc-item"><li><span><a href="#Function-declaration" data-toc-modified-id="Function-declaration-1.1">Function declaration</a></span></li><li><span><a href="#Function-definition" data-toc-modified-id="Function-definition-1.2">Function definition</a></span><ul class="toc-item"><li><span><a href="#A-terminology-note:-parameter-and-argument" data-toc-modified-id="A-terminology-note:-parameter-and-argument-1.2.1">A terminology note: <em>parameter</em> and <em>argument</em></a></span></li><li><span><a href="#Functions-can't-be-nested,-or-declared-within-blocks" data-toc-modified-id="Functions-can't-be-nested,-or-declared-within-blocks-1.2.2">Functions can't be nested, or declared within blocks</a></span></li></ul></li></ul></li><li><span><a href="#How-to-pass-arguments" data-toc-modified-id="How-to-pass-arguments-2">How to pass arguments</a></span><ul class="toc-item"><li><span><a href="#Passing-arguments-by-value" data-toc-modified-id="Passing-arguments-by-value-2.1">Passing arguments by value</a></span></li><li><span><a href="#Passing-arguments-by-reference" data-toc-modified-id="Passing-arguments-by-reference-2.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-2.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-2.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-3">Function with return value</a></span></li><li><span><a href="#Alternate-function-syntax" data-toc-modified-id="Alternate-function-syntax-4">Alternate function syntax</a></span></li><li><span><a href="#Function-overload" data-toc-modified-id="Function-overload-5">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-5.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!-5.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-5.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-5.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-5.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-6">Optional parameters</a></span></li><li><span><a href="#Lambda-functions" data-toc-modified-id="Lambda-functions-7">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-8">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-9">A very special function: <strong>main</strong></a></span></li><li><span><a href="#inline-functions" data-toc-modified-id="inline-functions-10"><code>inline</code> functions</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function declaration and definition ## Function declaration and definition
### Function declaration ### Function declaration
The aim of a **function declaration** is just to describe its prototype: The aim of a **function declaration** is just to describe its prototype:
- The return value of the function (or `void` if the function returns nothing). - The return value 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). - 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).
Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere. Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere.
Few examples of function declarations: Few examples of function declarations:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int ComputeMinimum(int a, int b); int ComputeMinimum(int a, int b);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void DoStuff(double); // naming the parameter is optional void DoStuff(double); // naming the parameter is optional
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int ReturnFive(); // providing a parameter is also optional... int ReturnFive(); // providing a parameter is also optional...
// Don't bother Xeus-cling warning: it IS here a function declaration! // Don't bother Xeus-cling warning: it IS here a function declaration!
``` ```
%% Cell type:markdown id: tags: %% 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 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.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Function definition ### Function definition
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand. On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand.
In a function definition: In a function definition:
- No semicolon after the prototype - No semicolon after the prototype
- A block follows the prototype instead; inside this block the implementation is written. - A block follows the prototype instead; inside this block the implementation is written.
- Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation. - Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation.
For instance: For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void PrintDivision(int numerator, int denominator) // no semicolon here! void PrintDivision(int numerator, int denominator) // no semicolon here!
{ {
if (denominator == 0) if (denominator == 0)
std::cout << "Failure: division by zero!" << std::endl; std::cout << "Failure: division by zero!" << std::endl;
else else
{ {
int division ; int division ;
division = numerator / denominator ; division = numerator / denominator ;
std::cout << numerator << " / " << numerator << " = " << division << std::endl ; std::cout << numerator << " / " << numerator << " = " << division << std::endl ;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and when the function is invoked at some point, the implementation above is directly put in motion: and when the function is invoked at some point, the implementation above is directly put in motion:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int num = 3; int num = 3;
int denom = 2; int denom = 2;
PrintDivision(num, denom); PrintDivision(num, denom);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### A terminology note: _parameter_ and _argument_ #### A terminology note: _parameter_ and _argument_
In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**. In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**.
For the purists: For the purists:
- **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition) - **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition)
- **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell) - **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell)
I 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 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).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Functions can't be nested, or declared within blocks #### Functions can't be nested, or declared within blocks
%% 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 Function1() // a function might have no arguments void Function1() // a function might have no arguments
{ {
void Subfunction() // COMPILATION ERROR! void Subfunction() // COMPILATION ERROR!
{ {
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule. To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## How to pass arguments ## How to pass arguments
### Passing arguments by value ### Passing arguments by value
In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function: In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void IncrementAndPrint(int i) void IncrementAndPrint(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!
IncrementAndPrint(i); IncrementAndPrint(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `i` in the block body and in the function definition are not the same: one or the other could have been named differently and the result would have been the same: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void IncrementAndPrint2(int local_argument) void IncrementAndPrint2(int local_argument)
{ {
++local_argument; ++local_argument;
std::cout << "Inside the function: local_argument = " << local_argument << std::endl; 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! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrint2(i); IncrementAndPrint2(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:
### Passing arguments by reference ### Passing arguments by reference
If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference: If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void IncrementAndPrintByReference(int& i) void IncrementAndPrintByReference(int& i)
{ {
++i; ++i;
std::cout << "Inside the function: i = " << i << std::endl; std::cout << "Inside the function: i = " << i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByReference(i); IncrementAndPrintByReference(i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As in C++ you 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 ComputeDivision(int arg1, int arg2, int& quotient, int& remainder) int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)
{ {
if (arg2 == 0) if (arg2 == 0)
return -1; // error code. return -1; // error code.
quotient = arg1 / arg2; quotient = arg1 / arg2;
remainder = arg1 % arg2; remainder = arg1 % arg2;
return 0; // code when everything is alright. return 0; // code when everything is alright.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int quotient, remainder; int quotient, remainder;
if (ComputeDivision(5, 3, quotient, remainder) == 0) if (ComputeDivision(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (ComputeDivision(5, 0, quotient, remainder) == 0) if (ComputeDivision(5, 0, quotient, remainder) == 0)
std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl; std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
else else
std::cout << "Can't divide by 0!" << std::endl; std::cout << "Can't divide by 0!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### A bit of wandering: using C-like error codes ### A bit of wandering: using C-like error codes
The function above gets two outputs: the quotient and the remainder of the euclidean division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider. The function above gets two outputs: the quotient and the remainder of the euclidean division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider.
Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `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 (and discuss a bit more error codes as well). Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `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 (and discuss a bit more error codes as well).
Below is an example where things go awry due to the lack of check: Below is an example where things go awry due to the lack of check:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void PrintDivision(int arg1, int arg2) void PrintDivision(int arg1, int arg2)
{ {
int quotient, remainder; int quotient, remainder;
ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code. ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of " std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl; << quotient << " and a remainder of " << remainder << std::endl;
} }
PrintDivision(8, 5); PrintDivision(8, 5);
PrintDivision(8, 0); // bug! PrintDivision(8, 0); // 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 `ComputeDivision` is not checked, so something is printed on screen. * The return value of `ComputeDivision` is not checked, so something is printed on screen.
* This is something completely out of control: quotient and remainder 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 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:
### 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 IncrementAndPrintByPointer(int* i) void IncrementAndPrintByPointer(int* i)
{ {
*i += 1; *i += 1;
std::cout << "Inside the function: i = " << *i << std::endl; std::cout << "Inside the function: i = " << *i << std::endl;
} }
{ {
int i = 5; // I could have named it differently - it doesn't matter as the scope is different! int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByPointer(&i); IncrementAndPrintByPointer(&i);
std::cout << "Outside the function: i = " << i << std::endl; std::cout << "Outside the function: i = " << i << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function with return value ## Function with return value
The value to return should come after the keyword `return`. The value to return should come after the keyword `return`.
A C++ function may include several return values in its implementation: A C++ function may include several return values in its implementation:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++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:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void H(double a) void H(double a)
{ {
std::cout << "h(double) is called with a = " << a << '.' << std::endl; std::cout << "h(double) is called with a = " << a << '.' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void H(double* a) // Ok void H(double* a) // Ok
{ {
std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl; std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.; *a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void H(double& a) // Ok... but not advised! (see below) void H(double& a) // Ok... but not advised! (see below)
{ {
std::cout << "h(double&) is called with a = " << a << "; a is doubled by the function." << std::endl; std::cout << "h(double&) is called with a = " << a << "; a is doubled by the function." << std::endl;
a *= 2.; a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
H(5); // Ok H(5); // Ok
double x = 1.; double x = 1.;
H(&x); // Ok H(&x); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, there is a possible ambiguity between the pass-by-copy and pass-by-reference: However, there is a possible ambiguity between the pass-by-copy and pass-by-reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double x = 1.; double x = 1.;
H(x); // COMPILATION ERROR: should it call h(double) or h(double& )? H(x); // COMPILATION ERROR: should it call h(double) or h(double& )?
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You can lift the ambiguity for the pass-by-value: You can lift the ambiguity for the pass-by-value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double x = 1.; double x = 1.;
H(static_cast<double>(x)); // Ok H(static_cast<double>(x)); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But not to my knowledge for the pass-by-reference... So you should really avoid doing so: if you really need both functions, name them differently to avoid the ambiguity. But not to my knowledge for the pass-by-reference... So you should really avoid doing so: if you really need both functions, name them differently to avoid the ambiguity.
I would even avoid the pointer case: granted, there is no ambiguity for a computer standpoint, but if you get a developer who is not 100% clear about the pointer syntax he might end-up calling the wrong function: I would even avoid the pointer case: granted, there is no ambiguity for a computer standpoint, but if you get a developer who is not 100% clear about the pointer syntax he might end-up calling the wrong function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void H2(double a) void H2(double a)
{ {
std::cout << "h2(double) is called with a = " << a << '.' << std::endl; std::cout << "h2(double) is called with a = " << a << '.' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void H2(double* a) void H2(double* a)
{ {
std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl; std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.; *a *= 2.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double x = 5.; double x = 5.;
double* ptr = &x; double* ptr = &x;
H2(x); // call h2(double) H2(x); // call h2(double)
H2(ptr); // call h2(double*) H2(ptr); // call h2(double*)
H2(*ptr); // call h2(double) H2(*ptr); // call h2(double)
H2(&x); // call h2(double*) H2(&x); // call h2(double*)
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Best viable function ### Best viable function
In fact, overloading may work even if the match is not perfect: the **best viable function** is chosen if possible... and some ambiguity may appear if none matches! In fact, overloading may work even if the match is not perfect: the **best viable function** is chosen if possible... and some ambiguity may appear if none matches!
The complete rules are very extensive and may be found [here](https://en.cppreference.com/w/cpp/language/overload_resolution); as a rule of thumb you should really strive to write overloaded functions with no easy ambiguity... or not using it at all: sometimes naming the function differently avoids loads of issues! The complete rules are very extensive and may be found [here](https://en.cppreference.com/w/cpp/language/overload_resolution); as a rule of thumb you should really strive to write overloaded functions with no easy ambiguity... or not using it at all: sometimes naming the function differently avoids loads of issues!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
int Min(int a, int b) int Min(int a, int b)
{ {
std::cout << "int version called!" << std::endl; std::cout << "int version called!" << std::endl;
return a < b ? a : b; return a < b ? a : b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
double Min(double a, double b) double Min(double a, double b)
{ {
std::cout << "double version called!" << std::endl; std::cout << "double version called!" << std::endl;
return a < b ? a : b; return a < b ? a : b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int i1 { 5 }, i2 { -7 }; int i1 { 5 }, i2 { -7 };
double d1 { 3.14}, d2 { -1.e24}; double d1 { 3.14}, d2 { -1.e24};
float f1 { 3.14f }, f2 { -4.2f}; float f1 { 3.14f }, f2 { -4.2f};
short s1 { 5 }, s2 { 7 }; short s1 { 5 }, s2 { 7 };
Min(5, 7); // no ambiguity Min(5, 7); // no ambiguity
Min(i1, i2); // no ambiguity Min(i1, i2); // no ambiguity
Min(f1, f2); // conversion to closest one Min(f1, f2); // conversion to closest one
Min(f1, d2); // conversion to closest one Min(f1, d2); // conversion to closest one
Min(s1, s2); // conversion to closest one Min(s1, s2); // conversion to closest one
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, with some other types it doesn't work as well if implicit conversion is dangerous and may loose data: However, with some other types it doesn't work as well if implicit conversion is dangerous and may loose data:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
unsigned int i1 { 5 }, i2 { 7 }; unsigned int i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate! Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
long i1 { 5 }, i2 { 7 }; long i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate! Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Likewise, if best candidate is not the same for each argument: Likewise, if best candidate is not the same for each argument:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
float f1 { 5.f }; float f1 { 5.f };
int i1 { 5 }; int i1 { 5 };
Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate... Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Advice: use overload only when there is no ambiguity whatsoever ### Advice: use overload only when there is no ambiguity whatsoever
That is when: That is when:
- The number of arguments is different between overloads. - The number of arguments is different between overloads.
- Or their types do not convert implicitly from one to another. For instance the following overloads are completely safe to use and the interface remains obvious for the end-user: - Or their types do not convert implicitly from one to another. For instance the following overloads are completely safe to use and the interface remains obvious for the end-user:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <iostream> #include <iostream>
std::string GenerateString() std::string GenerateString()
{ {
std::cout << "No argument!"; std::cout << "No argument!";
return ""; return "";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string GenerateString(char one_character) std::string GenerateString(char one_character)
{ {
std::cout << "One character: "; std::cout << "One character: ";
return std::string(1, one_character); return std::string(1, one_character);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string GenerateString(char value1, char value2) std::string GenerateString(char value1, char value2)
{ {
std::cout << "Two characters: "; std::cout << "Two characters: ";
std::string ret(1, value1); std::string ret(1, value1);
ret += value2; ret += value2;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string GenerateString(const std::string& string) std::string GenerateString(const std::string& string)
{ {
std::cout << "Std::string: "; std::cout << "Std::string: ";
return string; return string;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string GenerateString(const char* string) std::string GenerateString(const char* string)
{ {
std::cout << "Char*: "; std::cout << "Char*: ";
return std::string(string); return std::string(string);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::cout << GenerateString() << std::endl; std::cout << GenerateString() << std::endl;
std::cout << GenerateString('a') << std::endl; std::cout << GenerateString('a') << std::endl;
std::cout << GenerateString('a', 'b') << std::endl; std::cout << GenerateString('a', 'b') << std::endl;
std::cout << GenerateString("Hello world!") << std::endl; std::cout << GenerateString("Hello world!") << std::endl;
std::string text("Hello!"); std::string text("Hello!");
std::cout << GenerateString(text) << std::endl; std::cout << GenerateString(text) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Optional parameters ## Optional parameters
It is possible to provide optional parameters in the **declaration** of a function: It is possible to provide optional parameters in the **declaration** of a function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
// Declaration. // Declaration.
void FunctionWithOptional(double x, double y = 0., double z = 0.); void FunctionWithOptional(double x, double y = 0., double z = 0.);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
// Definition // Definition
void FunctionWithOptional(double x, double y, double z) // notice the absence of default value! void FunctionWithOptional(double x, double y, double z) // notice the absence of default value!
{ {
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl; std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
// WARNING: Xeus-cling issue! // WARNING: Xeus-cling issue!
{ {
FunctionWithOptional(3., 5., 6.); // ok FunctionWithOptional(3., 5., 6.); // ok
FunctionWithOptional(3.); // should be ok, but Xeus-cling issue. FunctionWithOptional(3.); // should be ok, but Xeus-cling issue.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The reason not to repeat them is rather obvious: if both were accepted you may modify one of them and forget to modify the others, which would be a bad design... The reason not to repeat them is rather obvious: if both were accepted you may modify one of them and forget to modify the others, which would be a bad design...
There is a way to put it in the same place, that I do not recommend it (and your compiler should warn you most of the time): if you do not declare the function beforehand, default arguments may be specified at definition: There is a way to put it in the same place, that I do not recommend it (and your compiler should warn you most of the time): if you do not declare the function beforehand, default arguments may be specified at definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
// Definition which double acts as declaration // Definition which double acts as declaration
void FunctionWithOptional2(double x, double y = 0., double z = 0.) void FunctionWithOptional2(double x, double y = 0., double z = 0.)
{ {
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl; std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
FunctionWithOptional2(3., 5., 6.); // ok FunctionWithOptional2(3., 5., 6.); // ok
FunctionWithOptional2(3.); // ok FunctionWithOptional2(3.); // ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C and C++, arguments are only **positional**: you do not have a way to explicitly set an argument with a name for instance. In C and C++, arguments are only **positional**: you do not have a way to explicitly set an argument with a name for instance.
Therefore: Therefore:
* Optional arguments must be put together at the end of the function. * Optional arguments must be put together at the end of the function.
* You must think carefully if there are several of them and put the less likely to be set manually by the function user at then end. In our example above, if you want do call the function with a `x` and a `z` you must mandatorily also provide explicitly `y`. * You must think carefully if there are several of them and put the less likely to be set manually by the function user at then end. In our example above, if you want do call the function with a `x` and a `z` you must mandatorily also provide explicitly `y`.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Lambda functions ## Lambda functions
C++ 11 introduced a shorthand to define functions called __lambda functions__. C++ 11 introduced a shorthand to define functions called __lambda functions__.
An example is the best way to introduce them: An example is the best way to introduce them:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
// Locally defined function. // Locally defined function.
auto Square = [](double x) -> double auto Square = [](double x) -> double
{ {
return x * x; return x * x;
}; };
std::cout << Square(5.) << std::endl; std::cout << Square(5.) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Several notes: Several notes:
* Use `auto` as its return type; said type is not reproducible (see the _square_ and _cube_ example below). * Use `auto` as its return type; said type is not reproducible (see the _square_ and _cube_ example below).
* The symbol `->` that specifies the type of the returned value is optional. * The symbol `->` that specifies the type of the returned value is optional.
* Parameters come after the `[]` in parenthesis with the same syntax as ordinary functions. * Parameters come after the `[]` in parenthesis with the same syntax as ordinary functions.
* This is not the same as the [alternate syntax](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) explained earlier, even if they look similar: a lambda may be defined locally (here within a block) whereas a standard function (with usual or alternate syntax) can't. * This is not the same as the [alternate syntax](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) explained earlier, even if they look similar: a lambda may be defined locally (here within a block) whereas a standard function (with usual or alternate syntax) can't.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
// Locally defined function. // Locally defined function.
auto Square = [](double x) auto Square = [](double x)
{ {
return x * x; return x * x;
}; };
auto Cube = [](double x) auto Cube = [](double x)
{ {
return x * x * x; return x * x * x;
}; };
std::cout << "Are the lambda prototypes the same type? " std::cout << "Are the lambda prototypes the same type? "
<< (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl; << (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Inside the `[]` you might specify values that are transmitted to the body of the function; by default nothing is transmitted: Inside the `[]` you might specify values that are transmitted to the body of the function; by default nothing is transmitted:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto APlusB = [](int b) auto APlusB = [](int b)
{ {
return a + b; return a + b;
}; };
std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body. std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto APlusB = [a](int b) // Notice the `[a]` here! auto APlusB = [a](int b) // Notice the `[a]` here!
{ {
return a + b; return a + b;
}; };
std::cout << APlusB(3) << std::endl; std::cout << APlusB(3) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The values captured in the lambda might be transmitted by reference: The values captured in the lambda might be transmitted by reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int a = 5; int a = 5;
auto AddToA = [&a](int b) // Notice the `[&a]` here! auto AddToA = [&a](int b) // Notice the `[&a]` here!
{ {
a += b; a += b;
}; };
AddToA(3); AddToA(3);
std::cout << a << std::endl; std::cout << a << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is possible to capture everything (in the scope where the lambda is defined) by reference by using `[&]` but it is really ill-advised; don't do this! It is possible to capture everything (in the scope where the lambda is defined) by reference by using `[&]` but it is really ill-advised; don't do this!
Lambda functions really shines when you want to use them in a very special context; see below an example using the 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: 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)).
%% Cell type:code id: tags: 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:
``` 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <algorithm> // for sort #include <algorithm> // for sort
{ {
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 }; std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::cout << "Initial list = "; std::cout << "Initial list = ";
for (int value : list) for (int value : list)
std::cout << value << ' '; std::cout << value << ' ';
// My very specific sort operation: // My very specific sort operation:
// Returns true if lhs is odd and rhs isn't or if lhs < rhs. // Returns true if lhs is odd and rhs isn't or if lhs < rhs.
auto odd_first = [](auto lhs, auto rhs) auto odd_first = [](auto lhs, auto rhs)
{ {
const bool is_lhs_odd = !(lhs % 2 == 0); const bool is_lhs_odd = !(lhs % 2 == 0);
const bool is_rhs_odd = !(rhs % 2 == 0); const bool is_rhs_odd = !(rhs % 2 == 0);
if (is_lhs_odd != is_rhs_odd) if (is_lhs_odd != is_rhs_odd)
return is_lhs_odd; return is_lhs_odd;
return lhs < rhs; return lhs < rhs;
}; };
std::sort(list.begin(), list.end(), odd_first); std::sort(list.begin(), list.end(), odd_first);
std::cout << std::endl << "Sorted list = "; std::cout << std::endl << "Sorted list = ";
for (int value : list) for (int value : list)
std::cout << value << ' '; std::cout << value << ' ';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the use of an intermediate local variable for the lambda is not mandatory; the lambda may be provided on the fly: Please notice the use of an intermediate local variable for the lambda is not mandatory; the lambda may be provided on the fly:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
{ {
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 }; std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::vector<int> even_only; std::vector<int> even_only;
// Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later! // Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later!
std::copy_if(list.cbegin(), std::copy_if(list.cbegin(),
list.cend(), list.cend(),
std::back_inserter(even_only), std::back_inserter(even_only),
[](int value) [](int value)
{ {
return value % 2 == 0; return value % 2 == 0;
}); });
for (int value : even_only) for (int value : even_only)
std::cout << value << ' '; std::cout << value << ' ';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Passing a function as a an argument ## Passing a function as a an argument
In some cases, you might want to pass a function as an argument (and honestly most of the time you should refrain to do so: it may underline your design is not top notch). In some cases, you might want to pass a function as an argument (and honestly most of the time you should refrain to do so: it may underline your design is not top notch).
The syntax to do so is a bit ugly and stems directly from C; it relies upon using a pointer to a function. The syntax to do so is a bit ugly and stems directly from C; it relies upon using a pointer to a function.
The syntax looks like: The syntax looks like:
`unsigned int (*f) (int, double)` `unsigned int (*f) (int, double)`
where: where:
* `unsigned int` is the return type. * `unsigned int` is the return type.
* `int, double` are the type of the parameters of the function given as argument. * `int, double` are the type of the parameters of the function given as argument.
* `f` is the name of the argument. * `f` is the name of the argument.
It will be clearer in an example: It will be clearer in an example:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void PrintFunctionCall(int (*f) (int, int), int m, int n) void PrintFunctionCall(int (*f) (int, int), int m, int n)
{ {
std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl; std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Multiply(int a, int b) int Multiply(int a, int b)
{ {
return a * b; return a * b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Add(int a, int b) int Add(int a, int b)
{ {
return a + b; return a + b;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
PrintFunctionCall(Multiply, 5, 6); PrintFunctionCall(Multiply, 5, 6);
PrintFunctionCall(Add, 5, 6); PrintFunctionCall(Add, 5, 6);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are other ways to do this task: There are other ways to do this task:
* Using a template parameter. Templates will be reached [later in this tutorial](../4-Templates/0-main.ipynb), but for me it's usually the way to go. * Using a template parameter. Templates will be reached [later in this tutorial](../4-Templates/0-main.ipynb), but for me it's usually the way to go.
* Using [functors](../3-Operators/5-Functors.ipynb) * Using [functors](../3-Operators/5-Functors.ipynb)
* Using `std::function`, introduced in C++ 11. However <a href="https://vittorioromeo.info/index/blog/passing_functions_to_functions.html">this blog</a> explains why it's not a good idea; on top of the arguments given there it doesn't seem to respect the prototype closely (a function with double instead of int is for instance accepted). * Using `std::function`, introduced in C++ 11. However <a href="https://vittorioromeo.info/index/blog/passing_functions_to_functions.html">this blog</a> explains why it's not a good idea; on top of the arguments given there it doesn't seem to respect the prototype closely (a function with double instead of int is for instance accepted).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## A very special function: __main__ ## A very special function: __main__
Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where: Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where:
* __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2. * __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2.
* __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given. * __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given.
Please notice the internal mechanics of C/C++ compiler returns these values; if a user type `isPrime qwerty 20`, the main functions will return argc = 3. It is up to the writer of the main to ensure the arguments are correct. Please notice the internal mechanics of C/C++ compiler returns these values; if a user type `isPrime qwerty 20`, the main functions will return argc = 3. It is up to the writer of the main to ensure the arguments are correct.
If some of these values should be interpreted as numbers, it is also up to the developer to foresee the conversion from the C-string to a numerical value. If some of these values should be interpreted as numbers, it is also up to the developer to foresee the conversion from the C-string to a numerical value.
In the very specific of our Jupyter notebook, a unique main might be defined or not in the file: _cling_ performs some magic to generate one under the hood. In the very specific of our Jupyter notebook, a unique main might be defined or not in the file: _cling_ performs some magic to generate one under the hood.
The __main__ function may also be defined as __int main()__ without arguments if the program doesn't actually need any. The __main__ function may also be defined as __int main()__ without arguments if the program doesn't actually need any.
Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers. Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers.
The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes. The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes.
We will deal with main functions later when we will work in a true C++ environment. We will deal with main functions later when we will work in a true C++ environment.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `inline` functions ## `inline` functions
You may also in a function declaration and definition function prepend the prototype by an `inline`. This indicates the compiler this function might be **inlined**: this means the content of the function may be copied directly, thus avoiding a function call and potentially making your code a tiny bit faster. So for instance if you have a function: You may also in a function declaration and definition function prepend the prototype by an `inline`. This indicates the compiler this function might be **inlined**: this means the content of the function may be copied directly, thus avoiding a function call and potentially making your code a tiny bit faster. So for instance if you have a function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
inline double Square(double x) inline double Square(double x)
{ {
return x * x; return x * x;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
when this function is called somewhere, the compiler may replace directly the function by the code inside the definition: when this function is called somewhere, the compiler may replace directly the function by the code inside the definition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Square(5.); // The compiler might substitute 5. * 5. to the actual function call here Square(5.); // The compiler might substitute 5. * 5. to the actual function call here
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This behaviour is pretty similar to the often frowned-upon **macros** from C, but the use of `inline` is absolutely not considered a bad practice... provided you have in mind the way it works: This behaviour is pretty similar to the often frowned-upon **macros** from C, but the use of `inline` is absolutely not considered a bad practice... provided you have in mind the way it works:
* You have probably notice the conditional in my statements regarding `inline`: the keyword is an _hint_ given to the compiler... that might be followed or not. * You have probably notice the conditional in my statements regarding `inline`: the keyword is an _hint_ given to the compiler... that might be followed or not.
* On the syntactic side, `inline` must be provided both in the declaration `and` the definition. * On the syntactic side, `inline` must be provided both in the declaration `and` the definition.
* `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation). * `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation).
* Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead. * Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead.
The `Square` example was sound: this is typically the kind of functions that might be inlined. The `Square` example was sound: this is typically the kind of functions that might be inlined.
Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled: Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#define SQUARE(x) ((x) * (x)) // macro #define SQUARE(x) ((x) * (x)) // macro
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
double x = 5.; double x = 5.;
std::cout << Square(++x) << std::endl; std::cout << Square(++x) << std::endl;
} }
{ {
double x = 5.; double x = 5.;
std::cout << SQUARE(++x) << std::endl; std::cout << SQUARE(++x) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_ © _CNRS 2016_ - _Inria 2018-2022_
_This notebook is an adaptation of a lecture prepared 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/)_ _This notebook is an adaptation of a lecture prepared 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 written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment