Skip to content
Snippets Groups Projects
README.md 19.1 KiB
Newer Older
CHAMONT David's avatar
CHAMONT David committed
# [Fonctions lambdas](https://bitbucket.org/hyhg/revisions/wiki/Home)

Les fonctions lambda (ou anonymes), introduites en C++11, peuvent être définies au vol
n'importe où dans le code, là ou l'on en a besoin. Cela permet de se passer d'une
CHAMONT David's avatar
CHAMONT David committed
déclaration préalable de la fonction, relativement inutile quand la fonction ne doit servir
qu'une fois. Par ailleurs, la mécanique de capture des variables locales permet d'éviter
l'écriture fastidieuse d'un foncteur (objet appelable à travers l'opérateur ()). Dans les cas simples,
CHAMONT David's avatar
CHAMONT David committed
le code produit est plus concis et plus clair. Si on pousse le mécanisme dans ses
retranchements avec des fonctions lambda très compliquées, c'est moins évident... à vous
de juger.

* prérequis : connaissance du C++ historique, et si possible des objets-fonctions.
* préinstallation : Docker.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CppLambda

CHAMONT David's avatar
CHAMONT David committed
---
## Motivation : la définition au vol de fonctions simples

Le C++ permet d'utiliser une fonction comme argument d'une autre fonction.
Parfois, cette fonction est triviale, ne va servir qu'une fois, et il est
fastidieux et "encombrant" de devoir la déclarer.

Ci-dessous, l'implémentation classique d'une fonction `applique` qui applique
une fonction `affiche*` à toutes les valeurs d'une collection.
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_motivation_1.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <vector>
#include <list>

template< typename Collection >
void applique( Collection & col, void (*f)( typename Collection::value_type ) )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { f(*itr) ; ++itr ; }
 }

void affiche( int i )
 {
  std::cout<<i<<std::endl ;
 }

int main()
 {
  std::vector<int> v {1,2,3,4,5} ;
  applique(v,affiche) ;
 }
```

Essayez d'exécuter ce code au sein de la machine docker `gcc 6.1` :
```
> docker pull gcc 6.1
> docker run -it -v `pwd`:/home/<USER>/Piscine gcc:6.1 /bin/bash
> cd /home/<USER>/Piscine
> g++ -std=c++11 lbd_motivation_1.cpp -o lbd_motivation_1
> ./lbd_motivation_1
```

Avec C++11 apparait la possibilité d'écrire directement la fonction là ou elle est
appelée, en remplacant son nom par "[]", ce qui permet d'alléger le code.
Ces fonctions sans nom sont appelées **fonctions anonymes** ou **fonctions lambdas**.
Ci-dessous, nous réécrivons notre exemple en utilisant cette nouvelle syntaxe :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_motivation_2.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <vector>

template< typename Collection >
void applique( Collection & col, void (*f)( typename Collection::value_type ) )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { f(*itr) ; ++itr ; }
 }

int main()
 {
  std::vector<int> v {1,2,3,4,5} ;
  applique(v, [] (int i) { std::cout<<i<<std::endl ; } ) ;
CHAMONT David's avatar
CHAMONT David committed
 }
```

Exercice 1 : modifiez le code de la fonction anonyme pour qu'elle
affiche le carré de chaque élement de la collection.

CHAMONT David's avatar
CHAMONT David committed
---
## Retourner un résultat

Si votre fonction lambda doit retourner un résultat, c'est à peu
prêt aussi facile. En C++11, si le corps de la fonction est fait d'une
seule instruction return qui renvoit une expression, le type de retour de
la fonction est déduit de celui de l'expression retournée.

Modifions l'exemple précédent. La fonction à appliquer est
maintenant une tranformation, et la fonction `applique()` se charge
d'afficher le résultat :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_resultat_1.cpp

#include <iostream>
#include <vector>

