Namespaces
Variants

Lambda expressions (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Construit une closure (un objet fonction anonyme capable de capturer les variables dans la portée).

Table des matières

Syntaxe

Expressions lambda sans liste de paramètres de modèle explicite (éventuellement non génériques)
[ captures  ] front-attr  (optionnel) ( params  ) specs  (optionnel) except  (optionnel)
back-attr  (optionnel) trailing  (optionnel) requires  (optionnel) contract-specs  (optionnel) { body }
(1)
[ captures  ] { body } (2) (jusqu'en C++23)
[ captures  ] front-attr  (optionnel) trailing  (optionnel) contract-specs  (optionnel) { body } (2) (depuis C++23)
[ captures  ] front-attr  (optionnel) except
back-attr  (optionnel) trailing  (optionnel) contract-specs  (optionnel) { body }
(3) (depuis C++23)
[ captures  ] front-attr  (optionnel) specs except  (optionnel)
back-attr  (optionnel) trailing  (optionnel) contract-specs  (optionnel) { body }
(4) (depuis C++23)
Expressions lambda avec une liste de paramètres de modèle explicite (toujours générique) (depuis C++20)
[ captures  ] < tparams  > t-requires  (facultatif)
front-attr  (facultatif) ( params  ) specs  (facultatif) except  (facultatif)
back-attr  (facultatif) trailing  (facultatif) requires  (facultatif) contract-specs  (facultatif) { body }
(1)
[ captures  ] < tparams  > t-requires  (facultatif) { body } (2) (jusqu'en C++23)
[ captures  ] < tparams  > t-requires  (facultatif)
front-attr  (facultatif) trailing  (facultatif) contract-specs  (facultatif) { body }
(2) (depuis C++23)
[ captures  ] < tparams  > t-requires  (facultatif) front-attr  (facultatif) except
back-attr  (facultatif) trailing  (facultatif) contract-specs  (facultatif) { body }
(3) (depuis C++23)
[ captures  ] < tparams  > t-requires  (facultatif) front-attr  (facultatif) specs except  (facultatif)
back-attr  (facultatif) trailing  (facultatif) contract-specs  (facultatif) { body }
(4) (depuis C++23)
1) L'expression lambda avec une liste de paramètres.
2-4) L'expression lambda sans liste de paramètres.
2) La syntaxe la plus simple. back-attr ne peut pas être appliqué.
3,4) back-attr ne peut être appliqué que si l'un des specs ou except est présent.

Explication

captures - Spécifie les entités à capturer .
tparams - Une liste non vide de paramètres séparés par des virgules, template parameters , utilisée pour fournir des noms aux paramètres de template d'une lambda générique (voir ClosureType::operator() ci-dessous).
t-requires - Ajoute constraints aux tparams .

Si t-requires se termine par une séquence de spécificateurs d'attributs, les attributs dans la séquence sont traités comme des attributs dans front-attr .

