Lambda expressions (since C++11)
Construye un closure (un objeto función anónimo capaz de capturar variables en el ámbito).
Contenidos |
Sintaxis
Expresiones lambda sin una lista de parámetros de plantilla explícita (posiblemente no genéricas)
[
capturas
]
atributos-frontales
(opcional)
(
parámetros
)
especificaciones
(opcional)
excepciones
(opcional)
atributos-traseros (opcional) calificador-final (opcional) requisitos (opcional) especificaciones-contrato (opcional)
{
cuerpo
}
|
(1) | ||||||||
[
capturas
] {
cuerpo
}
|
(2) | (hasta C++23) | |||||||
[
capturas
]
atributos-frontales
(opcional)
calificador-final
(opcional)
especificaciones-contrato
(opcional)
{
cuerpo
}
|
(2) | (desde C++23) | |||||||
[
capturas
]
atributos-frontales
(opcional)
excepciones
atributos-traseros (opcional) calificador-final (opcional) especificaciones-contrato (opcional)
{
cuerpo
}
|
(3) | (desde C++23) | |||||||
[
capturas
]
atributos-frontales
(opcional)
especificaciones
excepciones
(opcional)
atributos-traseros (opcional) calificador-final (opcional) especificaciones-contrato (opcional)
{
cuerpo
}
|
(4) | (desde C++23) | |||||||
Expresiones lambda con una lista de parámetros de plantilla explícita (siempre genéricas) (since C++20)
[
captures
] <
tparams
>
t-requires
(opcional)
front-attr (opcional)
(
params
)
specs
(opcional)
except
(opcional)
back-attr (opcional) trailing (opcional) requires (opcional) contract-specs (opcional)
{
body
}
|
(1) | ||||||||
[
captures
] <
tparams
>
t-requires
(opcional)
{
body
}
|
(2) | (hasta C++23) | |||||||
[
captures
] <
tparams
>
t-requires
(opcional)
front-attr (opcional) trailing (opcional) contract-specs (opcional)
{
body
}
|
(2) | (desde C++23) | |||||||
[
captures
] <
tparams
>
t-requires
(opcional)
front-attr
(opcional)
except
back-attr (opcional) trailing (opcional) contract-specs (opcional)
{
body
}
|
(3) | (desde C++23) | |||||||
[
captures
] <
tparams
>
t-requires
(opcional)
front-attr
(opcional)
specs
except
(opcional)
back-attr (opcional) trailing (opcional) contract-specs (opcional)
{
body
}
|
(4) | (desde C++23) | |||||||
Explicación
| captures | - | Especifica las entidades que deben ser capturadas . | ||||||||||||
| tparams | - |
Una lista no vacía separada por comas de
parámetros de plantilla
, utilizada para proporcionar nombres a los parámetros de plantilla de una lambda genérica (ver
ClosureType::operator()
abajo).
|
||||||||||||
| t-requires | - |
Añade
constraints
a
tparams
.
|
||||||||||||
| front-attr | - |
(since C++23)
Una
secuencia de especificadores de atributo
se aplica al
operator
(
)
del tipo de cierre (y por lo tanto el atributo
[[
noreturn
]]
puede ser utilizado).
|
||||||||||||
| params | - |
La
lista de parámetros
del
operator
(
)
del tipo de clausura.
|
||||||||||||
| specs | - |
Una lista de los siguientes especificadores, cada especificador está permitido como máximo una vez en cada secuencia.
|
||||||||||||
| except | - | Proporciona la especificación de excepción dinámica o (hasta C++20) el especificador noexcept para operator ( ) del tipo de cierre. | ||||||||||||
| back-attr | - |
Una
secuencia especificadora de atributos
se aplica al tipo de
operator
(
)
del tipo de cierre (y por lo tanto el atributo
[[
noreturn
]]
no puede ser utilizado).
|
||||||||||||
| trailing | - |
->
ret
, donde
ret
especifica el tipo de retorno.
|
||||||||||||
| requires | - | (since C++20) Añade constraints al operator ( ) del tipo de cierre. | ||||||||||||
| contract-specs | - | (since C++26) Una lista de especificadores de contrato de función para operator ( ) del tipo de cierre. | ||||||||||||
| body | - | El cuerpo de la función. |
|
Si
|
(desde C++14) |
Una variable __func__ está definida implícitamente al inicio del cuerpo , con semántica como se describe aquí .
Tipo de clausura
La expresión lambda es una expresión prvalue de tipo de clase único, sin nombre, no union , no aggregate , conocido como closure type , que se declara (para los propósitos de ADL ) en el ámbito de bloque, ámbito de clase o ámbito de namespace más pequeño que contiene la expresión lambda.
|
El tipo de cierre es un tipo estructural si y solo si captures está vacío. |
(since C++20) |
El tipo de clausura tiene los siguientes miembros, los cuales no pueden ser explícitamente instanciados , explícitamente especializados , o (desde C++14) nombrados en una declaración friend :
ClosureType:: operator()( params )
|
ret operator
(
)
(
params
)
{
body
}
|
(static y const pueden estar presentes, ver abajo) | |
|
template
<
template
-
params
>
ret operator ( ) ( params ) { body } |
(desde C++14)
(lambda genérica, static y const pueden estar presentes, ver abajo) |
|
Ejecuta el cuerpo de la expresión lambda, cuando se invoca. Al acceder a una variable, accede a su copia capturada (para las entidades capturadas por copia), o al objeto original (para las entidades capturadas por referencia).
La lista de parámetros de operator ( ) es params si se proporciona, de lo contrario la lista de parámetros está vacía.
El tipo de retorno de operator ( ) es el tipo especificado en trailing .
Si trailing no se proporciona, el tipo de retorno de operator ( ) se deduce automáticamente. [1]
A menos que se utilice la palabra clave mutable en los especificadores de lambda , o esté presente un parámetro de objeto explícito (desde C++23) , el calificador cv de operator ( ) es const y los objetos capturados por copia no son modificables desde dentro de este operator ( ) . No se permite el calificador const explícito. operator ( ) nunca es virtual y no puede tener el calificador volatile .
|
operator ( ) es siempre constexpr si cumple con los requisitos de una función constexpr . También es constexpr si se utilizó la palabra clave constexpr en los especificadores de lambda. |
(desde C++17) |
|
operator ( ) es una función inmediata si se utilizó la palabra clave consteval en los especificadores de lambda. |
(desde C++20) |
|
operator ( ) es una función miembro estática si se utilizó la palabra clave static en los especificadores de lambda. operator ( ) es una función miembro de objeto explícito si params contiene un parámetro de objeto explícito. |
(desde C++23) |
|
Para cada parámetro en params cuyo tipo está especificado como auto , se añade un parámetro de plantilla inventado a template-params , en orden de aparición. El parámetro de plantilla inventado puede ser un parameter pack si el correspondiente miembro de función de params es un function parameter pack. |
(since C++14) |
|
Si la definición lambda utiliza una lista de parámetros de plantilla explícita, esa lista de parámetros de plantilla se utiliza con operator ( ) . Para cada parámetro en params cuyo tipo se especifica como auto , se añade un parámetro de plantilla adicional inventado al final de esa lista de parámetros de plantilla: |
(desde C++20) |
La especificación de excepción except en la expresión lambda se aplica al operator ( ) .
Para el propósito de name lookup , determinar el tipo y valor del puntero this y para acceder a miembros no estáticos de la clase, el cuerpo del tipo de clausura operator ( ) se considera en el contexto de la expresión lambda.
struct X { int x, y; int operator()(int); void f() { // el contexto de la siguiente lambda es la función miembro X::f [=]() -> int { return operator()(this->x + y); // X::operator()(this->x + (*this).y) // this tiene tipo X* }; } };
Referencias colgantes
Si una entidad no referenciada es capturada por referencia, implícita o explícitamente, y operator ( ) del objeto de cierre es invocado después de que el tiempo de vida de la entidad ha terminado, ocurre un comportamiento indefinido. Los cierres de C++ no extienden los tiempos de vida de los objetos capturados por referencia.
Lo mismo se aplica al tiempo de vida del objeto actual
*
this
capturado mediante
this
.
- ↑ Aunque la deducción del tipo de retorno de funciones se introduce en C++14, su regla está disponible para la deducción del tipo de retorno de lambdas en C++11.
ClosureType:: operator ret (*)( params )()
|
lambda sin captura no genérica
|
||
|
using
F
=
ret
(
*
)
(
params
)
;
operator F ( ) const noexcept ; |
(hasta C++17) | |
|
using
F
=
ret
(
*
)
(
params
)
;
constexpr operator F ( ) const noexcept ; |
(desde C++17) | |
|
lambda sin captura genérica
|
||
|
template
<
template
-
params
>
using
fptr_t
=
/* ver abajo */
;
template
<
template
-
params
>
|
(desde C++14)
(hasta C++17) |
|
|
template
<
template
-
params
>
using
fptr_t
=
/* ver abajo */
;
template
<
template
-
params
>
|
(desde C++17) | |
Esta función de conversión definida por el usuario solo se define si la expresión lambda no tiene captures y no tiene parámetro de objeto explícito (since C++23) . Es una función miembro pública, constexpr, (since C++17) no virtual, no explícita, const noexcept del objeto de cierre.
|
Esta función es una función inmediata si el operador de llamada a función (o especialización, para lambdas genéricas) es una función inmediata. |
(desde C++20) |
|
Una lambda genérica sin capturas tiene una función de conversión definida por el usuario con la misma lista de parámetros de plantilla inventada que operator ( ) . |
(desde C++14) |
|
El valor devuelto por la función de conversión es un puntero a una función con language linkage de C++ que, al ser invocada, tiene el mismo efecto que invocar el operador de llamada a función del tipo de clausura en una instancia construida por defecto del tipo de clausura. |
(hasta C++14) |
|
El valor devuelto por la función de conversión (plantilla) es un puntero a una función con language linkage de C++ que, al ser invocada, tiene el mismo efecto que:
|
(desde C++14)
(hasta C++23) |
|
El valor devuelto por la función de conversión (plantilla) es
|
(desde C++23) |
|
Esta función es constexpr si el operador de llamada a función (o especialización, para lambdas genéricas) es constexpr. Si el objeto de cierre operator ( ) tiene una especificación de excepción no lanzadora, entonces el puntero devuelto por esta función tiene el tipo puntero a función noexcept. |
(desde C++17) |
ClosureType:: ClosureType()
|
ClosureType
(
)
=
default
;
|
(desde C++20)
(solo si no se especifican capturas) |
|
|
ClosureType
(
const
ClosureType
&
)
=
default
;
|
||
|
ClosureType
(
ClosureType
&&
)
=
default
;
|
||
|
Los tipos de clausura no son DefaultConstructible . Los tipos de clausura no tienen constructor por defecto. |
(hasta C++20) |
|
Si no se especifican capturas , el tipo de clausura tiene un constructor por defecto predeterminado. De lo contrario, no tiene constructor por defecto (esto incluye el caso cuando hay una captura por defecto , incluso si en realidad no captura nada). |
(desde C++20) |
El constructor de copia y el constructor de movimiento se declaran como predeterminados y pueden definirse implícitamente de acuerdo con las reglas habituales para constructores de copia y constructores de movimiento .
ClosureType:: operator=(const ClosureType&)
|
ClosureType
&
operator
=
(
const
ClosureType
&
)
=
delete
;
|
(hasta C++20) | |
|
ClosureType
&
operator
=
(
const
ClosureType
&
)
=
default
;
ClosureType & operator = ( ClosureType && ) = default ; |
(desde C++20)
(solo si no se especifican capturas) |
|
|
ClosureType
&
operator
=
(
const
ClosureType
&
)
=
delete
;
|
(desde C++20)
(en caso contrario) |
|
|
El operador de asignación de copia está definido como eliminado (y el operador de asignación de movimiento no está declarado). Los tipos de cierre no son CopyAssignable . |
(hasta C++20) |
|
Si no se especifican capturas , el tipo de cierre tiene un operador de asignación de copia predeterminado y un operador de asignación de movimiento predeterminado. En caso contrario, tiene un operador de asignación de copia eliminado (esto incluye el caso cuando hay una captura por defecto , incluso si no captura realmente nada). |
(desde C++20) |
ClosureType:: ~ClosureType()
|
~ClosureType
(
)
=
default
;
|
||
El destructor está declarado implícitamente.
ClosureType:: Captures
|
T1 a
;
T2 b
;
|
||
Si la expresión lambda captura algo por copia (ya sea implícitamente con la cláusula de captura
[=]
o explícitamente con una captura que no incluya el carácter &, por ejemplo
[a, b, c]
), el tipo de clausura incluye miembros de datos no estáticos sin nombre, declarados en un orden no especificado, que contienen copias de todas las entidades que fueron capturadas de esta manera.
Esos miembros de datos que corresponden a capturas sin inicializadores son inicializados directamente cuando se evalúa la expresión lambda. Aquellos que corresponden a capturas con inicializadores se inicializan según lo requiera el inicializador (podría ser inicialización por copia o directa). Si se captura un array, los elementos del array se inicializan directamente en orden creciente de índice. El orden en que se inicializan los miembros de datos es el orden en que se declaran (que no está especificado).
El tipo de cada miembro de datos es el tipo de la entidad capturada correspondiente, excepto si la entidad tiene tipo de referencia (en ese caso, las referencias a funciones se capturan como referencias de lvalue a las funciones referenciadas, y las referencias a objetos se capturan como copias de los objetos referenciados).
Para las entidades que se capturan por referencia (con el
capture-default
[&]
o cuando se usa el carácter &, por ejemplo
[&a, &b, &c]
), no está especificado si se declaran miembros de datos adicionales en el tipo de clausura
, pero cualquier miembro adicional de este tipo debe satisfacer
LiteralType
(desde C++17)
.
|
Las expresiones lambda no están permitidas en expresiones no evaluadas , argumentos de plantilla , declaraciones de alias , declaraciones typedef , y en cualquier parte de una declaración de función (o plantilla de función) excepto en el cuerpo de la función y los argumentos por defecto de la función. |
(hasta C++20) |
Captura de lambda
Las captures definen las variables externas que son accesibles desde dentro del cuerpo de la función lambda. Su sintaxis se define de la siguiente manera:
| capture-default | (1) | ||||||||
| capture-list | (2) | ||||||||
capture-default
,
capture-list
|
(3) | ||||||||
| capture-default | - |
uno de
&
y
=
|
| capture-list | - | una lista separada por comas de capture s |
La sintaxis de
capture
se define de la siguiente manera:
| identificador | (1) | ||||||||
identificador
...
|
(2) | ||||||||
| identificador inicializador | (3) | (desde C++14) | |||||||
&
identificador
|
(4) | ||||||||
&
identificador
...
|
(5) | ||||||||
&
identificador
inicializador
|
(6) | (desde C++14) | |||||||
this
|
(7) | ||||||||
*
this
|
(8) | (desde C++17) | |||||||
...
identificador
inicializador
|
(9) | (desde C++20) | |||||||
&
...
identificador
inicializador
|
(10) | (desde C++20) | |||||||
Si la
capture-default
es
&
, las capturas simples posteriores no deben comenzar con
&
.
struct S2 { void f(int i); }; void S2::f(int i) { [&] {}; // OK: captura por referencia por defecto [&, i] {}; // OK: captura por referencia, excepto i que se captura por copia [&, &i] {}; // Error: captura por referencia cuando por referencia es el valor por defecto [&, this] {}; // OK, equivalente a [&] [&, this, i] {}; // OK, equivalente a [&, i] }
Si la
captura-por-defecto
es
=
, las capturas simples posteriores deben comenzar con
&
o ser
*this
(desde C++17)
o
this
(desde C++20)
.
struct S2 { void f(int i); }; void S2::f(int i) { [=] {}; // OK: captura por copia por defecto [=, &i] {}; // OK: captura por copia, excepto i que se captura por referencia [=, *this] {}; // hasta C++17: Error: sintaxis inválida // desde C++17: OK: captura el S2 envolvente por copia [=, this] {}; // hasta C++20: Error: this cuando = es el valor por defecto // desde C++20: OK, igual que [=] }
Cualquier captura puede aparecer solo una vez, y su nombre debe ser diferente de cualquier nombre de parámetro:
struct S2 { void f(int i); }; void S2::f(int i) { [i, i] {}; // Error: i repetido [this, *this] {}; // Error: "this" repetido (C++17) [i] (int i) {}; // Error: parámetro y captura tienen el mismo nombre }
Una expresión lambda puede usar una variable sin capturarla si la variable
- es una variable no local o tiene duración de almacenamiento estática o local de hilo (en cuyo caso la variable no puede ser capturada), o
- es una referencia que ha sido inicializada con una expresión constante .
Una expresión lambda puede leer el valor de una variable sin capturarla si la variable
- tiene un tipo integral o de enumeración const no volátil y ha sido inicializado con una expresión constante , o
- es constexpr y no tiene miembros mutables.
El objeto actual (
*
this
) puede ser capturado implícitamente si existe un modo de captura por defecto. Si se captura implícitamente, siempre se captura por referencia, incluso si el modo de captura por defecto es
=
.
La captura implícita de
*
this
cuando el modo de captura por defecto es
=
está obsoleta.
(since C++20)
Solo las expresiones lambda que cumplan con alguna de las siguientes condiciones pueden tener un capture-default o capture sin inicializadores:
- Su ámbito más interno circundante es un ámbito de bloque .
- Aparece dentro de un inicializador de miembro predeterminado , y su ámbito más interno circundante es el ámbito de clase correspondiente.
|
(desde C++26) |
Para tal expresión lambda, el ámbito de alcance se define como el conjunto de ámbitos envolventes hasta e incluyendo la función envolvente más interna (y sus parámetros). Esto incluye ámbitos de bloques anidados y los ámbitos de lambdas envolventes si esta lambda está anidada.
El
identificador
en cualquier captura sin un inicializador (distinta de la captura
this
) se busca utilizando la
búsqueda de nombre no calificada
habitual en el
ámbito de alcance
de la lambda. El resultado de la búsqueda debe ser una
variable
con duración de almacenamiento automático declarada en el ámbito de alcance
, o un
enlace estructurado
cuya variable correspondiente cumpla con dichos requisitos
(desde C++20)
. La entidad es
explícitamente capturada
.
|
Una captura con un inicializador, llamada
init-capture
, actúa como si declarara y capturara explícitamente una variable declarada con el especificador de tipo
Esto se utiliza para capturar tipos que solo se pueden mover con una captura como x = std :: move ( x ) . Esto también hace posible capturar por referencia constante, con & cr = std:: as_const ( x ) o similar. int x = 4; auto y = [&r = x, x = x + 1]() -> int { r += 2; return x * x; }(); // actualiza ::x a 6 e inicializa y a 25. |
(desde C++14) |
Si captures tiene un capture-default y no captura explícitamente el objeto envolvente (como this o * this ), o una variable automática que es odr-usable en el cuerpo de la lambda , o un structured binding cuya variable correspondiente tiene duración de almacenamiento atómica (since C++20) , captura la entidad implícitamente si la entidad se nombra en una expresión potencialmente evaluada dentro de una expresión (incluyendo cuando el this - > implícito se añade antes de un uso de un miembro no estático de la clase).
Para el propósito de determinar capturas implícitas,
typeid
nunca se considera para hacer que sus operandos no sean evaluados.
|
Las entidades podrían ser capturadas implícitamente incluso si solo se nombran dentro de una sentencia descartada después de la instanciación del cuerpo 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: llama a #1, no captura x auto g0 = [](auto a) { f(x); }; // igual que arriba auto l1 = [=]{ f(x); }; // OK: captura x (desde P0588R1) y llama a #1 // la captura puede optimizarse auto g1 = [=](auto a) { f(x); }; // igual que arriba auto ltid = [=]{ typeid(x); }; // OK: captura x (desde P0588R1) // aunque x no se evalúa // la captura puede optimizarse auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2] = {}; f(x, selector); // OK: es una expresión dependiente, así que captura x }; auto g3 = [=](auto a) { typeid(a + x); // captura x independientemente de // si a + x es un operando no evaluado }; constexpr NoncopyableLiteralType w{42}; auto l4 = []{ return w.n_; }; // OK: w no se usa como odr, la captura es innecesaria // auto l5 = [=]{ return w.n_; }; // error: w necesita ser capturado por copia }
Si el cuerpo de una lambda odr-uses una entidad capturada por copia, se accede al miembro del tipo de clausura. Si no está odr-using la entidad, el acceso es al objeto original:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // no es un odr-use: se refiere al const int N de g f(&N); // odr-use: provoca que N sea capturado (por copia) // &N es la dirección del miembro N del objeto de clausura, no del N de g }(); }
Si una lambda utiliza odr-una referencia que es capturada por referencia, está utilizando el objeto referido por la referencia original, no la referencia capturada en sí misma:
#include <iostream> auto make_function(int& x) { return [&] { std::cout << x << '\n'; }; } int main() { int i = 3; auto f = make_function(i); // el uso de x en f se vincula directamente a i i = 5; f(); // OK: imprime 5 }
Dentro del cuerpo de una lambda con captura por defecto
=
, el tipo de cualquier entidad capturable es como si fuera capturada (y por lo tanto a menudo se añade calificación const si la lambda no es
mutable
), incluso si la entidad está en un operando no evaluado y no es capturada (por ejemplo, en
decltype
):
void f3() { float x, &r = x; [=] { // x y r no son capturadas (la aparición en un operando decltype no es un odr-use) decltype(x) y1; // y1 tiene tipo float decltype((x)) y2 = y1; // y2 tiene tipo float const& porque este lambda // no es mutable y x es un lvalue decltype(r) r1 = y1; // r1 tiene tipo float& (transformación no considerada) decltype((r)) r2 = y2; // r2 tiene tipo float const& }; }
Cualquier entidad capturada por un lambda (implícitamente o explícitamente) es odr-used por la expresión lambda (por lo tanto, la captura implícita por un lambda anidado activa la captura implícita en el lambda que lo engloba).
Todas las variables capturadas implícitamente deben declararse dentro del alcance de accesibilidad de la lambda.
Si una lambda captura el objeto envolvente (como this o * this ), la función envolvente más cercana debe ser una función miembro no estática o la lambda debe estar en un inicializador de miembro predeterminado :
struct s2 { double ohseven = .007; auto f() // función envolvente más cercana para los siguientes dos lambdas { return [this] // capturar el s2 envolvente por referencia { return [*this] // capturar el s2 envolvente por copia (C++17) { return ohseven; // OK } }(); } auto g() { return [] // no capturar nada { return [*this] {}; // error: *this no capturado por la expresión lambda externa }(); } };
Si una expresión lambda (o una especialización del operador de llamada a función de una lambda genérica) (desde C++14) utiliza ODR * this o cualquier variable con duración de almacenamiento automático, debe ser capturada por la expresión lambda.
void f1(int i) { int const N = 20; auto m1 = [=] { int const M = 30; auto m2 = [i] { int x[N][M]; // N y M no son odr-usadas // (está bien que no sean capturadas) x[0][0] = i; // i es capturada explícitamente por m2 // e implícitamente capturada por m1 }; }; struct s1 // clase local dentro de f1() { int f; void work(int n) // función miembro no estática { int m = n * n; int j = 40; auto m3 = [this, m] { auto m4 = [&, j] // error: j no es capturada por m3 { int x = n; // error: n es capturada implícitamente por m4 // pero no es capturada por m3 x += m; // OK: m es capturada implícitamente por m4 // y explícitamente capturada por m3 x += i; // error: i está fuera del alcance de captura // (que termina en work()) x += f; // OK: this es capturado implícitamente por m4 // y explícitamente capturado por m3 }; }; } }; }
Los miembros de clase no pueden capturarse explícitamente mediante una captura sin inicializador (como se mencionó anteriormente, solo se permiten variables en la capture-list ):
class S { int x = 0; void f() { int i = 0; // auto l1 = [i, x] { use(i, x); }; // error: x no es una variable auto l2 = [i, x = x] { use(i, x); }; // OK, captura por copia i = 1; x = 1; l2(); // llama a use(0,0) auto l3 = [i, &x = x] { use(i, x); }; // OK, captura por referencia i = 2; x = 2; l3(); // llama a use(1,2) } };
Cuando una lambda captura un miembro mediante captura implícita por copia, no realiza una copia de esa variable miembro: el uso de una variable miembro
m
se trata como una expresión
(
*
this
)
.
m
, y
*
this
siempre se captura implícitamente por referencia:
class S { int x = 0; void f() { int i = 0; auto l1 = [=] { use(i, x); }; // captura una copia de i y // una copia del puntero this i = 1; x = 1; l1(); // llama a use(0, 1), como si // i fuera por copia y x por referencia auto l2 = [i, this] { use(i, x); }; // igual que arriba, hecho explícito i = 2; x = 2; l2(); // llama a use(1, 2), como si // i fuera por copia y x por referencia auto l3 = [&] { use(i, x); }; // captura i por referencia y // una copia del puntero this i = 3; x = 2; l3(); // llama a use(3, 2), como si // tanto i como x fueran por referencia auto l4 = [i, *this] { use(i, x); }; // hace una copia de *this, // incluyendo una copia de x i = 4; x = 4; l4(); // llama a use(3, 2), como si // tanto i como x fueran por copia } };
Si una expresión lambda aparece en un argumento por defecto , no puede capturar nada explícita o implícitamente , a menos que todas las capturas tengan inicializadores que satisfagan las restricciones de una expresión que aparece en un argumento por defecto (desde C++14) :
void f2() { int i = 1; void g1( int = [i] { return i; }() ); // error: captura algo void g2( int = [i] { return 0; }() ); // error: captura algo void g3( int = [=] { return i; }() ); // error: captura algo void g4( int = [=] { return 0; }() ); // OK: sin captura void g5( int = [] { return sizeof i; }() ); // OK: sin captura // C++14 void g6( int = [x = 1] { return x; }() ); // OK: 1 puede aparecer // en un argumento por defecto void g7( int = [x = i] { return x; }() ); // error: i no puede aparecer // en un argumento por defecto }
Los miembros de anonymous unions no pueden ser capturados. Los bit-fields solo pueden ser capturados por copia.
Si una lambda anidada
m2
captura algo que también es capturado por la lambda envolvente inmediata
m1
, entonces la captura de
m2
se transforma de la siguiente manera:
-
si la lambda envolvente
m1captura por copia,m2está capturando el miembro no estático del tipo de clausura dem1, no la variable original o * this ; sim1no es mutable, el miembro de datos no estático se considera calificado como const. -
si la lambda envolvente
m1captura por referencia,m2está capturando la variable original o * 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(); // llama a m2() e imprime 123 std::cout << a << b << c << '\n'; // imprime 234 }
|
Si una lambda captura algo, el tipo del parámetro de objeto explícito (si existe) del operador de llamada a función solo puede ser
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 } |
(desde C++23) |
Notas
| Macro de prueba de características | Valor | Std | Característica |
|---|---|---|---|
__cpp_lambdas
|
200907L
|
(C++11) | Expresiones lambda |
__cpp_generic_lambdas
|
201304L
|
(C++14) | Expresiones lambda genéricas |
201707L
|
(C++20) | Lista de parámetros de plantilla explícita para lambdas genéricas | |
__cpp_init_captures
|
201304L
|
(C++14) | Captura de inicialización lambda |
201803L
|
(C++20) | Permitir expansión de paquete en captura de inicialización lambda | |
__cpp_capture_star_this
|
201603L
|
(C++17) | Captura lambda de * this por valor como [ = , * this ] |
__cpp_constexpr
|
201603L
|
(C++17) | Lambda constexpr |
__cpp_static_call_operator
|
202207L
|
(C++23) | static operator ( ) para lambdas sin captura |
La regla para la captura implícita de lambdas se modifica ligeramente por el informe de defectos P0588R1 . A partir de octubre de 2023, algunas implementaciones principales no han implementado completamente el DR, y por lo tanto la regla anterior, que detecta odr-use , todavía se utiliza en algunos casos.
| Regla anterior a P0588R1 | ||
|---|---|---|
|
Si
captures
tiene una
captura por defecto
y no captura explícitamente el objeto envolvente (como
|
Ejemplo
Este ejemplo muestra cómo pasar una lambda a un algoritmo genérico y cómo los objetos resultantes de una expresión lambda pueden almacenarse en std::function objetos.
#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'; // el tipo de una clausura no puede ser nombrado, pero puede ser inferido con auto // desde C++14, lambda puede tener argumentos predeterminados auto func1 = [](int i = 6) { return i + 4; }; std::cout << "func1: " << func1() << '\n'; // como todos los objetos invocables, los closures pueden capturarse en std::function // (esto puede generar una sobrecarga innecesaria) std::function<int(int)> func2 = [](int i) { return i + 4; }; std::cout << "func2: " << func2(6) << '\n'; constexpr int fib_max {8}; std::cout << "Emular llamadas `recursive lambda`:\nNúmeros 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 << "Enfoque alternativo para la recursión lambda:\nNúmeros 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 << "Enfoque de C++23 para la recursión de lambdas:\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 }
Salida posible:
c: 5 6 7 func1: 10 func2: 10 Emular llamadas `recursive lambda`: Números de Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13 Enfoque alternativo para recursión lambda: Números de Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 974 | C++11 |
no se permitía el argumento por defecto en la
lista de parámetros de una expresión lambda |
permitido |
|
CWG 1048
( N3638 ) |
C++11 |
el tipo de retorno solo podía deducirse para lambdas
con cuerpos que contenían solo una sentencia return |
se mejoró la deducción
del tipo de retorno |
| CWG 1249 | C++11 |
no estaba claro si el miembro capturado de la
lambda no mutable envolvente se considera const o no |
se considera const |
| CWG 1557 | C++11 |
no se especificaba el linkage de lenguaje del tipo de función retornado
por la función de conversión del tipo closure |
tiene linkage de lenguaje
C++ |
| CWG 1607 | C++11 |
las expresiones lambda podían aparecer en
firmas de funciones y plantillas de funciones |
no permitido |
| CWG 1612 | C++11 | se podían capturar miembros de uniones anónimas | no permitido |
| CWG 1722 | C++11 |
la función de conversión para lambdas sin captura
tenía especificación de excepciones no especificada |
la función de conversión
es noexcept |
| CWG 1772 | C++11 | la semántica de __func__ en el cuerpo lambda no estaba clara |
se refiere al operator()
de la clase closure |
| CWG 1780 | C++14 |
no estaba claro si los miembros de los tipos closure de lambdas
genéricas pueden instanciarse o especializarse explícitamente |
ninguna está permitida |
| CWG 1891 | C++11 |
el closure tenía un constructor por defecto eliminado
y constructores de copia/movimiento implícitos |
sin constructor por defecto y con
constructores de copia/movimiento por defecto |
| CWG 1937 | C++11 |
respecto al efecto de invocar el resultado de la
función de conversión, no se especificaba sobre qué objeto llamar a su operator ( ) tiene el mismo efecto |
sobre una instancia construida por
defecto del tipo closure |
| CWG 1973 | C++11 |
la lista de parámetros del
operator
(
)
del tipo closure
podía referirse a la lista de parámetros dada en trailing |
solo puede referirse
a params |
| CWG 2011 | C++11 |
para una referencia capturada por referencia, no se especificaba
a qué entidad se refiere el identificador de la captura |
se refiere a la entidad
originalmente referenciada |
| CWG 2095 | C++11 |
el comportamiento de capturar referencias a rvalue
a funciones por copia no estaba claro |
se aclaró |
| CWG 2211 | C++11 |
el comportamiento no estaba especificado si una captura
tiene el mismo nombre que un parámetro |
el programa está mal
formado en este caso |
| CWG 2358 | C++14 |
las expresiones lambda que aparecen en argumentos por defecto
tenían que ser sin captura incluso si todas las capturas se inicializan con expresiones que pueden aparecer en argumentos por defecto |
permitir tales expresiones
lambda con capturas |
| CWG 2509 | C++17 |
cada especificador podía tener múltiples
ocurrencias en la secuencia de especificadores |
cada especificador solo puede
aparecer como máximo una vez en la secuencia de especificadores |
| CWG 2561 | C++23 |
una lambda con parámetro de objeto explícito podría tener una
función de conversión a un tipo de puntero a función no deseado |
no tiene tal función
de conversión |
| CWG 2881 | C++23 |
operator
(
)
con parámetro explícito podría instanciarse para
una clase derivada cuando la herencia no era pública o era ambigua |
se hace mal formado |
| P0588R1 | C++11 | la regla para captura lambda implícita detectaba uso odr | la detección se simplifica |
Véase también
auto
especificador
(C++11)
|
especifica un tipo deducido de una expresión |
|
(C++11)
|
envoltorio copiable de cualquier objeto invocable copiable
(plantilla de clase) |
|
(C++23)
|
envoltorio no copiable de cualquier objeto invocable que soporta calificadores en una firma de llamada dada
(plantilla de clase) |
Enlaces externos
| Función anidada - una función que se define dentro de otra función ( envolvente ). |