template< typename Collection >
void applique( Collection & col, typename Collection::value_type (*f)( typename Collection::value_type ) )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<f(*itr)<<std::endl ; ++itr ; }
 }

int main()
 {
  std::vector<int> v {1,2,3,4,5} ;
  applique(v, [] (int i) { return i*i ; } ) ;
 }
CHAMONT David's avatar
CHAMONT David committed
```

Ci-dessus, le type de retour de la lambda est celui de l'expression
`i*i`, donc `int`. Si jamais le corps de la lambda est plus complexe
et que votre compilateur est pointilleux, il peut devenir néessaire de
préciser explicitement le type de retour, à l'aide d'une flèche (->) suivie du type,
placée entre la spécification des arguments et le corps de fonction. En reprenant
le même exemple :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_resultat_2.cpp

#include <iostream>
#include <vector>

template< typename Collection >
void applique( Collection & col, typename Collection::value_type (*f)( typename Collection::value_type ) )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<f(*itr)<<std::endl ; ++itr ; }
 }

int main()
 {
  std::vector<int> v {1,2,3,4,5} ;
  applique(v, [] (int i) -> int { return i*i ; } ) ;
 }
CHAMONT David's avatar
CHAMONT David committed
```

En réalité, le compilateur se permet souvent de déuire le type au-delà de la spécification
stricte de C++11, et C++14 relâche certaines contraintes, en autorisant notamment les instructions
multiples, du moment que toutes les instructions `return` retournent des expressions du
même type. Il est donc peu probable que vous soyez obligés de préciser le type de retour,
si ce n'est pour rendre votre code un peu plus lisible quand la fonction commence à
se complexifier :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_resultat_3.cpp

#include <iostream>
#include <vector>

template< typename Collection >
void applique( Collection & col, typename Collection::value_type (*f)( typename Collection::value_type ) )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<f(*itr)<<std::endl ; ++itr ; }
 }

int main()
 {
  std::vector<int> v {-2,-1,0,1,2} ;
  applique(v,[] (int i) -> int
   {
    if (i>=0) return i*i ;
    else return -i*i ;
   }) ;
 }
CHAMONT David's avatar
CHAMONT David committed
```

Exercice 2 : dans le code ci-dessus, transformez la collection en vecteur de valeurs réelles,
et la fonction anonyme pour qu'elle calcule la racine carrée (fonction `std::sqrt` définie
dans le fichier d'entête `cmath`). Pour les nombres négatifs, on prendra la racine carrée
de l'opposé.

CHAMONT David's avatar
CHAMONT David committed
Ces premiers exemples montrent déjà des utilisations pratiques des
lambdas. Mais là où elles deviennent réellement redoutables, c'est dans
leur capacité à capturer des variables locales.

---
## Capturer une variable par valeur

On veut parfois doter une fonction de certains paramètres qui sont
fixés lors de sa création, puis réutilisés, sans avoir à les redonner
en argument à chaque appel. La façon "orientée objet" de le faire consiste à
CHAMONT David's avatar
CHAMONT David committed
créer un objet-fonction, ou "foncteur", instance d'une classe dotée
d'un opérator (), telle que la classe Mult ci-dessous.

```c++
// Fichier lbd_capture_1.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <vector>
#include <functional>

template< typename Collection >
void applique( Collection & col, std::function<typename Collection::value_type(typename Collection::value_type)> f )
CHAMONT David's avatar
CHAMONT David committed
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<" "<<f(*itr) ; ++itr ; }
class MultFoncteur
CHAMONT David's avatar
CHAMONT David committed
 {
  public :
    MultFoncteur( int m ) : m_{m} {}
    int operator() ( int i ) { return m_*i ; }
CHAMONT David's avatar
CHAMONT David committed
  private :
    int m_ ;
 } ;

template< typename Collection >
void mult( Collection & col, int m )
CHAMONT David's avatar
CHAMONT David committed
 {
  std::cout<<"*"<<m<<" :" ;
  applique(col,MultFoncteur(m)) ;
CHAMONT David's avatar
CHAMONT David committed
  std::cout<<std::endl ;
 }