(since C++23)
front-attr - (depuis C++23) Une séquence de spécificateurs d'attributs s'applique à operator ( ) du type de fermeture (et ainsi l'attribut [[ noreturn ]] peut être utilisé).
params - La liste de paramètres de operator ( ) du type de fermeture.

Elle peut avoir un paramètre objet explicite .

(depuis C++23)
specs - Une liste des spécificateurs suivants, chaque spécificateur étant autorisé au maximum une fois dans chaque séquence.
Spécificateur Effet
mutable Permet au corps de modifier les objets capturés par copie et d'appeler leurs fonctions membres non constantes.
  • Ne peut pas être utilisé si un paramètre objet explicite est présent.
(depuis C++23)
constexpr
(depuis C++17)
Spécifie explicitement que operator ( ) est une fonction constexpr .
  • Si operator ( ) satisfait toutes les exigences des fonctions constexpr, operator ( ) sera constexpr même si constexpr n'est pas présent.
consteval
(depuis C++20)
Spécifie que operator ( ) est une fonction immédiate .
  • consteval et constexpr ne peuvent pas être spécifiés simultanément.
static
(depuis C++23)
Spécifie que operator ( ) est une fonction membre statique .
  • static et mutable ne peuvent pas être spécifiés simultanément.
  • Ne peut pas être utilisé si captures n'est pas vide, ou si un paramètre objet explicite est présent.
except - Fournit la spécification d'exception dynamique ou (jusqu'à C++20) le spécificateur noexcept pour operator ( ) du type de fermeture.
back-attr - Une séquence de spécification d'attributs s'applique au type de operator ( ) du type de fermeture (et donc l'attribut [[ noreturn ]] ne peut pas être utilisé).
trailing - -> ret , où ret spécifie le type de retour.
requires - (since C++20) Ajoute des contraintes à operator ( ) du type de fermeture.
contract-specs - (since C++26) Une liste de spécificateurs de contrat de fonction pour operator ( ) du type de fermeture.
body - Le corps de la fonction.


Si auto est utilisé comme type d'un paramètre ou si une liste de paramètres de modèle explicite est fournie (depuis C++20) , la lambda est une lambda générique .

(depuis C++14)

Une variable __func__ est implicitement définie au début du corps , avec une sémantique comme décrite ici .

Type de fermeture

L'expression lambda est une expression prvalue d'un type de classe non-union non-agrégat unique et sans nom, appelé type de fermeture , qui est déclaré (aux fins de ADL ) dans la plus petite portée de bloc, de classe ou d'espace de noms qui contient l'expression lambda.

Le type de fermeture est un type structurel si et seulement si captures est vide.

(depuis C++20)

Le type de fermeture possède les membres suivants, ils ne peuvent pas être explicitement instanciés , explicitement spécialisés , ou (depuis C++14) nommés dans une déclaration friend :

ClosureType:: operator()( params )

ret operator ( ) ( params ) { body }
(static et const peuvent être présents, voir ci-dessous)
template < template - params >
ret operator ( ) ( params ) { body }
(depuis C++14)
(lambda générique, static et const peuvent être présents, voir ci-dessous)

Exécute le corps de l'expression lambda, lorsqu'elle est invoquée. Lors de l'accès à une variable, accède à sa copie capturée (pour les entités capturées par copie), ou à l'objet original (pour les entités capturées par référence).

La liste des paramètres de operator ( ) est params si elle est fournie, sinon la liste des paramètres est vide.

Le type de retour de operator ( ) est le type spécifié dans le spécificateur de fin .

Si trailing n'est pas fourni, le type de retour de operator ( ) est automatiquement déduit . [1]

Sauf si le mot-clé mutable a été utilisé dans les spécificateurs de lambda , ou qu'un paramètre objet explicite est présent (depuis C++23) , le qualificatif cv de operator ( ) est const et les objets capturés par copie ne sont pas modifiables depuis cet operator ( ) . Le qualificatif explicite const n'est pas autorisé. operator ( ) n'est jamais virtuel et ne peut pas avoir le qualificatif volatile .

operator ( ) est toujours constexpr s'il satisfait aux exigences d'une fonction constexpr . Il est également constexpr si le mot-clé constexpr a été utilisé dans les spécificateurs lambda.

(depuis C++17)

operator ( ) est une fonction immédiate si le mot-clé consteval a été utilisé dans les spécificateurs lambda.

(depuis C++20)

operator ( ) est une fonction membre statique si le mot-clé static a été utilisé dans les spécificateurs lambda.

operator ( ) est une fonction membre à objet explicite si params contient un paramètre objet explicite.

(depuis C++23)


Pour chaque paramètre dans params dont le type est spécifié comme auto , un paramètre de template inventé est ajouté à template-params , dans l'ordre d'apparition. Le paramètre de template inventé peut être un parameter pack si le membre fonction correspondant de params est un function parameter pack.

// generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // nullary lambda (takes no parameters):
        return [=] { printer(ts...); };
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14
(depuis C++14)


Si la définition du lambda utilise une liste de paramètres de modèle explicite, cette liste de paramètres de modèle est utilisée avec operator ( ) . Pour chaque paramètre dans params dont le type est spécifié comme auto , un paramètre de modèle supplémentaire inventé est ajouté à la fin de cette liste de paramètres de modèle :

// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(depuis C++20)

La spécification d'exception except sur l'expression lambda s'applique à operator ( ) .

À des fins de recherche de nom , de détermination du type et de la valeur du pointeur this et pour l'accès aux membres non statiques de la classe, le corps de l'opérateur operator ( ) du type de closure est considéré dans le contexte de l'expression lambda.

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // le contexte de la lambda suivante est la fonction membre X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this a le type X*
        };
    }
};

Références pendantes

Si une entité non référence est capturée par référence, implicitement ou explicitement, et que l' operator ( ) de l'objet de fermeture est invoqué après la fin de la durée de vie de l'entité, un comportement indéfini se produit. Les fermetures C++ n'étendent pas la durée de vie des objets capturés par référence.

Il en va de même pour la durée de vie de l'objet * this actuel capturé via this .

  1. Bien que la déduction du type de retour des fonctions soit introduite en C++14, sa règle est disponible pour la déduction du type de retour des lambda en C++11.

ClosureType:: operator ret (*)( params )()

