Lambda expressions (since C++11)
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) | |||||||
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
.
|
||||||||||||
| 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.
|
||||||||||||
| specs | - |
Une liste des spécificateurs suivants, chaque spécificateur étant autorisé au maximum une fois dans chaque séquence.
|
||||||||||||
| 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
|
(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. |
(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 : |
(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
.
- ↑ 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
>
|
(depuis C++14)
(jusqu'en C++17) |
|
|
template
<
template
-
params
>
using
fptr_t
=
/* voir ci-dessous */
;
template
<
template
-
params
>
|
(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 ( ) . |
(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 :
|
(depuis C++14)
(jusqu'en C++23) |
|
La valeur retournée par la fonction de conversion (modèle) est
|
(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. 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) | |||||||
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
- est une variable non locale ou possède une durée de stockage statique ou thread_local (auquel cas la variable ne peut pas être capturée), ou
- est une référence qui a été initialisée avec une expression constante .
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 :
- Sa portée la plus interne englobante est une portée de bloc .
- Il apparaît dans un initialiseur de membre par défaut , et sa portée la plus interne englobante est la portée de classe correspondante.
|
(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
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
m1capture par copie,m2capture le membre non statique du type de fermeture dem1, et non la variable originale ou * this ; sim1n'est pas mutable, le membre de données non statique est considéré comme qualifié const. -
si le lambda englobant
m1capture par référence,m2capture 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
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
|
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) |
|
(C++23)
|
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 ). |