int main()
 {
  std::vector<int> v{1,2,3,4,5} ;
  mult(v,1) ;
  mult(v,2) ;
  mult(v,3) ;
Au passage, on remarquera ci-dessus l'utilisation du type `std::function<...>` à la place du
pointeur ordinaire. Ceci permet à l'argument `f` de recevoir indifféremment un pointeur
de fonction ou un foncteur, du moment que ce dernier dispose d'un opérateur "()" doté des
types appropriés.

Revenons aux captures. Une fonction lambda, lorsqu'elle est introduite par des crochets vides "[]",
n'a accès qu'à ses propres arguments, où aux variables globales, comme n'importe quelle fonction.
Mais ces crochets permettent aussi de "capturer" la valeur d'une variable disponible là ou notre
lambda est créée. Dans notre exemple précédent, on peut capturer `m` et se passer facilement du
foncteur :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_capture_2.cpp

#include <iostream>
#include <vector>
#include <functional>

CHAMONT David's avatar
CHAMONT David committed
template< typename Collection >
void applique( Collection & col, std::function<typename Collection::value_type(typename Collection::value_type)> f )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<" "<<f(*itr) ; ++itr ; }
 }

template< typename Collection >
void mult( Collection & col, int m )
CHAMONT David's avatar
CHAMONT David committed
 {
  std::cout<<"*"<<m<<" :" ;
  applique(col, [m] (int i) -> int { return m*i ; } ) ;
CHAMONT David's avatar
CHAMONT David committed
  std::cout<<std::endl ;
 }

int main()
 {
  std::vector<int> v{1,2,3,4,5} ;
  mult(v,1) ;
  mult(v,2) ;
  mult(v,3) ;
 }
CHAMONT David's avatar
CHAMONT David committed
```

D'un point de vue interne, vous pouvez imaginez qu'une classe équivalente
à notre ancienne `MultFoncteur` a été créée au vol.

L'écriture des types bâtis avec `std::function` pouvant rapidement s'avérer fastidieux,
on l'évitera quand c'est possible. Par exemple, ici, à l'aide d'un deuxième paramètre 
dans le patron :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_capture_3.cpp

#include <iostream>
#include <vector>

template< typename Collection, typename Fonction >
void applique( Collection & col, Fonction f )
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<" "<<f(*itr) ; ++itr ; }
 }

template< typename Collection >
void mult( Collection & col, int m )
 {
  std::cout<<"*"<<m<<" :" ;
  applique(col, [m] (int i) -> int { return m*i ; } ) ;
  std::cout<<std::endl ;
 }

int main()
 {
  std::vector<int> v{1,2,3,4,5} ;
  mult(v,1) ;
  mult(v,2) ;
  mult(v,3) ;
 }
```

Ci-dessous, pour démontrer que la variable capturée est *copiée*, on stocke la lambda
dans une variable (de type `std::function<>`), afin de pouvoir la réappeler après avoir
modifier la variable originale, et on constatera que notre lambda renvoit toujours le
même résultat, indépendamment de la valeur courante de m.

```c++
// Fichier lbd_capture_4.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <functional>

int main()
 {
  int i = 1 ;
  std::function<void(void)> f = [i] () { std::cout<<i<<std::endl ; } ;
  f() ;
  i = 2 ;
  f() ;
  i = 3 ;
  f() ;
 }
```

Cette fois, pour faire l'économie de la déclaration explicite de `std::function<>`,
redondante,  on peut s'appuyer sur le mécanisme de déduction de type disponible dans
C++11, via le mot-clef `auto` :

```c++
// Fichier lbd_capture_5.cpp

#include <iostream>
#include <functional>

int main()
 {
  int i = 1 ;
  auto f = [i] () { std::cout<<i<<std::endl ; } ;
CHAMONT David's avatar
CHAMONT David committed
  f() ;
  i = 2 ;
  f() ;
  i = 3 ;
  f() ;
 }
```