lambda sans capture non générique
using F = ret ( * ) ( params ) ;
operator F ( ) const noexcept ;
(jusqu'en C++17)
using F = ret ( * ) ( params ) ;
constexpr operator F ( ) const noexcept ;
(depuis C++17)
lambda sans capture générique
template < template - params > using fptr_t = /* voir ci-dessous */ ;

template < template - params >

operator fptr_t < template - params > ( ) const noexcept ;
(depuis C++14)
(jusqu'en C++17)
template < template - params > using fptr_t = /* voir ci-dessous */ ;

template < template - params >

constexpr operator fptr_t < template - params > ( ) const noexcept ;
(depuis C++17)

Cette fonction de conversion définie par l'utilisateur n'est définie que si l'expression lambda n'a pas de captures  et n'a pas de paramètre objet explicite (depuis C++23) . C'est une fonction membre publique, constexpr, (depuis C++17) non virtuelle, non explicite, const noexcept de l'objet de fermeture.

Cette fonction est une fonction immédiate si l'opérateur d'appel de fonction (ou sa spécialisation, pour les lambdas génériques) est une fonction immédiate.

(depuis C++20)

Une lambda générique sans capture possède une fonction de conversion générique définie par l'utilisateur avec la même liste de paramètres de template inventée que operator ( ) .

void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {}  // #1
void h(char (*)(int)) {} // #2
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // erreur : non convertible
h(glambda);  // OK : appelle #1 car #2 n'est pas convertible
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(depuis C++14)


La valeur retournée par la fonction de conversion est un pointeur vers une fonction avec la liaison linguistique C++ qui, lorsqu'elle est invoquée, a le même effet que d'invoquer l'opérateur d'appel de fonction du type de fermeture sur une instance default-construite du type de fermeture.

(jusqu'en C++14)

La valeur retournée par la fonction de conversion (modèle) est un pointeur vers une fonction avec la liaison linguistique C++ qui, lorsqu'elle est invoquée, a le même effet que :

  • pour les lambdas non génériques, invoquer l'opérateur operator ( ) du type de fermeture sur une instance default-construite du type de fermeture.
  • pour les lambdas génériques, invoquer la spécialisation correspondante de l'opérateur operator ( ) du lambda générique sur une instance default-construite du type de fermeture.
(depuis C++14)
(jusqu'en C++23)

La valeur retournée par la fonction de conversion (modèle) est

  • si l'opérateur operator ( ) est statique, un pointeur vers cet opérateur avec la liaison linguistique C++,
  • sinon, un pointeur vers une fonction avec la liaison linguistique C++ qui, lorsqu'elle est invoquée, a le même effet que :
    • pour les lambdas non génériques, invoquer l'opérateur operator ( ) du type de fermeture sur une instance default-construite du type de fermeture.
    • pour les lambdas génériques, invoquer la spécialisation correspondante de l'opérateur operator ( ) du lambda générique sur une instance default-construite du type de fermeture.
(depuis C++23)


Cette fonction est constexpr si l'opérateur d'appel de fonction (ou sa spécialisation, pour les lambdas génériques) est constexpr.

auto Fwd = [](int(*fp)(int), auto a) { return fp(a); };
auto C = [](auto a) { return a; };
static_assert(Fwd(C, 3) == 3);  // OK
auto NC = [](auto a) { static int s; return a; };
static_assert(Fwd(NC, 3) == 3); // error: no specialization can be
                                // constexpr because of static s

Si l'opérateur d'appel de l'objet de fermeture operator ( ) possède une spécification d'exception non levante, alors le pointeur retourné par cette fonction a le type pointeur vers fonction noexcept.

(depuis C++17)

ClosureType:: ClosureType()

ClosureType ( ) = default ;
(depuis C++20)
(uniquement si aucune capture n'est spécifiée)
ClosureType ( const ClosureType & ) = default ;
ClosureType ( ClosureType && ) = default ;

Les types de fermeture ne sont pas DefaultConstructible . Les types de fermeture n'ont pas de constructeur par défaut.

(jusqu'à C++20)

Si aucune capture n'est spécifiée, le type de fermeture a un constructeur par défaut par défaut. Sinon, il n'a pas de constructeur par défaut (ceci inclut le cas où il y a une capture par défaut , même si elle ne capture effectivement rien).

(depuis C++20)

Le constructeur de copie et le constructeur de déplacement sont déclarés comme étant par défaut et peuvent être implicitement définis selon les règles habituelles pour les constructeurs de copie et les constructeurs de déplacement .

ClosureType:: operator=(const ClosureType&)

ClosureType & operator = ( const ClosureType & ) = delete ;
(jusqu'à C++20)
ClosureType & operator = ( const ClosureType & ) = default ;
ClosureType & operator = ( ClosureType && ) = default ;
(depuis C++20)
(seulement si aucune capture n'est spécifiée)
ClosureType & operator = ( const ClosureType & ) = delete ;
(depuis C++20)
(sinon)

L'opérateur d'affectation par copie est défini comme supprimé (et l'opérateur d'affectation par déplacement n'est pas déclaré). Les types de fermeture ne sont pas CopyAssignable .

(jusqu'à C++20)

Si aucune capture n'est spécifiée, le type de fermeture a un opérateur d'affectation par copie par défaut et un opérateur d'affectation par déplacement par défaut. Sinon, il a un opérateur d'affectation par copie supprimé (ceci inclut le cas où il y a une capture par défaut , même si elle ne capture effectivement rien).

(depuis C++20)

ClosureType:: ~ClosureType()

~ClosureType ( ) = default ;

Le destructeur est implicitement déclaré.

ClosureType:: Captures

T1 a ;

T2 b ;

...

Si l'expression lambda capture quoi que ce soit par copie (soit implicitement avec la clause de capture [=] soit explicitement avec une capture qui n'inclut pas le caractère &, par exemple [a, b, c] ), le type de fermeture inclut des membres de données non statiques sans nom, déclarés dans un ordre non spécifié, qui détiennent des copies de toutes les entités ainsi capturées.

Ces membres de données qui correspondent aux captures sans initialiseurs sont initialisés directement lorsque l'expression lambda est évaluée. Ceux qui correspondent aux captures avec initialiseurs sont initialisés selon les exigences de l'initialiseur (pourrait être une initialisation par copie ou directe). Si un tableau est capturé, les éléments du tableau sont initialisés directement dans l'ordre croissant des indices. L'ordre dans lequel les membres de données sont initialisés est l'ordre dans lequel ils sont déclarés (qui est non spécifié).

Le type de chaque membre de données est le type de l'entité capturée correspondante, sauf si l'entité a un type référence (dans ce cas, les références aux fonctions sont capturées comme des références de lvalue aux fonctions référencées, et les références aux objets sont capturées comme des copies des objets référencés).

Pour les entités qui sont capturées par référence (avec la capture par défaut [&] ou lors de l'utilisation du caractère &, par exemple [&a, &b, &c] ), il n'est pas spécifié si des membres de données supplémentaires sont déclarés dans le type de fermeture , mais tout membre supplémentaire de ce type doit satisfaire LiteralType (depuis C++17) .


Les expressions lambda ne sont pas autorisées dans les expressions non évaluées , arguments de template , déclarations d'alias , déclarations typedef , et n'importe où dans une déclaration de fonction (ou de fonction template) sauf le corps de la fonction et les arguments par défaut de la fonction.

(jusqu'à C++20)

Capture de lambda

Les captures définissent les variables externes accessibles depuis le corps de la fonction lambda. Sa syntaxe est définie comme suit :

capture-default (1)
capture-list (2)
capture-default , capture-list (3)
capture-default - l'un des & et =
capture-list - une liste séparée par des virgules de capture s


La syntaxe de capture est définie comme suit :

identifiant (1)
identifiant ... (2)
identifiant initialiseur (3) (depuis C++14)
& identifiant (4)
& identifiant ... (5)
& identifiant initialiseur (6) (depuis C++14)
this (7)
* this (8) (depuis C++17)
... identifiant initialiseur (9) (depuis C++20)
& ... identifiant initialiseur (10) (depuis C++20)
1) capture simple par copie
2) capture simple par copie qui est une expansion de pack
3) capture par copie avec un initializer
4) capture simple par référence
5) capture simple par référence qui est une expansion de paquet
6) capture par référence avec un initialiseur
7) capture simple par référence de l'objet courant
8) capture simple par copie de l'objet courant
9) capture par copie avec un initialiseur qui est une expansion de pack
10) capture par référence avec un initialiseur qui est une expansion de paquet

Si le capture-default est & , les captures simples suivantes ne doivent pas commencer par & .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // OK : capture par référence par défaut
    [&, i] {};       // OK : capture par référence, sauf i qui est capturé par copie
    [&, &i] {};      // Erreur : capture par référence lorsque la capture par référence est le défaut
    [&, this] {};    // OK, équivalent à [&]
    [&, this, i] {}; // OK, équivalent à [&, i]
}

