Skip to content
Snippets Groups Projects
README.md 11.1 KiB
Newer Older
CHAMONT David's avatar
CHAMONT David committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
# [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 inutile
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
la lourdeur d'un foncteur (objet appelable à travers l'opérateur ()). Dans les cas simples,
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.

---
## 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
pénible et "encombrant" de devoir la déclarer comme n'importe quelle autre.

```c++
#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) ;
  std::list<int> l {1,2,3,4,5} ;
  applique(l,affiche) ;
 }
```

C++11 offre 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 et
de le rendre plus expressif, lorsque cette définition est brève.
Ces fonctions sans nom sont appelées **fonctions lambdas**.

```c++
#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 ; }) ;
 }
```

---
## Retourner un résultat

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

```c++
std::condition_variable cond;
bool data_ready;
std::mutex m;

void wait_for_data()
{
  std::unique_lock<std::mutex> lk(m);
  cond.wait(lk,[]{return data_ready;}); // (1)
}
```

Ci-dessus, le type de retour de la lambda est celui de la variable
data_ready, donc bool. Si jamais le corps de la lambda est plus complexe
qu'une instruction return, il faut préciser explicitement ce 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
l'exemple ci-dessus :

```c++
cond.wait(lk,[]()->bool{return data_ready;}) ;
```

Et puisque le type de retour est maintenant explicite,
on peut complexifier à loisir le corps de la fonction :

```c++
cond.wait(lk,[]()->bool
{
  if(data_ready)
  {
    std::cout<<Data ready<<std::endl;
    return true;
  }
  else
  {
    std::cout<<Data not ready, resuming wait<<std::endl;
    return false;
  }
});
```

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 consister à
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++
#include <iostream>
#include <vector>
#include <functional>

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

class Mult
 {
  public :
    Mult( int m ) : m_{m} {}
    void operator() ( int i ) { std::cout<<" "<<m_*i ; }
  private :
    int m_ ;
 } ;

template< typename Collection >
int f( Collection & col, int m )
 {
  std::cout<<"*"<<m<<" :" ;
  applique(col,Mult(m)) ;
  std::cout<<std::endl ;
 }

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

Une fonction lambda introduite par "[]" 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 ainsi capturer `m`
et se passer facilement du foncteur :

```c++
...
template< typename Collection >
int f( Collection & col, int m )
 {
  std::cout<<"*"<<m<<" :" ;
  applique(col, [m](int i){ std::cout<<" "<<m*i ; } ) ;
  std::cout<<std::endl ;
 }
...
```

D'un point de vue interne, vous pouvez imaginez qu'une classe équivalente
à notre ancienne `Mult` a été créée au vol. 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++
#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() ;
 }
```

---
## 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++
#include <iostream>
#include <functional>

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

---
## 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++
#include <iostream>
#include <functional>

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

Si on raisonne en terme d'objets-fonctions, comme nous le faisions plus tôt
avec notre classe `Mult`, on peut imaginer une classe qui capture i, j
et k, ce qu'en terme savant on appelera une fermeture ou une clôture
(closure) :

```c++
#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} ;
  auto f = [i,&j,k](){ std::cout<<(i+j+k)<<std::endl ; } ;
  f() ;
  i = 200, j = 20, k = 2 ;
  f() ;
  i = 300, j = 30, k = 2 ;
  f() ;
 }
```

Vous pouvez également décider de capturer tout ce qui peut l'être
sans discernement, à l'aide de la notation "[=]" pour le faire par
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++
#include <functional>

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

---
## 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++
#include <iostream>
#include <functional>
#include <vector>

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

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

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

En programmation concurrente, les lambdas sont particulièrement utiles pour
servir de prédicats dans `condition_variable::wait()`, avec `std::packaged_task<>`
ou pour regrouper des petites tâches dans un "thread pool". Elles peuvent
aussi être confiées à un constructeur de `std::thread` ou à un algorithme
parallèle tel que `parallel_for_each()`.

---
## Lambdas polymorphes

Pour en terminer avec les lambdas, revenons à notre exemple initial, et imaginons qu'on
cherche à traiter des collections de nature différente :

```c++
#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> 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++
...
int main()
 {
  auto f = [](auto i)
   { std::cout<<i<<std::endl ; } ;
 
  std::vector<int> vi{1,2,3,4,5} ;
  applique(vi,f) ;

  std::vector<double> vd{1.1,2.2,3.3,4.4} ;
  applique(vd,f) ;

  ...
 }
```

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

```c++
class Cloture
 {
  public :
    template<typename Value>
    void operator() (Value v)
     { std::cout<<v<<std::endl ; }
 } ;
```

---
## Exercice

Toujours dans le répertoire `Exercices`, étudiez et faites tourner
le code `lbd_complexe.cpp` à l'aide du script `lbd_complex.sh` :

```
./lbd_complex.sh 2 3 1
./lbd_complex.sh 1024 1000 0
```

Modifiez `lbd_complexe.cpp` pour utiliser un maximum de lambdas.

---
© *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/)*