Qu'est-ce qu'une expression lambda en C ++ 11?

1488

Qu'est-ce qu'une expression lambda en C ++ 11? Quand devrais-je en utiliser un? Quelle classe de problèmes résolvent-ils qui n'était pas possible avant leur introduction?

Quelques exemples et cas d'utilisation seraient utiles.

Nawaz
la source
14
J'ai vu un cas où le lambda était très utile: un de mes collègues faisait du code qui a des millions d'itérations pour résoudre un problème d'optimisation de l'espace. L'algorithme était beaucoup plus rapide lors de l'utilisation d'un lambda qu'une fonction appropriée! Le compilateur est Visual C ++ 2013.
sergiol

Réponses:

1491

Le problème

C ++ inclut des fonctions génériques utiles comme std::for_eachet std::transform, qui peuvent être très utiles. Malheureusement, ils peuvent également être assez lourds à utiliser, en particulier si le foncteur que vous souhaitez appliquer est unique pour la fonction particulière.

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

Si vous ne l'utilisez fqu'une seule fois et à cet endroit spécifique, il semble exagéré d'écrire une classe entière juste pour faire quelque chose de trivial et unique.

En C ++ 03, vous pourriez être tenté d'écrire quelque chose comme ceci, pour garder le foncteur local:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

cependant, cela n'est pas autorisé et fne peut pas être transmis à une fonction de modèle en C ++ 03.

La nouvelle solution

C ++ 11 introduit lambdas vous permet d'écrire un foncteur anonyme en ligne pour remplacer le struct f. Pour les petits exemples simples, cela peut être plus propre à lire (il garde tout au même endroit) et potentiellement plus simple à maintenir, par exemple sous la forme la plus simple:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Les fonctions lambda ne sont que du sucre syntaxique pour les foncteurs anonymes.

Types de retour

Dans les cas simples, le type de retour du lambda est déduit pour vous, par exemple:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

cependant, lorsque vous commencez à écrire des lambdas plus complexes, vous rencontrerez rapidement des cas où le type de retour ne peut pas être déduit par le compilateur, par exemple:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Pour résoudre ce problème, vous êtes autorisé à spécifier explicitement un type de retour pour une fonction lambda, en utilisant -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

"Capture" des variables

Jusqu'à présent, nous n'avons utilisé rien d'autre que ce qui a été transmis au lambda en son sein, mais nous pouvons également utiliser d'autres variables, au sein du lambda. Si vous souhaitez accéder à d'autres variables, vous pouvez utiliser la clause de capture (celle []de l'expression), qui n'a jusqu'à présent pas été utilisée dans ces exemples, par exemple:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

Vous pouvez capturer à la fois par référence et par valeur, que vous pouvez spécifier en utilisant &et =respectivement:

  • [&epsilon] capture par référence
  • [&] capture toutes les variables utilisées dans le lambda par référence
  • [=] capture toutes les variables utilisées dans le lambda par valeur
  • [&, epsilon] capture des variables comme avec [&], mais epsilon par valeur
  • [=, &epsilon] capture des variables comme avec [=], mais epsilon par référence

Le généré operator()est constpar défaut, avec l'implication que les captures seront constlorsque vous y accéderez par défaut. Cela a pour effet que chaque appel avec la même entrée produirait le même résultat, mais vous pouvez marquer le lambdamutable pour demander que ce operator()qui est produit ne le soit pas const.