Si la capture-par-défaut est = , les captures simples suivantes doivent commencer par & ou être *this (depuis C++17) ou this (depuis C++20) .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK : capture par défaut par copie
    [=, &i] {};    // OK : capture par copie, sauf i capturé par référence
    [=, *this] {}; // jusqu'à C++17 : Erreur : syntaxe invalide
                   // depuis C++17 : OK : capture l'objet S2 englobant par copie
    [=, this] {};  // jusqu'à C++20 : Erreur : this lorsque = est la valeur par défaut
                   // depuis C++20 : OK, identique à [=]
}

Toute capture ne peut apparaître qu'une seule fois, et son nom doit être différent de tout nom de paramètre :

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // Erreur : i répété
    [this, *this] {}; // Erreur : "this" répété (C++17)
    [i] (int i) {};   // Erreur : paramètre et capture ont le même nom
}

Une expression lambda peut utiliser une variable sans la capturer si la variable

Une expression lambda peut lire la valeur d'une variable sans la capturer si la variable

  • a un type intégral ou énumération const non volatile et a été initialisé avec une expression constante , ou
  • est constexpr et n'a pas de membres mutable.

L'objet actuel ( * this ) peut être capturé implicitement si un défaut de capture est présent. S'il est capturé implicitement, il est toujours capturé par référence, même si le défaut de capture est = . La capture implicite de * this lorsque le défaut de capture est = est dépréciée. (depuis C++20)

