Syntaxe d'expression lambda
Cet article décrit la syntaxe et les éléments structuraux des expressions lambda. Pour une description des expressions lambda, voir Expressions lambda en C++.
Syntaxe d'une expression lambda
La définition suivante de la norme ISO C++11 montre la grammaire d'une expression lambda. (Les éléments signalés par l'indice opt sont facultatifs.)
lambda-introducer lambda-declaratoropt compound-statement
Ces composants de syntaxe sont spécifiés plus précisément :
lambda-introducer :
[ lambda-captureopt ]
lambda-capture :
capture-default
capture-list
capture-default , capture-list
capture-default :
&
=
capture-list :
capture ...opt
capture-list , capture ...opt
capture :
identifier
& identifier
this
lambda-declarator :
( parameter-declaration-clause ) mutableopt
exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt
Visual Studio prend en charge la syntaxe et les fonctionnalités des expressions lambda de la norme C++11, avec les exceptions suivantes :
comme toutes les autres classes, les expressions lambda n'obtiennent pas automatiquement des constructeurs de déplacement générés et des opérateurs d'assignation de déplacement. Pour plus d'informations sur la prise en charge des comportements des références rvalue, voir la section « Références rvalue » de Prise en charge des fonctionnalités C++11 (Modern C++).
L'expression attribute-specifier-seq n'est pas prise en charge dans cette version.
Visual Studio ajoute aux fonctionnalités des expressions lambda C++11 les fonctionnalités suivantes :
les expressions lambda sans état, qui sont converties en pointeurs de fonction utilisant des conventions d'appel aléatoires ;
les types de retour automatiquement déduits pour les corps d'expressions lambda plus complexes que { return expression; }, si toutes les instructions return sont de même type. (Cette fonctionnalité fait partie du projet de norme C++14.)
Propriétés des expressions lambda
L'illustration suivante mappe la grammaire à un exemple :
lambda-introducer (également connu comme la clause de capture)
lambda-declarator (également connu comme la liste de paramètres)
mutable (également connu comme la spécification mutable)
exception-specification (également connu comme la spécification des exceptions)
trailing-return-type (également connu comme le type de retour)
compound-statement (également connu comme le corps d'une expression lambda)
Clause de capture
Une expression lambda est composée d'une classe, d'un constructeur et d'un opérateur d'appel de fonction. De la même façon que lorsque vous définissez une classe, vous devez décidez dans une expression lambda si l'objet obtenu capture les variables par valeur, par référence ou pas du tout. Si une expression lambda a accès à des variables et paramètres de fonction locaux, ceux-ci doivent être capturés. La clause de capture (lambda-introducer dans la syntaxe standard) spécifie si le corps de l'expression lambda peut accéder aux variables figurant dans la portée englobante par valeur ou par référence. Les variables avec le préfixe esperluette (&) sont accessibles par référence et celles qui n'ont pas ce préfixe sont accessibles par valeur.
Une clause de capture vide, [ ], indique que le corps de l'expression lambda n'accède à aucune variable dans la portée englobante.
Vous pouvez utiliser le mode de capture par défaut (capture-default dans la syntaxe standard) pour capturer des variables non spécifiées par valeur ou par référence. Vous spécifiez le mode de capture par défaut en ajoutant & ou = comme premier élément de la clause de capture. L'élément & indique au corps de l'expression lambda qu'il doit accéder par référence aux variables non spécifiées. L'élément = indique au corps de l'expression lambda qu'il doit accéder par valeur aux variables non spécifiées. Par exemple, si le corps d'une expression lambda accède à la variable externe total par référence et à la variable externe factor par valeur, les clauses de capture suivantes sont équivalentes :
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
Il existe une idée reçue concernant l'élément capture-default selon laquelle toutes les variables d'une portée sont capturées, qu'elles soient ou non utilisées dans l'expression lambda. Or, ce n'est pas le cas. Seules les variables mentionnées dans l'expression lambda sont capturées pendant l'utilisation d'un élément capture-default.
Si une clause de capture inclut capture-default &, aucun identifier d'une capture de cette clause de capture ne peut avoir la forme & identifier. De la même façon, si une clause de capture inclut capture-default =, aucune capture de cette clause de capture ne peut avoir la forme = identifier. Les identificateurs (this) ne peuvent pas apparaître plusieurs fois dans une clause de capture. Quelques exemples sont illustrés dans l'extrait de code suivant.
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[i, i]{}; // ERROR: i repeated
}
Un élément capture suivi de points de suspension correspond à une expansion de package, comme le montre l'exemple suivant de modèle variadique :
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
Vous pouvez utiliser les expressions lambda dans le corps d'une méthode de classe. Passez le pointeur this à la clause de capture pour permettre l'accès aux méthodes et membres de données de la classe englobante. Pour obtenir un exemple d'utilisation d'expressions lambda avec des méthodes de classe, voir « Exemple : utilisation d'une expression lambda dans une méthode », dans la rubrique Exemples d'expressions lambda.
Quand vous utilisez une clause de capture, nous vous conseillons de garder les points suivants à l'esprit, en particulier si vous utilisez des expressions lambda avec le multithreading :
les captures de référence peuvent être utilisées pour modifier les variables externes, contrairement aux captures de valeur. (mutable autorise la modification des copies et non des originaux.) ;
les captures de référence reflètent les mises à jour des variables externes, contrairement aux captures de valeur ;
les captures de référence introduisent une dépendance liée à la durée de vie, contrairement aux captures de valeur.
Liste de paramètres
Une liste de paramètres (déclarateur d'expressions lambda dans la syntaxe standard) est facultative et rassemble la liste des paramètres d'une fonction.
Une expression lambda peut accepter une autre expression lambda comme argument. Pour plus d'informations, consultez « Expressions lambda d'ordre supérieur » dans la rubrique Exemples d'expressions lambda.
En raison du caractère facultatif de la liste de paramètres, vous pouvez omettre les parenthèses vides si vous ne passez pas d'argument à une expression lambda et que son lambda-declarator: ne contient ni exception-specification, ni trailing-return-type, ni mutable.
Spécification mutable
En règle générale, l'opérateur d'appel de fonction d'une expression lambda est de type const par valeur, mais ceci s'annule lorsque mutable est ajouté. Cela ne produit pas de données membres mutables. La spécification "mutable" permet au corps d'une expression lambda de modifier les variables capturées par valeur. Certains des exemples cités plus loin dans cet article montrent comment utiliser mutable.
Spécification d'exception
Vous pouvez utiliser la spécification d'exception throw() pour indiquer que l'expression lambda ne lève pas d'exception. Comme avec les fonctions ordinaires, le compilateur Visual C++ génère l'avertissement C4297 si une expression lambda déclare la spécification d'exception throw() et que le corps de l'expression lambda lève une exception, comme indiqué dans l'exemple suivant :
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() throw() { throw 5; }();
}
Pour plus d'informations, consultez Spécifications d'exception.
Type de retour
Le type de retour d'une expression lambda est déduit automatiquement. Vous n'avez pas besoin d'ajouter le mot clé auto, sauf si vous spécifiez un trailing-return-type. Le trailing-return-type ressemble à la partie return-type d'une méthode ou d'une fonction ordinaire. Toutefois, le type de retour doit suivre la liste de paramètres. Vous devez donc ajouter le mot clé trailing-return-type -> avant le type de retour.
Vous pouvez omettre la partie return-type d'une expression lambda si le corps de l'expression lambda contient une seule instruction return ou que l'expression lambda ne retourne pas de valeur. Si le corps d'une expression lambda contient une seule instruction return, le compilateur déduit le type de retour du type de l'expression de retour. Sinon, le compilateur déduit que le type de retour doit être void. Étudiez les extraits de code suivants qui illustrent ce principe.
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list is not valid
Une expression lambda peut générer une autre expression lambda comme valeur de retour. Pour plus d'informations, voir « Expressions lambda d'ordre supérieur » dans la rubrique Exemples d'expressions lambda.
Corps lambda
Le corps d'une expression lambda (compound-statement dans la syntaxe standard) peut contenir tout ce que le corps d'une méthode ou d'une fonction ordinaire peut contenir. Le corps d'une fonction ordinaire et d'une expression lambda peut accéder aux types de variables suivants :
Paramètres
Variables déclarées localement
Données membres de classe, quand elles sont déclarées dans une classe et que this est capturé
Toute variable ayant une durée de stockage statique (par exemple, les variables globales)
En outre, une expression lambda peut accéder aux variables qu'elle capture dans la portée englobante. Une variable est explicitement capturée si elle apparaît dans la clause de capture de l'expression lambda. Sinon, la variable est implicitement capturée. Le corps de l'expression lambda utilise le mode de capture par défaut pour accéder aux variables implicitement capturées.
L'exemple suivant contient une expression lambda qui capture explicitement la variable n par valeur, et capture implicitement la variable m par référence :
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
Sortie :
La variable n étant capturée par valeur, sa valeur reste 0 après l'appel à l'expression lambda. La spécification mutable permet à n d'être modifié au sein d'une expression lambda.
Bien qu'une expression lambda ne puisse capturer que les variables qui ont une durée de stockage automatique, vous pouvez utiliser les variables qui ont une durée de stockage statique dans le corps d'une expression lambda. L'exemple suivant utilise la fonction generate et une expression lambda pour assigner une valeur à chaque élément dans un objet vector. L'expression lambda modifie la variable statique pour générer la valeur de l'élément suivant.
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
Pour plus d'informations, consultez générer.
L'exemple de code suivant utilise la fonction de l'exemple précédent et ajoute un exemple d'expression lambda utilisant l'algorithme STL generate_n. Cette expression lambda affecte un élément d'un objet vector à la somme des deux éléments précédents. Le mot clé mutable est utilisé pour que le corps de l'expression lambda puisse modifier ses copies des variables externes x et y, que l'expression lambda capture par valeur. Étant donné que l'expression lambda capture les variables x et y d'origine par valeur, leurs valeurs restent égales à 1 après l'exécution de l'expression.
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
Sortie :
Pour plus d'informations, consultez generate_n.
Modificateurs Microsoft spécifiques
Si vous utilisez un modificateur Microsoft spécifique tel que __declspec, vous pouvez l'insérer dans une expression lambda immédiatement après parameter-declaration-clause. Par exemple :
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
Pour déterminer si un modificateur est pris en charge par les expressions lambda, voir l'article sur ce sujet dans la section Modificateurs Microsoft spécifiques de la documentation.