Flexo
la source
9
@Yakk vous avez été piégé. les lambdas sans capture ont une conversion implicite en pointeurs de type fonction. la fonction de conversion est consttoujours ...
Johannes Schaub - litb
2
@ JohannesSchaub-litb oh sneaky - et cela se produit lorsque vous invoquez ()- il est passé comme un lambda à zéro argument, mais parce () constqu'il ne correspond pas au lambda, il recherche une conversion de type qui le permet, qui inclut le cast implicite -to-function-pointer, puis appelle ça! Sournois!
Yakk - Adam Nevraumont
2
Intéressant - Je pensais à l'origine que les lambdas étaient des fonctions anonymes plutôt que des foncteurs, et j'étais confus quant au fonctionnement des captures.
user253751
50
Si vous voulez utiliser des lambdas comme variables dans votre programme, vous pouvez utiliser: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Mais généralement, nous laissons le compilateur déduire le type: auto f = [](int a, bool b) -> double { ... }; (et n'oubliez pas de #include <functional>)
Evert Heylen
11
Je suppose que tout le monde ne comprend pas pourquoi il return d < 0.00001 ? 0 : d;est garanti de retourner le double, lorsque l'un des opérandes est une constante entière (c'est à cause d'une règle de promotion implicite de l'opérateur?: Où les 2e et 3e opérandes sont équilibrés les uns par rapport aux autres via l'arithmétique habituelle conversions, quel que soit celui qui est sélectionné). Le fait de changer en 0.0 : drendrait peut-être l'exemple plus facile à comprendre.
Lundin
831

Qu'est-ce qu'une fonction lambda?

Le concept C ++ d'une fonction lambda trouve son origine dans le calcul lambda et la programmation fonctionnelle. Un lambda est une fonction sans nom qui est utile (en programmation réelle, pas en théorie) pour de courts extraits de code qui sont impossibles à réutiliser et ne valent pas la peine d'être nommés.

En C ++, une fonction lambda est définie comme ceci

[]() { } // barebone lambda

ou dans toute sa splendeur

[]() mutable -> T { } // T is the return type, still lacking throw()

[]est la liste de capture, ()la liste d'arguments et {}le corps de la fonction.

La liste de capture

La liste de capture définit ce qui doit être disponible de l'extérieur du lambda à l'intérieur du corps de la fonction et comment. Cela peut être soit:

  1. une valeur: [x]
  2. une référence [& x]
  3. toute variable actuellement dans la portée par référence [&]
  4. identique à 3, mais par valeur [=]

Vous pouvez mélanger tout ce qui précède dans une liste séparée par des virgules [x, &y].

La liste des arguments

La liste d'arguments est la même que dans toute autre fonction C ++.

Le corps de fonction

Le code qui sera exécuté lorsque le lambda est réellement appelé.

Déduction du type de retour

Si un lambda n'a qu'une seule instruction de retour, le type de retour peut être omis et a le type implicite de decltype(return_statement).

Mutable

Si un lambda est marqué comme mutable (par exemple []() mutable { } ), il est autorisé de muter les valeurs qui ont été capturées par valeur.

Cas d'utilisation

La bibliothèque définie par la norme ISO bénéficie largement des lambdas et augmente la convivialité de plusieurs barres, car les utilisateurs n'ont plus à encombrer leur code avec de petits foncteurs dans une portée accessible.

C ++ 14

En C ++ 14, les lambdas ont été étendus par diverses propositions.

Captures Lambda initialisées

Un élément de la liste de capture peut maintenant être initialisé avec =. Cela permet de renommer les variables et de les capturer en les déplaçant. Un exemple tiré de la norme:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

et un extrait de Wikipedia montrant comment capturer avec std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Lambdas génériques

Les lambdas peuvent maintenant être génériques ( autoseraient équivalents à Tici s'il y Tavait un argument de modèle de type quelque part dans la portée environnante):

auto lambda = [](auto x, auto y) {return x + y;};

Déduction du type de retour améliorée

C ++ 14 autorise les types de retour déduits pour chaque fonction et ne la limite pas aux fonctions du formulaire return expression;. Ceci est également étendu aux lambdas.

pmr
la source
2
Dans votre exemple pour les captures lambda initialisées ci-dessus, pourquoi mettez-vous fin à la fonction lamba avec le () ;? Cela apparaît comme [] () {} (); au lieu de [](){};. La valeur de x ne devrait-elle pas également être 5?
Ramakrishnan Kannan
7
@RamakrishnanKannan: 1) les () sont là pour appeler le lambda juste après l'avoir défini et y donner sa valeur de retour. La variable y est un entier, pas le lambda. 2) Non, x = 5 est local au lambda (une capture par valeur qui se trouve avoir le même nom que la variable de portée externe x), puis x + 2 = 5 + 2 est retourné. La réaffectation de la variable externe x se fait via la référence r:, r = &x; r += 2;mais cela arrive à la valeur d'origine de 4.
The Vee
168

Les expressions lambda sont généralement utilisées pour encapsuler des algorithmes afin de les transmettre à une autre fonction. Cependant, il est possible d'exécuter un lambda immédiatement après la définition :

[&](){ ...your code... }(); // immediately executed lambda expression

est fonctionnellement équivalent à

{ ...your code... } // simple code block

Cela fait des expressions lambda un outil puissant pour refactoriser des fonctions complexes . Vous commencez par encapsuler une section de code dans une fonction lambda comme indiqué ci-dessus. Le processus de paramétrage explicite peut ensuite être effectué progressivement avec des tests intermédiaires après chaque étape. Une fois que le bloc de code est entièrement paramétré (comme le démontre la suppression du& ), vous pouvez déplacer le code vers un emplacement externe et en faire une fonction normale.

De même, vous pouvez utiliser des expressions lambda pour initialiser des variables en fonction du résultat d'un algorithme ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

Pour partitionner la logique de votre programme , vous pourriez même trouver utile de passer une expression lambda comme argument à une autre expression lambda ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Les expressions Lambda vous permettent également de créer des fonctions imbriquées nommées , ce qui peut être un moyen pratique d'éviter la logique en double. L'utilisation de lambdas nommés a également tendance à être un peu plus facile pour les yeux (par rapport aux lambdas en ligne anonymes) lors du passage d'une fonction non triviale en tant que paramètre à une autre fonction. Remarque: n'oubliez pas le point-virgule après l'accolade fermante.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

Si le profilage suivant révèle une surcharge d'initialisation importante pour l'objet fonction, vous pouvez choisir de le réécrire en tant que fonction normale.

nobar
la source
11
Avez-vous réalisé que cette question a été posée il y a 1,5 an et que la dernière activité remonte à près d'un an? Quoi qu'il en soit, vous apportez des idées intéressantes que je n'ai jamais vues auparavant!
Piotr99
7
Merci pour l'astuce de définition et d'exécution simultanée! Je pense qu'il convient de noter que cela fonctionne comme une contidion pour les ifdéclarations :, en if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespacesupposant que ic'est unstd::string
Blacklight Shining
74
Voici donc une expression légale: [](){}(); .
nobar
8
Pouah! La (lambda: None)()syntaxe de Python est tellement plus lisible.
dan04
9
@nobar - tu as raison, j'ai mal tapé. C'est légal (je l'ai testé cette fois)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38

Réponses

Q: Qu'est-ce qu'une expression lambda en C ++ 11?

R: Sous le capot, c'est l'objet d'une classe autogénérée avec surcharge opérateur () const . Un tel objet est appelé fermeture et créé par le compilateur. Ce concept de «fermeture» est proche du concept de liaison de C ++ 11. Mais les lambdas génèrent généralement un meilleur code. Et les appels via les fermetures permettent une intégration complète.

Q: Quand devrais-je en utiliser un?

R: Pour définir une "logique simple et petite" et demander au compilateur d'effectuer la génération à partir de la question précédente. Vous donnez au compilateur quelques expressions que vous voulez voir dans operator (). Tous les autres compilateurs de trucs vont vous générer.

Q: Quelle classe de problèmes résolvent-ils qui n'était pas possible avant leur introduction?

R: Il s'agit d'une sorte de sucre de syntaxe comme la surcharge des opérateurs au lieu des fonctions personnalisées opérations d' ajout, de sous-traitance ... Mais cela enregistre plus de lignes de code inutile pour encapsuler 1-3 lignes de logique réelle dans certaines classes, etc.! Certains ingénieurs pensent que si le nombre de lignes est plus petit, il y a moins de chance de faire des erreurs (je pense aussi)

Exemple d'utilisation

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Extras sur les lambdas, non couverts par la question. Ignorez cette section si vous n'êtes pas intéressé

1. Valeurs capturées. Ce que vous pouvez capturer

1.1. Vous pouvez faire référence à une variable avec une durée de stockage statique dans lambdas. Ils sont tous capturés.

1.2. Vous pouvez utiliser lambda pour capturer des valeurs "par valeur". Dans ce cas, les variables capturées seront copiées dans l'objet fonction (fermeture).

[captureVar1,captureVar2](int arg1){}

1.3. Vous pouvez capturer comme référence. & - dans ce contexte signifie référence, pas pointeurs.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Il existe une notation pour capturer toutes les variables non statiques par valeur ou par référence

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Il existe une notation pour capturer tous les vars non statiques par valeur ou par référence et spécifier smth. plus. Exemples: capture tous les vars non statiques par valeur, mais par référence capture Param2

[=,&Param2](int arg1){} 

Capture tous les vars non statiques par référence, mais par capture de valeur Param2

[&,Param2](int arg1){} 

2. Déduction du type de déclaration

2.1. Le type de retour lambda peut être déduit si lambda est une expression. Ou vous pouvez le spécifier explicitement.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

Si lambda a plus d'une expression, le type de retour doit être spécifié via le type de retour de fin. En outre, une syntaxe similaire peut être appliquée aux fonctions automatiques et aux fonctions membres

3. Valeurs capturées. Ce que vous ne pouvez pas capturer

3.1. Vous ne pouvez capturer que des variables locales, pas des variables membres de l'objet.

4. Сonversions

4.1 !! Lambda n'est pas un pointeur de fonction et ce n'est pas une fonction anonyme, mais les lambdas sans capture peuvent être implicitement convertis en pointeur de fonction.

ps

  1. Plus d'informations sur la grammaire lambda peuvent être trouvées dans Working draft for Programming Language C ++ # 337, 2012-01-16, 5.1.2. Expressions Lambda, p.88

  2. Dans C ++ 14, la fonctionnalité supplémentaire nommée "capture d'initialisation" a été ajoutée. Il permet d'effectuer arbitrairement la déclaration des données de fermeture des membres:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
bruziuz
la source
Cela [&,=Param2](int arg1){}ne semble pas être une syntaxe valide. La forme correcte serait[&,Param2](int arg1){}
GetFree
Merci. J'ai d'abord essayé de compiler cet extrait. Et il semble que l'assymétrie soit étrange dans les modificateurs autorisés dans la liste de capture // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) mutable {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutable {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
bruziuz
Il semble que cette nouvelle ligne ne soit pas prise en charge dans les commentaires. J'ai ensuite ouvert les expressions Lambda 5.1.2, p.88, "Working Draft, Standard for Programming Language C ++", Dcoument Number: # 337, 2012-01-16. Et regardé la syntaxe grammaticale. Et tu as raison. Il n'y a rien de tel que la capture via "= arg"
bruziuz
Grand merci, corrigé dans la description et également acquérir de nouvelles connaissances par rapport à lui.
bruziuz
16

Une fonction lambda est une fonction anonyme que vous créez en ligne. Il peut capturer des variables comme certains l'ont expliqué (par exemple http://www.stroustrup.com/C++11FAQ.html#lambda ) mais il y a quelques limitations. Par exemple, s'il existe une interface de rappel comme celle-ci,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

vous pouvez écrire une fonction sur place pour l'utiliser comme celle passée à appliquer ci-dessous:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

Mais vous ne pouvez pas faire ça:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

en raison des limitations de la norme C ++ 11. Si vous souhaitez utiliser des captures, vous devez vous fier à la bibliothèque et

#include <functional> 

(ou une autre bibliothèque STL comme un algorithme pour l'obtenir indirectement), puis travaillez avec std :: function au lieu de passer des fonctions normales comme paramètres comme ceci:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}
Ted
la source
1
la raison en est qu'un lambda ne peut se convertir en pointeur de fonction que s'il n'a pas de capture. si applyc'était un modèle qui acceptait un foncteur, cela fonctionnerait
sp2danny
1
Mais le problème est que si apply est une interface existante, vous n'avez peut-être pas le luxe de pouvoir la déclarer différemment d'une ancienne fonction. La norme aurait pu être conçue pour permettre à une nouvelle instance d'une ancienne fonction simple d'être générée chaque fois qu'une telle expression lambda est exécutée, avec des références codées en dur générées aux variables capturées. Il semble qu'une fonction lambda soit générée au moment de la compilation. Il y a également d'autres conséquences. Par exemple, si vous déclarez une variable statique, même si vous réévaluez l'expression lambda, vous n'obtenez pas de nouvelle variable statique.
Ted
1
le pointeur de fonction est souvent destiné à être enregistré, et une capture lambdas peut être hors de portée. que seuls les lambdas de capture moins convertir en fonction des pointeurs était par la conception
sp2danny
1
Vous devez toujours prêter attention aux variables de pile qui sont désallouées pour la même raison dans les deux cas. Voir blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… L'exemple que j'ai écrit avec sortie et appliquer est écrit de telle sorte que si, à la place, les pointeurs de fonction étaient autorisés et utilisés, ils fonctionneraient également. Le col reste alloué jusqu'à la fin de tous les appels de fonction de apply. Comment réécririez-vous ce code pour qu'il fonctionne en utilisant l'interface d'application existante? Souhaitez-vous finir par utiliser des variables globales ou statiques, ou une transformation plus obscure du code?
Ted
1
ou peut-être voulez-vous simplement dire que les expressions lambda sont des valeurs r et donc temporaires, mais que le code reste constant (singleton / statique) afin qu'il puisse être appelé à l'avenir. Dans ce cas, la fonction doit peut-être rester allouée tant que ses captures allouées par pile restent allouées. Bien sûr, cela pourrait devenir compliqué de le dérouler si, par exemple, de nombreuses variations de la fonction sont allouées dans une boucle.
Ted
12

L'une des meilleures explications lambda expressionest donnée par l'auteur de C ++ Bjarne Stroustrup dans son livre ***The C++ Programming Language***chapitre 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Une expression lambda , parfois aussi appelée fonction lambda ou (à proprement parler, mais familièrement) comme lambda , est une notation simplifiée pour définir et utiliser un objet fonction anonyme . Au lieu de définir une classe nommée avec un opérateur (), de créer plus tard un objet de cette classe, et enfin de l'invoquer, nous pouvons utiliser un raccourci.

When would I use one?

Ceci est particulièrement utile lorsque nous voulons passer une opération comme argument à un algorithme. Dans le contexte des interfaces utilisateur graphiques (et ailleurs), ces opérations sont souvent appelées rappels .

What class of problem do they solve that wasn't possible prior to their introduction?

Ici, je suppose que toutes les actions effectuées avec l'expression lambda peuvent être résolues sans elles, mais avec beaucoup plus de code et une complexité beaucoup plus grande. Expression lambda c'est le moyen d'optimiser votre code et un moyen de le rendre plus attractif. Aussi triste de Stroustup:

des moyens efficaces d'optimiser

Some examples

via l'expression lambda

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

ou via la fonction

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

ou même

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

si vous en avez besoin, vous pouvez nommer lambda expressioncomme ci-dessous:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

Ou supposez un autre échantillon simple

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

générera ensuite

0

1

0

1

0

1

0

1

0

1

0 triéx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[]- c'est la liste de capture ou lambda introducer: silambdas n'avez pas besoin d'accéder à leur environnement local, nous pouvons l'utiliser.

Citation du livre:

Le premier caractère d'une expression lambda est toujours [ . Un introducteur lambda peut prendre différentes formes:

[] : une liste de capture vide. Cela implique qu'aucun nom local du contexte environnant ne peut être utilisé dans le corps lambda. Pour de telles expressions lambda, les données sont obtenues à partir d'arguments ou de variables non locales.

[&] : capture implicitement par référence. Tous les noms locaux peuvent être utilisés. Toutes les variables locales sont accessibles par référence.

[=] : capture implicitement par valeur. Tous les noms locaux peuvent être utilisés. Tous les noms font référence à des copies des variables locales prises au point d'appel de l'expression lambda.

[capture-list]: capture explicite; la capture-list est la liste des noms des variables locales à capturer (c'est-à-dire stockées dans l'objet) par référence ou par valeur. Les variables dont le nom est précédé de & sont capturées par référence. D'autres variables sont capturées par valeur. Une liste de capture peut également contenir ceci et les noms suivis de ... comme éléments.

[&, capture-list] : capture implicitement par référence toutes les variables locales dont les noms ne sont pas mentionnés dans la liste. La liste de capture peut contenir cela. Les noms répertoriés ne peuvent pas être précédés de &. Les variables nommées dans la liste de capture sont capturées par valeur.

[=, capture-list] : capture implicitement par valeur toutes les variables locales avec des noms non mentionnés dans la liste. La liste de capture ne peut pas contenir cela. Les noms répertoriés doivent être précédés de &. Les variables nommées dans la liste de capture sont capturées par référence.

Notez qu'un nom local précédé de & est toujours capturé par référence et qu'un nom local non précédé de & est toujours capturé par valeur. Seule la capture par référence permet de modifier des variables dans l'environnement appelant.

Additional

Lambda expression format

entrez la description de l'image ici

Références supplémentaires:

gbk
la source
Belle explication. En utilisant des boucles basées sur une plage, vous pouvez éviter les lambdas et raccourcir le codefor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2

Eh bien, une utilisation pratique que j'ai découverte consiste à réduire le code de plaque de la chaudière. Par exemple:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Sans lambda, vous devrez peut-être faire quelque chose pour différents bsizecas. Bien sûr, vous pouvez créer une fonction, mais que faire si vous souhaitez limiter l'utilisation dans le cadre de la fonction utilisateur âme? la nature de lambda remplit cette condition et je l'utilise pour ce cas.

Mégévolution
la source
2

Les lambda en c ++ sont traités comme "fonction disponible à la volée". oui c'est littéralement sur la route, vous le définissez; utilise le; et lorsque la portée de la fonction parent se termine, la fonction lambda a disparu.

c ++ l'a introduit dans c ++ 11 et tout le monde a commencé à l'utiliser comme à tous les endroits possibles. l'exemple et ce qu'est lambda peuvent être trouvés ici https://en.cppreference.com/w/cpp/language/lambda

je décrirai ce qui n'est pas là mais essentiel à savoir pour chaque programmeur c ++

Lambda n'est pas destiné à être utilisé partout et chaque fonction ne peut pas être remplacée par lambda. Ce n'est pas non plus le plus rapide par rapport à la fonction normale. car il a des frais généraux qui doivent être gérés par lambda.

cela aidera sûrement à réduire le nombre de lignes dans certains cas. il peut être essentiellement utilisé pour la section de code, qui est appelée dans la même fonction une ou plusieurs fois et ce morceau de code n'est pas nécessaire ailleurs pour que vous puissiez créer une fonction autonome pour cela.

Voici l'exemple de base de lambda et ce qui se passe en arrière-plan.

Code d'utilisateur:

int main()
{
  // Lambda & auto
  int member=10;
  auto endGame = [=](int a, int b){ return a+b+member;};

  endGame(4,5);

  return 0;

}

Comment compiler le développe:

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(int a, int b) const
    {
      return a + b + member;
    }

    public: __lambda_6_18(int _member)
    : member{_member}
    {}

  };

  __lambda_6_18 endGame = __lambda_6_18{member};
  endGame.operator()(4, 5);

  return 0;
}

comme vous pouvez le voir, quel type de surcharge il ajoute lorsque vous l'utilisez. donc ce n'est pas une bonne idée de les utiliser partout. il peut être utilisé aux endroits où ils sont applicables.

Sachin Nale
la source
oui c'est littéralement en mouvement, vous le définissez; utilise le; et lorsque la portée de la fonction parent se termine, la fonction lambda a disparu . Et si la fonction renvoie le lambda à l'appelant?
Nawaz
1
Ce n'est pas non plus le plus rapide par rapport à la fonction normale. car il a des frais généraux qui doivent être gérés par lambda. Avez - vous déjà fait exécuter un point de repère pour étayer cette affirmation ? Au contraire, les modèles lambda + produisent souvent le code le plus rapide possible.
Nawaz
1

Un problème qu'il résout: Code plus simple que lambda pour un appel dans un constructeur qui utilise une fonction de paramètre de sortie pour initialiser un membre const

Vous pouvez initialiser un membre const de votre classe, avec un appel à une fonction qui définit sa valeur en redonnant sa sortie comme paramètre de sortie.

sergiol
la source
Cela peut également être fait avec une fonction simple, ce qui est même ce que la réponse acceptée à la question à laquelle vous avez lié dit de faire.
SirGuy