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

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

quelques exemples et cas d'utilisation serait utile.

1224
demandé sur hugomg 2011-10-02 18:58:12
la source

8 ответов

le problème

c++ inclut des fonctions génériques utiles comme std::for_each et std::transform , qui peuvent être très pratiques. Malheureusement, ils peuvent aussi être assez encombrants à utiliser, en particulier si le functor que vous souhaitez appliquer est unique à 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 utilisez uniquement f une fois dans cet endroit spécifique, il semble exagéré d'écrire toute une classe juste pour faire quelque chose de trivial et un.

dans C++03 vous pourriez être tenté d'écrire quelque chose comme ce qui suit, pour garder le fonctionnement local:

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

cependant ceci n'est pas autorisé, f ne peut pas être passé à une fonction template en C++03.

la nouvelle solution

C++11 présente lambdas vous permettent d'écrire un foncteur en ligne, Anonyme Pour remplacer le struct f . Pour les petits exemples simples cela peut être plus propre à lire (il garde tout à un endroit) et potentiellement plus simple à maintenir, par exemple dans 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 fonctionnaires anonymes.

types de retour

dans les cas simples le type de retour de la 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 quand vous commencez à écrire plus lambdas 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 ceci 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;
            }
        });
}

"la Capture" variables

jusqu'à présent nous n'avons pas utilisé autre chose que ce qui a été passé à la lambda à l'intérieur, mais nous pouvons également utiliser d'autres variables, dans le lambda. Si vous voulez accéder à d'autres variables, vous pouvez utiliser la clause de capture (la [] de l'expression), qui a été jusqu'à présent inutilisé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 par référence et 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 la lambda en valeur
  • [&, epsilon] capture des variables comme avec [ & ], mais epsilon en valeur
  • [=, &epsilon] capture des variables comme avec [ = ], mais epsilon par référence

le operator() généré est const par défaut, avec l'implication que les captures seront const lorsque vous y accéder par défaut. Cela a l'effet que chaque appel avec la même entrée produirait le même résultat, cependant vous pouvez marquer la lambda comme mutable pour demander que le operator() qui est produit n'est pas const .

1239
répondu Flexo 2018-08-14 12:50:26
la source

qu'est Ce qu'une fonction lambda?

Le C++ concept d'une fonction lambda d'origine dans le lambda-calcul et de 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.

dans C++ une fonction lambda est définie comme ceci

[]() { } // barebone lambda

ou dans toute sa gloire

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

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

La liste de capture

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

  1. a valeur: [x]
  2. une référence [&x]
  3. toute variable actuellement en champ d'application par référence [ & ]
  4. identique à 3, mais en valeur [ = ]

vous pouvez mélanger l'un des éléments ci-dessus dans une liste séparée par des virgules [x, &y] .

La liste des arguments

la liste des arguments est la même que dans toute autre fonction C++.

le corps de La fonction

le code qui sera exécuté lorsque la lambda est effectivement appelée.

déduction du type de déclaration

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

Mutable

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

cas d'Utilisation

la bibliothèque définie par la norme ISO présente de nombreux avantages: lambdas et soulève la convivialité plusieurs barres, car maintenant les utilisateurs n'ont pas à encombrer leur code avec de petits foncteurs dans une certaine portée accessible.

C++14

en C++14 lambdas ont été prolongés par diverses propositions.

Initialisé Lambda Capture

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 pris 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érique

Lambdas peut maintenant être générique ( auto serait équivalent à T ici si T étaient un argument de type de gabarit quelque part dans la portée environnante):

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

Déduction Améliorée Du Type De Déclaration

C++14 permet types de retour déduits pour chaque fonction et ne se limite pas aux fonctions de la forme return expression; . Cela est également étendu à lambdas.

731
répondu pmr 2014-06-03 13:26:10
la source
Les expressions Lambda

sont typiquement utilisées pour encapsuler des algorithmes afin qu'ils puissent être passés à une autre fonction. Toutefois, il est possible d'exécuter une lambda immédiatement après la définition :

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

est fonctionnellement équivalent à

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

ce qui rend lambda expressions un outil puissant pour le remaniement des fonctions complexes . Vous commencez par envelopper une section de code dans une lambda fonction comme indiqué ci-dessus. Le processus de paramétrage explicite peut alors être réalisé progressivement avec des essais intermédiaires après chaque étape. Une fois le bloc de code entièrement paramétré (comme démontré par 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 les expressions lambda pour initialiser des variables basées sur le résultat d'un algorithme ...

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

comme" 15197092020 "une façon de partitionner votre logique de 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...
   });

Lambda expressions vous permettent également de créer nommé fonctions imbriquées , qui peut être un moyen pratique d'éviter la double logique. En utilisant nommé lambdas également tendance à être un peu plus facile sur les yeux (par rapport à anonymous inline lambdas) en passant une fonction non triviale comme 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 subséquent révèle une initialisation significative au-dessus de l'objet de la fonction, vous pouvez choisir de réécrire ceci comme une fonction normale.

153
répondu nobar 2015-11-24 12:22:53
la source

Réponses

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

A: Sous la hotte, il fait l'objet d'une classe autogénérée avec surcharge operator() const . Un tel objet est appelé fermeture et créé par compilateur. Ce concept de "fermeture" est proche du concept bind de C++11. Mais lambdas génère généralement un meilleur code. Et les appels par le biais de fermetures permettent plein d'inlining.

Q: Quand en utiliserais-je un?

A: définir "logique simple et petite" et demander compilateur effectuer la génération de la question précédente. Vous donnez à un compilateur quelques expressions que vous voulez être dans operator(). Toutes les autres choses compilateur va générer pour vous.

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