Seules les expressions lambda satisfaisant l'une des conditions suivantes peuvent avoir une capture-default ou une capture sans initialiseurs :

(depuis C++26)

Pour une telle expression lambda, la portée atteignable est définie comme l'ensemble des portées englobantes jusqu'à et y compris la fonction englobante la plus interne (et ses paramètres). Cela inclut les portées de blocs imbriqués et les portées des lambdas englobants si ce lambda est imbriqué.

L' identifiant dans toute capture sans initialiseur (autre que la capture this ) est recherché en utilisant la recherche de nom non qualifiée habituelle dans la portée englobante de la lambda. Le résultat de la recherche doit être une variable avec une durée de stockage automatique déclarée dans la portée englobante , ou une liaison structurée dont la variable correspondante satisfait à ces exigences (depuis C++20) . L'entité est explicitement capturée .

Une capture avec un initialiseur, appelée init-capture , agit comme si elle déclarait et capturait explicitement une variable déclarée avec le spécificateur de type auto et le même initialiseur, dont la région déclarative est le corps de l'expression lambda (c'est-à-dire qu'elle n'est pas dans la portée de son initialiseur), sauf que :

  • si la capture est par copie, le membre de données non statique introduit de l'objet de fermeture est une autre façon de se référer à cette variable ;
    • en d'autres termes, la variable source n'existe pas réellement, et la déduction de type via auto et l'initialisation sont appliquées au membre de données non statique ;
  • si la capture est par référence, la durée de vie de la variable de référence se termine lorsque la durée de vie de l'objet de fermeture se termine.

Ceci est utilisé pour capturer des types non copiables avec une capture telle que x = std :: move ( x ) .

Cela permet également de capturer par référence constante, avec & cr = std:: as_const ( x ) ou similaire.

int x = 4;
auto y = [&r = x, x = x + 1]() -> int
{
    r += 2;
    return x * x;
}(); // met à jour ::x à 6 et initialise y à 25.
(depuis C++14)

Si captures a une capture-default et ne capture pas explicitement l'objet englobant (comme this ou * this ), ou une variable automatique qui est odr-usable dans le corps de la lambda , ou une structured binding dont la variable correspondante a une durée de stockage atomique (depuis C++20) , elle capture l'entité implicitement si l'entité est nommée dans une expression potentiellement évaluée au sein d'une expression (y compris lorsque le this - > implicite est ajouté avant une utilisation d'un membre de classe non statique).

Pour déterminer les captures implicites, typeid n'est jamais considéré comme rendant ses opérandes non évalués.

Les entités peuvent être implicitement capturées même si elles sont uniquement nommées dans une instruction rejetée après l'instanciation du corps de la lambda.

(since C++17)
void f(int, const int (&)[2] = {}) {}   // #1
void f(const int&, const int (&)[1]) {} // #2
struct NoncopyableLiteralType
{
    constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
    NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
    int n_;
};
void test()
{
    const int x = 17;
    auto l0 = []{ f(x); };           // OK : appelle #1, ne capture pas x
    auto g0 = [](auto a) { f(x); };  // identique au précédent
    auto l1 = [=]{ f(x); };          // OK : capture x (depuis P0588R1) et appelle #1
                                     // la capture peut être optimisée
    auto g1 = [=](auto a) { f(x); }; // identique au précédent
    auto ltid = [=]{ typeid(x); };   // OK : capture x (depuis P0588R1)
                                     // même si x n'est pas évalué
                                     // la capture peut être optimisée
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK : expression dépendante, donc capture x
    };
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // capture x indépendamment du fait
                        // que a + x soit un opérande non évalué
    };
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK : w n'est pas odr-utilisé, capture inutile
    // auto l5 = [=]{ return w.n_; };  // erreur : w doit être capturé par copie
}

Si le corps d'un lambda odr-utilise une entité capturée par copie, le membre du type de fermeture est accédé. S'il n'odr-utilise pas l'entité, l'accès se fait à l'objet original :

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // pas une utilisation odr : fait référence au const int N de g
        f(&N); // utilisation odr : provoque la capture de N (par copie)
               // &N est l'adresse du membre N de l'objet de fermeture, pas de N de g
    }();
}

Si une lambda utilise odr une référence qui est capturée par référence, elle utilise l'objet désigné par la référence originale, et non la référence capturée elle-même :

