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

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)
1) La expresión lambda con una lista de parámetros.
2-4) La expresión lambda sin una lista de parámetros.
2) La sintaxis más simple. back-attr no se puede aplicar.
3,4) back-attr solo puede aplicarse si alguno de specs o except está presente.

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 .

Si t-requires termina con una secuencia de especificadores de atributos, los atributos en la secuencia se tratan como atributos en front-attr .

(since C++23)
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.

Puede tener un parámetro de objeto explícito .

(since C++23)
specs - Una lista de los siguientes especificadores, cada especificador está permitido como máximo una vez en cada secuencia.
Especificador Efecto
mutable Permite que body modifique los objetos capturados por copia y llame a sus funciones miembro no constantes.
  • No se puede utilizar si está presente un parámetro de objeto explícito.
(desde C++23)
constexpr
(desde C++17)
Especifica explícitamente que operator ( ) es una función constexpr .
  • Si operator ( ) cumple con todos los requisitos de función constexpr, operator ( ) será constexpr incluso si constexpr no está presente.
consteval
(desde C++20)
Especifica que operator ( ) es una función inmediata .
  • consteval y constexpr no se pueden especificar al mismo tiempo.
static
(desde C++23)
Especifica que operator ( ) es una función miembro estática .
  • static y mutable no se pueden especificar al mismo tiempo.
  • No se puede utilizar si captures no está vacío, o si está presente un parámetro de objeto explícito.
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 auto se utiliza como tipo de un parámetro o se proporciona una lista de parámetros de plantilla explícita (desde C++20) , la lambda es una lambda genérica .

(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.

// 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
(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:

// 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)...);
};
(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 .

  1. 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 >

operator fptr_t < template - params > ( ) const noexcept ;
(desde C++14)
(hasta C++17)
template < template - params > using fptr_t = /* ver abajo */ ;

template < template - params >

constexpr operator fptr_t < template - params > ( ) const noexcept ;
(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 ( ) .

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); // error: no convertible
h(glambda);  // OK: llama a #1 ya que #2 no es convertible
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(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:

  • para lambdas no genéricas, invocar el operator ( ) del tipo de clausura en una instancia construida por defecto del tipo de clausura.
  • para lambdas genéricas, invocar la especialización correspondiente del operator ( ) de la lambda genérica en una instancia construida por defecto del tipo de clausura.
(desde C++14)
(hasta C++23)

El valor devuelto por la función de conversión (plantilla) es

  • si operator ( ) es estático, un puntero a ese operator ( ) con language linkage de C++,
  • en caso contrario, un puntero a una función con language linkage de C++ que, al ser invocada, tiene el mismo efecto que:
    • para lambdas no genéricas, invocar el operator ( ) del tipo de clausura en una instancia construida por defecto del tipo de clausura.
    • para lambdas genéricas, invocar la especialización correspondiente del operator ( ) de la lambda genérica en una instancia construida por defecto del tipo de clausura.
(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.

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 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)
1) captura simple por copia
2) captura simple por copia que es una pack expansion
3) captura por copia con un initializer
4) captura simple por referencia
5) captura simple por referencia que es una pack expansion
6) captura por referencia con un inicializador
7) captura simple por referencia del objeto actual
8) captura simple por copia del objeto actual
9) captura por copia con un inicializador que es una expansión de paquete
10) captura por referencia con un inicializador que es una expansión de paquete

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

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:

(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 auto y el mismo inicializador, cuya región declarativa es el cuerpo de la expresión lambda (es decir, no está en alcance dentro de su inicializador), excepto que:

  • si la captura es por-copia, el miembro de datos no estático introducido del objeto de cierre es otra forma de referirse a esa variable;
    • en otras palabras, la variable fuente no existe realmente, y la deducción de tipo mediante auto y la inicialización se aplican al miembro de datos no estático;
  • si la captura es por-referencia, el tiempo de vida de la variable de referencia termina cuando termina el tiempo de vida del objeto de cierre.

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 m1 captura por copia, m2 está capturando el miembro no estático del tipo de clausura de m1 , no la variable original o * this ; si m1 no es mutable, el miembro de datos no estático se considera calificado como const.
  • si la lambda envolvente m1 captura por referencia, m2 está 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

  • el tipo de cierre,
  • un tipo de clase derivado pública e inequívocamente del tipo de cierre, o
  • una referencia a un tipo de este tipo posiblemente calificado 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
}
(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 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 (desde C++20) , captura la entidad implícitamente si la entidad es

  • mencionada en una expresión potencialmente evaluada dentro de una expresión que depende de un parámetro de plantilla de una lambda genérica, o
(desde C++14)

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)
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 ).