A: c'est une sorte de sucre de syntaxe comme les opérateurs de surcharge au lieu de fonctions pour les opérations personnalisées Ajouter, sous-traiter ...Mais il sauve plus de lignes de code inutiles pour envelopper 1-3 lignes de logique réelle à certaines classes,et 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'usage

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

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

1. Valeurs saisies. Ce que vous pouvez capturer

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

1.2. Vous pouvez utiliser lambda pour les valeurs de capture "par valeur". Dans ce cas, Vars capturé sera copié à l'objet de fonction (fermeture).

[captureVar1,captureVar2](int arg1){}

1.3. Vous pouvez capturer comme référence. et, dans ce contexte moyenne de référence, pas de pointeurs.

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

1.4. Il existe une notation pour saisir tous les var 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 var non statiques par valeur, ou par référence et spécifier smth. plus. Exemple: Capturer tous les vars non statiques par valeur, mais par Param2 de capture de référence 1519110920"

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

Capturer tous les non-statique vars par référence, mais par la valeur de capture Param2

[&,Param2](int arg1){} 

2. Type de déclaration déduction

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, alors le type de retour doit être spécifié via le type de retour arrière. Aussi, une syntaxe similaire peut être appliqué à des fonctions automatiques et des états-fonctions

3. Valeurs saisies. Ce que vous ne pouvez pas capturer

3.1. Vous ne pouvez capturer que les variables locales vars, pas la variable member de l'objet.

4. Conversions

4.1. lambda n'est pas un pointeur de fonction et il n'est pas une fonction anonyme , mais peut être implicitement converti à une fonction pointeur.

p. S.

  1. de plus amples renseignements sur la grammaire lambda se trouvent dans le document de travail pour le langage de programmation C++ #337, 2012-01-16, 5.1.2. Lambda Expressions,p. 88

  2. dans C++14, la fonction supplémentaire appelée "INIT capture" a été ajoutée. Il permet d'effectuer la déclaration arbitrairement des données de fermeture membres:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
    
30
répondu bruziuz 2018-06-23 00:39:58
la source

Une fonction lambda est une fonction anonyme que vous créez en ligne. Il peut saisir des variables comme certains l'ont expliqué, (par exemple http://www.stroustrup.com/C++11FAQ.html # lambda ) mais il y a certaines limites. Par exemple, s'il y a 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 tu ne peux pas faire ça:

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

à cause des limites de la norme C++11. Si vous voulez utiliser des captures, vous devez compter sur la bibliothèque et

#include <functional> 

(ou une autre bibliothèque STL comme algorithme pour l'obtenir indirectement) et puis travailler 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');
    });
}
12
répondu Ted 2015-03-11 03:16:13
la source

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

What is a lambda expression?

Un lambda expression , parfois aussi appelé lambda la fonction ou (strictement parlant incorrectement, mais familièrement) comme un lambda , est une notation simplifiée pour définir et utiliser un objet de fonction anonyme . Au lieu de définir une classe nommée avec un opérateur(), faire plus tard un objet de cette classe, et finalement pour l'invoquer, on peut utiliser un raccourci.

When would I use one?

C'est particulièrement utile quand nous voulons passer une opération comme une argument vers un algorithme. Dans le contexte des interfaces utilisateur graphiques (et ailleurs), ces opérations sont souvent appelées callbacks .

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

ici je suppose que chaque action faite avec l'expression lambda peut être résolue sans eux, mais avec beaucoup plus de code et beaucoup plus de complexité. Expression Lambda c'est la façon d'optimiser pour votre code et une façon de le rendre plus attrayant. Comme triste par Stroustup:

méthodes efficaces d'optimisation

Some examples

via lambda expression

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 avez besoin du nom lambda expression comme 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 supposons 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);
             }
             );
}

va générer la prochaine

0

1

0

1

0

1

0

1

0

1

0 sortedx-1;x-3;x-4;x-5;x-6;x-7;x-33;

[] - c'est la liste de capture ou lambda introducer : si lambdas ne nécessitent pas d'accès à leur environnement local, nous pouvons l'utiliser.

citation du livre:

le premier caractère d'une expression lambda est toujours [ . Lambda introducteur peut prendre différentes formulaires:

[] : une liste de capture vide. Ce implique qu'aucune des noms locaux du contexte environnant peut être utilisé dans le corps de lambda. Pour de telles expressions lambda, les données sont obtenues à partir de arguments ou à partir de variables non locales.

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

[=] : implicitement saisie par valeur. Tous les locaux les noms peuvent être utilisés. Tous les noms renvoient à des copies des variables locales prise 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 avec des noms précédés de & sont capturées par référence. Les autres variables sont saisies par valeur. Une liste de capture peut contient aussi ceci et les noms suivis par ... en tant qu'éléments.

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

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

notez qu'un nom local précédé de & est toujours saisi par référence et un nom local non pré-cédé par & est toujours saisi par valeur. Seule la saisie par référence permet de modifier les variables la vocation de l'environnement.

Additional

Lambda expression format

enter image description here

autres références:

6
répondu gbk 2016-11-09 14:11:21
la source

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

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

1
répondu sergiol 2017-05-23 15:18:29
la source

Eh bien, une utilisation pratique que j'ai découvert est de réduire le code 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 pourriez avoir besoin de faire quelque chose pour différents bsize cas. Bien sûr, vous pouvez créer une fonction mais que faire si vous voulez limiter l'utilisation dans le cadre de la fonction d'utilisateur de l'âme? la nature de lambda répond à cette exigence et je l'utilise pour cette affaire.

1
répondu Misgevolution 2017-03-30 07:24:52
la source

Autres questions sur c++ lambda c++-faq c++11