---
## Capture de variable par référence

La capture par valeur est la plus sûre, parce que les variables
sont copiées, mais on peut également capturer par référence,
en ajoutant un "&". Dans ce cas, comme pour n'importe quelle
référence, le comportement est indéfini si jamais la variable originale
vient à disparaitre avant la fonction lambda. Dans notre exemple
précédent, une capture par référence permet d'obtenir une lambda
qui prend en compte la valeur courante de m :

```c++
// Fichier lbd_capture_6.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>

int main()
 {
  int i = 1 ;
  auto f = [&i] () { std::cout<<i<<std::endl ; } ;
CHAMONT David's avatar
CHAMONT David committed
  f() ;
  i = 2 ;
  f() ;
  i = 3 ;
  f() ;
 }
```

Exercice 3 : au lieu de la fonction lambda ci-dessus, écrivez le foncteur équivalent.

CHAMONT David's avatar
CHAMONT David committed
---
## Captures multiples

Vous n'êtes pas limité à capturer une seule variable. Les crochets
peuvent inclure autant de variables que vous le désirez, séparées par
des virgules, en choississant individuellement pour chaque variable
si elle doit être capturée par valeur ou par référence.

```c++
// Fichier lbd_capture_7.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>

int main()
 {
  int i{100}, j{10}, k{1} ;
  auto f = [i,&j,k] () { std::cout<<(i+j+k)<<std::endl ; } ;
CHAMONT David's avatar
CHAMONT David committed
  f() ;
  i = 200, j = 20, k = 2 ;
  f() ;
  i = 300, j = 30, k = 2 ;
  f() ;
 }
```

Si on raisonne en terme d'objets-fonctions, on peut imaginer une classe qui
capture i, j et k, ce qu'en termes plus savants on appelera une fermeture
ou une clôture (closure) :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_capture_8.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>

class Cloture
 {
  public :
    Cloture( int i, int & j, int k ) : i_{i}, j_{j}, k_{k} {}
    void operator() () { std::cout<<(i_+j_+k_)<<std::endl ; }
  private :
    int i_, & j_, k_ ;
 } ;

int main()
 {
  int i{100}, j{10}, k{1} ;
  Cloture f(i,j,k) ;
CHAMONT David's avatar
CHAMONT David committed
  f() ;
  i = 200, j = 20, k = 2 ;
  f() ;
  i = 300, j = 30, k = 2 ;
  f() ;
 }
```

Retournons aux lamndas : vous pouvez également décider de capturer tout ce
qui peut l'être sans discernement, à l'aide de la notation "[=]" pour le faire par
CHAMONT David's avatar
CHAMONT David committed
valeur, ou "[&]" pour le faire par référence.

Enfin, vous pouvez utilisez cette dernière notation comme capture
par défaut, et spécifier explicitement le mode de capture de certaines
variables échappant au mode par défaut. 

```c++
// Fichier lbd_capture_9.cpp

#include <iostream>
CHAMONT David's avatar
CHAMONT David committed

int main()
 {
  int i{100}, j{10}, k{1} ;
  auto f = [=,&j] () { std::cout<<(i+j+k)<<std::endl ; } ;
CHAMONT David's avatar
CHAMONT David committed
  f() ;
  i = 200, j = 20, k = 2 ;
  f() ;
  i = 300, j = 30, k = 2 ;
  f() ;
 }
```

Pour finir, une remise en garde : chaque capture par reference porte un danger,
celui que la variable originale cesse d'exister avant la fonction lambda, auquel
cas la capture devient invalide.

CHAMONT David's avatar
CHAMONT David committed
---
## Au sein d'une fonction membre

Au sein d'une fonction membre, pour accéder aux variables membres de l'objet courant,
il faut capturer this.

```c++
// Fichier lbd_this_1.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <vector>