#include <iostream>
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
int main()
{
    int i = 3;
    auto f = make_function(i); // l'utilisation de x dans f se lie directement à i
    i = 5;
    f(); // OK : affiche 5
}

Dans le corps d'une lambda avec capture par défaut = , le type de toute entité capturable est comme s'il était capturé (et donc une qualification const est souvent ajoutée si la lambda n'est pas mutable ), même si l'entité se trouve dans un opérande non évalué et n'est pas capturée (par exemple dans decltype ):

void f3()
{
    float x, &r = x;
    [=]
    { // x et r ne sont pas capturés (l'apparition dans un opérande decltype n'est pas une odr-use)
        decltype(x) y1;        // y1 a le type float
        decltype((x)) y2 = y1; // y2 a le type float const& car ce lambda
                               // n'est pas mutable et x est une lvalue
        decltype(r) r1 = y1;   // r1 a le type float& (transformation non considérée)
        decltype((r)) r2 = y2; // r2 a le type float const&
    };
}

Toute entité capturée par une lambda (implicitement ou explicitement) est odr-utilisée par l'expression lambda (par conséquent, la capture implicite par une lambda imbriquée déclenche la capture implicite dans la lambda englobante).

Toutes les variables implicitement capturées doivent être déclarées dans la portée atteignable de la lambda.

Si une lambda capture l'objet englobant (comme this ou * this ), soit la fonction englobante la plus proche doit être une fonction membre non statique, soit la lambda doit se trouver dans un initialiseur de membre par défaut :

struct s2
{
    double ohseven = .007;
    auto f() // fonction englobante la plus proche pour les deux lambdas suivantes
    {
        return [this]      // capture l'instance s2 englobante par référence
        {
            return [*this] // capture l'instance s2 englobante par copie (C++17)
            {
                return ohseven;// OK
            }
        }();
    }
    auto g()
    {
        return [] // ne capture rien
        { 
            return [*this] {};// erreur : *this non capturé par l'expression lambda externe
        }();
    }
};

Si une expression lambda (ou une spécialisation de l'opérateur d'appel de fonction d'une lambda générique) (depuis C++14) utilise ODR * this ou toute variable avec durée de stockage automatique, elle doit être capturée par l'expression lambda.

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N et M ne sont pas odr-utilisés
                         // (correct qu'ils ne soient pas capturés)
            x[0][0] = i; // i est explicitement capturé par m2
                         // et implicitement capturé par m1
        };
    };
    struct s1 // classe locale dans f1()
    {
        int f;
        void work(int n) // fonction membre non-statique
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // erreur : j n'est pas capturé par m3
                {
                    int x = n; // erreur : n est implicitement capturé par m4
                               // mais pas capturé par m3
                    x += m;    // OK : m est implicitement capturé par m4
                               // et explicitement capturé par m3
                    x += i;    // erreur : i est en dehors de la portée atteignable
                               // (qui se termine à work())
                    x += f;    // OK : this est capturé implicitement par m4
                               // et explicitement capturé par m3
                };
            };
        }
    };
}

Les membres de classe ne peuvent pas être capturés explicitement par une capture sans initialiseur (comme mentionné ci-dessus, seules les variables sont autorisées dans la capture-list ):

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // erreur : x n'est pas une variable
        auto l2 = [i, x = x] { use(i, x); };  // OK, capture par copie
        i = 1; x = 1; l2(); // appelle use(0,0)
        auto l3 = [i, &x = x] { use(i, x); }; // OK, capture par référence
        i = 2; x = 2; l3(); // appelle use(1,2)
    }
};

Lorsqu'un lambda capture un membre par copie implicite, il ne fait pas de copie de cette variable membre : l'utilisation d'une variable membre m est traitée comme une expression ( * this ) . m , et * this est toujours implicitement capturé par référence :

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
        auto l1 = [=] { use(i, x); }; // capture une copie de i et
                                      // une copie du pointeur this
        i = 1; x = 1; l1();           // appelle use(0, 1), comme si
                                      // i par copie et x par référence
        auto l2 = [i, this] { use(i, x); }; // identique au précédent, rendu explicite
        i = 2; x = 2; l2();           // appelle use(1, 2), comme si
                                      // i par copie et x par référence
        auto l3 = [&] { use(i, x); }; // capture i par référence et
                                      // une copie du pointeur this
        i = 3; x = 2; l3();           // appelle use(3, 2), comme si
                                      // i et x sont tous deux par référence
        auto l4 = [i, *this] { use(i, x); }; // effectue une copie de *this,
                                             // incluant une copie de x
        i = 4; x = 4; l4();           // appelle use(3, 2), comme si
                                      // i et x sont tous deux par copie
    }
};

Si une expression lambda apparaît dans un argument par défaut , elle ne peut rien capturer explicitement ou implicitement , sauf si toutes les captures ont des initialiseurs qui satisfont aux contraintes d'une expression apparaissant dans un argument par défaut (depuis C++14) :