template< typename Collection, typename Fonction >
void applique( Collection & col, Fonction f )
CHAMONT David's avatar
CHAMONT David committed
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { std::cout<<" "<<f(*itr) ; ++itr ; }
CHAMONT David's avatar
CHAMONT David committed
 }

class Mult
 {
  public :
    Mult( int m ) : m_{m} {}
    template< typename Collection >
    void parcourt( Collection & col )
     {
      std::cout<<"*"<<m_<<" :" ;
      applique(col, [this] (int i) { return m_*i ; } ) ;
CHAMONT David's avatar
CHAMONT David committed
      std::cout<<std::endl ;
     }
  private :
    int m_ ;
 } ;

int main()
 {
  std::vector<int> v{1,2,3,4,5} ;
  Mult m(3) ;
  m.parcourt(v) ;
CHAMONT David's avatar
CHAMONT David committed
 }
```

---
## Lambdas polymorphes

Pour en terminer avec la théorie, revenons à notre exemple initial, et imaginons
qu'on cherche à traiter des collections de nature différente :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_poly_1.cpp

CHAMONT David's avatar
CHAMONT David committed
#include <iostream>
#include <vector>

template< typename Collection, typename Fonction >
void applique( Collection & col, Fonction f )
CHAMONT David's avatar
CHAMONT David committed
 {
  auto itr = col.begin() ;
  while (itr!=col.end())
   { f(*itr) ; ++itr ; }
 }

int main()
 {
  std::vector<int> vi{1,2,3,4,5} ;
  applique(vi,[](int i){ std::cout<<i<<std::endl ; }) ;

  std::vector<double> vd{1.1,2.2,3.3,4.4} ;
  applique(vd,[](double i){ std::cout<<i<<std::endl ; }) ;
 }
```

On aurait naturellement envie de créer un patron de lambda, ou quelque
chose qui y ressemble et qui apporte une forme de polymorphisme, plutot
que de redéclarer notre lambda à chaque fois.

C++14 répond à cette envie, en nous permettant de remplacer les
types de notre choix par `auto` :

```c++
// Fichier lbd_poly_2.cpp
CHAMONT David's avatar
CHAMONT David committed
...
int main()
 {
  auto affiche = [] (auto i)
CHAMONT David's avatar
CHAMONT David committed
   { std::cout<<i<<std::endl ; } ;
 
  std::vector<int> vi{1,2,3,4,5} ;
  applique(vi,affiche) ;
CHAMONT David's avatar
CHAMONT David committed

  std::vector<double> vd{1.1,2.2,3.3,4.4} ;
  applique(vd,affiche) ;
Pour pouvoir compiler un tel code, passez au compilateur l'option
`-std=c++14`.

CHAMONT David's avatar
CHAMONT David committed
Si l'on recherche la clôture équivalente, cela revient
à transformer en patron l'opérateur (), et notre objet
`affiche` serait une instance de :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_poly_3.cpp
...
CHAMONT David's avatar
CHAMONT David committed
class Cloture
 {
  public :
    template<typename Value>
    void operator() (Value v)
     { std::cout<<v<<std::endl ; }
 } ;
CHAMONT David's avatar
CHAMONT David committed
```

Notez que c'est l'opérateur d'éxecution (`operator ()`) qui est
paramétré, et non la classe elle-même.

CHAMONT David's avatar
CHAMONT David committed
---
## Exercice final
CHAMONT David's avatar
CHAMONT David committed

Pour notre exercice principal, nous allons utiliser un programme un peu plus riche,
qui génère aléatoirement 5 nombres complexes, les élève à la puissance 3, et affiche
ces complexes et leur produit :
CHAMONT David's avatar
CHAMONT David committed