void f2()
{
    int i = 1;
    void g1( int = [i] { return i; }() ); // erreur : capture quelque chose
    void g2( int = [i] { return 0; }() ); // erreur : capture quelque chose
    void g3( int = [=] { return i; }() ); // erreur : capture quelque chose
    void g4( int = [=] { return 0; }() );       // OK : sans capture
    void g5( int = [] { return sizeof i; }() ); // OK : sans capture
    // C++14
    void g6( int = [x = 1] { return x; }() ); // OK : 1 peut apparaître
                                              //     dans un argument par défaut
    void g7( int = [x = i] { return x; }() ); // erreur : i ne peut pas apparaître
                                              //        dans un argument par défaut
}

Les membres des unions anonymes ne peuvent pas être capturés. Les champs de bits ne peuvent être capturés que par copie.

Si une lambda imbriquée m2 capture quelque chose qui est également capturée par la lambda englobante immédiate m1 , alors la capture de m2 est transformée comme suit :

  • si le lambda englobant m1 capture par copie, m2 capture le membre non statique du type de fermeture de m1 , et non la variable originale ou * this ; si m1 n'est pas mutable, le membre de données non statique est considéré comme qualifié const.
  • si le lambda englobant m1 capture par référence, m2 capture la variable originale ou * this .
#include <iostream>
int main()
{
    int a = 1, b = 1, c = 1;
    auto m1 = [a, &b, &c]() mutable
    {
        auto m2 = [a, b, &c]() mutable
        {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };
    a = 2; b = 2; c = 2;
    m1();                             // appelle m2() et affiche 123
    std::cout << a << b << c << '\n'; // affiche 234
}

Si une lambda capture quoi que ce soit, le type du paramètre objet explicite (s'il y en a un) de l'opérateur d'appel de fonction ne peut être que

  • le type de fermeture,
  • un type de classe publiquement et sans ambiguïté dérivé du type de fermeture, ou
  • une référence à un tel type éventuellement qualifié cv.
struct C 
{
    template<typename T>
    C(T);
};
void func(int i) 
{
    int x = [=](this auto&&) { return i; }();  // OK
    int y = [=](this C) { return i; }();       // error
    int z = [](this C) { return 42; }();       // OK
    auto lambda = [n = 42] (this auto self) { return n; };
    using Closure = decltype(lambda);
    struct D : private Closure {
        D(Closure l) : Closure(l) {}
        using Closure::operator();
        friend Closure;
    };
    D{lambda}(); // error
}
(depuis C++23)

Notes

Macro de test de fonctionnalité Valeur Std Fonctionnalité
__cpp_lambdas 200907L (C++11) Expressions lambda
__cpp_generic_lambdas 201304L (C++14) Expressions lambda génériques
201707L (C++20) Liste de paramètres de template explicite pour les lambdas génériques
__cpp_init_captures 201304L (C++14) Capture d'initialisation lambda
201803L (C++20) Autoriser l'expansion de pack dans la capture d'initialisation lambda
__cpp_capture_star_this 201603L (C++17) Capture lambda de * this par valeur comme [ = , * this ]
__cpp_constexpr 201603L (C++17) Lambda constexpr
__cpp_static_call_operator 202207L (C++23) static operator ( ) pour les lambdas sans capture

La règle pour la capture implicite des lambdas est légèrement modifiée par le rapport de défaut P0588R1 . En date d'octobre 2023, certaines implémentations majeures n'ont pas complètement implémenté ce DR, et donc l'ancienne règle, qui détecte l'utilisation ODR , est encore utilisée dans certains cas.

Règle ancienne avant P0588R1

Si captures a une capture-par-défaut et ne capture pas explicitement l'objet englobant (comme this ou *this ), ou une variable automatique qui est odr-utilisable dans le corps de la lambda , ou une liaison structurée dont la variable correspondante a une durée de stockage atomique (depuis C++20) , elle capture l'entité implicitement si l'entité est

  • nommée dans une expression potentiellement évaluée au sein d'une expression qui dépend d'un paramètre de template d'une lambda générique, ou
(depuis C++14)

Exemple

Cet exemple montre comment passer une lambda à un algorithme générique et comment les objets résultant d'une expression lambda peuvent être stockés dans des objets std::function .

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
int main()
{
    std::vector<int> c{1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
    std::cout << '\n';
    // le type d'une fermeture ne peut pas être nommé, mais peut être déduit avec auto
    // depuis C++14, les lambda peuvent posséder des arguments par défaut
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
    // comme tous les objets appelables, les fermetures peuvent être capturées dans std::function
    // (cela peut entraîner une surcharge inutile)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
    constexpr int fib_max {8};
    std::cout << "Émuler les appels de `lambda récursive` :\nNombres de Fibonacci : ";
    auto nth_fibonacci = [](int n)
    {
        std::function<int(int, int, int)> fib = [&](int n, int a, int b)
        {
            return n ? fib(n - 1, a + b, a) : b;
        };
        return fib(n, 0, 1);
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n");
    std::cout << "Approche alternative pour la récursion lambda :\nNombres de Fibonacci : ";
    auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int
    {
        return n ? self(self, n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n");
#ifdef __cpp_explicit_this_parameter
    std::cout << "Approche C++23 pour la récursion lambda:\n";
    auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int
    {
         return n ? self(n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n");
#endif
}

Sortie possible :

c: 5 6 7
func1: 10
func2: 10
Émuler les appels de `lambda récursifs`:
Nombres de Fibonacci : 0, 1, 1, 2, 3, 5, 8, 13
Approche alternative pour la récursion lambda:
Nombres de Fibonacci : 0, 1, 1, 2, 3, 5, 8, 13

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement corrigé
CWG 974 C++11 l'argument par défaut n'était pas autorisé dans la
liste de paramètres d'une expression lambda
autorisé
CWG 1048
( N3638 )
C++11 le type de retour ne pouvait être déduit que pour les lambdas
contenant une seule instruction return
amélioration de la déduction
du type de retour
CWG 1249 C++11 il n'était pas clair si le membre capturé du lambda
englobant non mutable était considéré const ou non
considéré const
CWG 1557 C++11 la liaison linguistique du type de fonction retourné
par la fonction de conversion du type closure n'était pas spécifiée
elle a une liaison
linguistique C++
CWG 1607 C++11 les expressions lambda pouvaient apparaître dans
les signatures de fonctions et de modèles de fonctions
non autorisé
CWG 1612 C++11 les membres d'unions anonymes pouvaient être capturés non autorisé
CWG 1722 C++11 la fonction de conversion pour les lambdas sans capture
avait une spécification d'exception non spécifiée
la fonction de conversion
est noexcept
CWG 1772 C++11 la sémantique de __func__ dans le corps lambda n'était pas claire elle fait référence à l'opérateur()
de la classe closure
CWG 1780 C++14 il n'était pas clair si les membres des types closure des lambdas
génériques pouvaient être explicitement instanciés ou spécialisés
aucune des deux n'est autorisée
CWG 1891 C++11 le closure avait un constructeur par défaut supprimé
et des constructeurs de copie/déplacement implicites
pas de constructeur par défaut et
constructeurs de copie/déplacement par défaut
CWG 1937 C++11 concernant l'effet de l'invocation du résultat de la
fonction de conversion, il n'était pas spécifié sur quel
objet l'appel de son operator ( ) avait le même effet
sur une instance construite par défaut
du type closure
CWG 1973 C++11 la liste de paramètres de l' operator ( ) du type closure
pouvait faire référence à la liste de paramètres donnée dans trailing
ne peut faire référence
qu'à params
CWG 2011 C++11 pour une référence capturée par référence, il n'était pas spécifié
à quelle entité l'identifiant de la capture faisait référence
il fait référence à l'entité
référencée à l'origine
CWG 2095 C++11 le comportement de capture des références rvalue
vers des fonctions par copie n'était pas clair
clarifié
CWG 2211 C++11 le comportement n'était pas spécifié si une capture
avait le même nom qu'un paramètre
le programme est mal
formé dans ce cas
CWG 2358 C++14 les expressions lambda apparaissant dans les arguments par défaut
devaient être sans capture même si toutes les captures sont
initialisées avec des expressions pouvant apparaître en arguments par défaut
autoriser de telles expressions
lambda avec captures
CWG 2509 C++17 chaque spécificateur pouvait avoir plusieurs
occurrences dans la séquence de spécificateurs
chaque spécificateur ne peut
apparaître qu'au plus une fois dans
la séquence de spécificateurs
CWG 2561 C++23 un lambda avec paramètre objet explicite pouvait avoir
une fonction de conversion vers un type de pointeur de fonction non souhaité
il n'a pas une telle
fonction de conversion
CWG 2881 C++23 operator ( ) avec paramètre explicite pouvait être instancié pour
une classe dérivée quand l'héritage n'était pas public ou était ambigu
rendu mal formé
P0588R1 C++11 la règle pour la capture lambda implicite détectait l'utilisation odr la détection est simplifiée

Voir aussi

auto spécificateur (C++11) spécifie un type déduit d'une expression
(C++11)
wrapper copiable de tout objet appelable copiable
(modèle de classe)
wrapper non copiable de tout objet appelable qui supporte les qualificateurs dans une signature d'appel donnée
(modèle de classe)

Liens externes

Fonction imbriquée - une fonction définie à l'intérieur d'une autre fonction ( englobante ).