```c++
// Fichier lbd_exercice_4.cpp

#include <complex>
#include <vector>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cmath>

// alias de types
typedef double Real ;
typedef std::complex<Real> Complex  ;
typedef std::vector<Complex> Complexes ;

// affichage d'un complexe
std::ostream & operator<<( std::ostream & os, const Complex & c )
 {
  return os
    <<std::scientific<<std::setprecision(4)
    <<"( "<<std::setw(11)<<c.real()<<" , "<<std::setw(11)<<c.imag()<<" )" ;
 }
 
// generation aleatoire d'un complexe sur le cercle unitaire 
void random_complex( Complex & c )
 { 
  int r = rand() ;
  Real d = ((Real) r)/RAND_MAX ;
  Real e = 2*M_PI*d ;
  c = Complex(cos(e),sin(e)) ;
 }

// affichage d'un tableau de complexe
void print_complexes( const Complexes & cs )
 {
  int i = 0 ;
  Complexes::const_iterator itr ;
  for ( itr = cs.begin() ; itr != cs.end() ; ++itr )
   { std::cout<<"x["<<i++<<"] = " <<(*itr)<<"\n" ; }
 }

// calcul et affichage du produit des complexes d'un tableau
void print_product( const Complexes & cs )
 {
  Complex res(1.,0.) ;
  Complexes::const_iterator itr ;
  for ( itr = cs.begin() ; itr != cs.end() ; ++itr )
   { res *= (*itr) ; }
  std::cout<<"*... = "<<res<<"\n" ;
 }

// foncteur qui élève tout complexe à une puissance
// donnée à la construction
class ComplexPow
 {
  public :
    ComplexPow( int p ) : p_(p) {}
    Complex operator()( const Complex & c ) const
     {
      int ip ;
      Complex res(1.,0.) ;
      for ( ip=0 ; ip<p_ ; ++ip )
       { res *= c ; }
      return res ;
     }
  private :
    int p_ ;
 } ;

// programme principal
int main ()
 {
  int d = 5 ;
  int p = 3 ;

  Complexes input(d) ;
  Complexes output(d) ;

  // generation aleatoire des complexes d'entree
  srand(20151214) ;
  std::for_each(input.begin(),input.end(),random_complex) ;
   
  // elevation à la puissance p
  std::transform(input.begin(),input.end(),output.begin(),ComplexPow(p)) ;
  
  // affichage des résultats
  print_complexes(output) ;
  print_product(output) ;

  return 0 ;
 }
CHAMONT David's avatar
CHAMONT David committed
```

Dans le programme principal, on notera l'utilisation de la fonction `std::for_each()`,
qui ressemble à notre fonction `applique()`, à ceci près qu'elle prend en arguments
un itérateur de début et de fin, plutôt que la collection elle-même (ce qui permet de
traiter une sous-partie de la collection si on le souhaite).

Question 1 : remplacez la fonction `random_complex()` par une fonction anonyme donnée
directement dans le programme principal.

On notera également l'utilisation de la fonction `std::transform()`, qui effectue
un calcul sur chaque élément d'une collection, et stocke le résultat dans une autre
collection. Ici on utilise un foncteur, car on veut y stocker la puissance à laquelle
on veut élever les éléments d'entrée.

Question 2 : remplacez le foncteur `ComplexPow` par une fonction anonyme qui capture
la valeur de `p`.

La bonne façon de simplifier l'écriture des fonctions `print_complexes()` et
`print_product()`, en C++ moderne, serait sans doute d'utiliser un "for
généralisé". Mais pour finir en beauté ce plongeon, essayons d'y glisser des fonctions
lambdas.

Question 3 : réécrivez les boucles de `print_complexes()` et `print_product()`
avec un appel à `std::for_each()` combiné à une fonction lambda. Une capture
par référence s'impose.
CHAMONT David's avatar
CHAMONT David committed

---
© *CNRS 2016*  
*Assemblé et rédigé par David Chamont, cette œuvre est mise à disposition selon les termes de la*  
*[Licence Creative Commons - Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/)*