Buscar..


Sintaxis

  • [ captura por defecto , lista de captura ] ( lista de argumentos ) atributos de especificación de lanzamiento mutable -> return-type { lambda-body } // Orden de los especificadores y atributos de lambda.
  • [ lista de captura ] ( lista de argumentos ) { lambda-cuerpo } // Definición común de lambda.
  • [=] ( argument-list ) { lambda-body } // Captura todas las variables locales necesarias por valor.
  • [&] ( argument-list ) { lambda-body } // Captura todas las variables locales necesarias por referencia.
  • [ capture-list ] { lambda-body } // Se pueden omitir la lista de argumentos y los especificadores.

Parámetros

Parámetro Detalles
captura por defecto Especifica cómo se capturan todas las variables no listadas. Puede ser = (captura por valor) o & (captura por referencia). Si se omite, las variables no listadas son inaccesibles dentro del cuerpo lambda . La captura por defecto debe preceder a la lista de captura .
lista de captura Especifica cómo las variables locales se hacen accesibles dentro del cuerpo lambda . Las variables sin prefijo son capturadas por valor. Las variables con el prefijo & son capturadas por referencia. Dentro de un método de clase, this se puede usar para hacer que todos sus miembros sean accesibles por referencia. Las variables no listadas son inaccesibles, a menos que la lista esté precedida por una captura predeterminada .
lista de argumentos Especifica los argumentos de la función lambda.
mudable (Opcional) Normalmente las variables capturadas por valor son const . Especificando mutable hace no const. Los cambios en esas variables se mantienen entre las llamadas.
especificaciones de lanzamiento (opcional) Especifica el comportamiento de lanzamiento de la excepción de la función lambda. Por ejemplo: noexcept o throw(std::exception) .
atributos (Opcional) Cualquier atributo para la función lambda. Por ejemplo, si el lambda-body siempre lanza una excepción, se puede usar [[noreturn]] .
-> tipo de retorno (opcional) Especifica el tipo de retorno de la función lambda. Se requiere cuando el compilador no puede determinar el tipo de retorno.
cuerpo lambda Un bloque de código que contiene la implementación de la función lambda.

Observaciones

C ++ 17 (el borrador actual) introduce constexpr lambdas, básicamente lambdas que pueden evaluarse en tiempo de compilación. Un lambda es automáticamente constexpr si satisface los requisitos de constexpr , pero también puede especificarlo usando la palabra clave constexpr :

//Explicitly define this lambdas as constexpr
[]() constexpr {
    //Do stuff
}

¿Qué es una expresión lambda?

Una expresión lambda proporciona una forma concisa de crear objetos de funciones simples. Una expresión lambda es un prvalue cuyo objeto de resultado se llama objeto de cierre , que se comporta como un objeto de función.

El nombre 'expresión lambda' se origina en el cálculo lambda , que es un formalismo matemático inventado en la década de 1930 por Alonzo Church para investigar cuestiones sobre lógica y computabilidad. El cálculo lambda formó la base de LISP , un lenguaje de programación funcional. En comparación con el cálculo lambda y LISP, las expresiones lambda de C ++ comparten las propiedades de no tener nombre y capturan variables del contexto circundante, pero carecen de la capacidad de operar y devolver funciones.

Una expresión lambda se usa a menudo como un argumento para funciones que toman un objeto llamable. Eso puede ser más simple que crear una función nombrada, que solo se usaría cuando se pase como argumento. En tales casos, las expresiones lambda generalmente se prefieren porque permiten definir los objetos de función en línea.

Una lambda consta normalmente de tres partes: una lista de captura [] , una lista de parámetros opcional () y un cuerpo {} , todos los cuales pueden estar vacíos:

[](){}                // An empty lambda, which does and returns nothing

Lista de captura

[] es la lista de captura . Por defecto, no se puede acceder a las variables del ámbito adjunto por un lambda. Capturar una variable lo hace accesible dentro de la lambda, ya sea como una copia o como una referencia . Las variables capturadas se convierten en parte de la lambda; en contraste con los argumentos de la función, no se tienen que pasar al llamar a lambda.

int a = 0;                       // Define an integer variable
auto f = []()   { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]()  { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
                                 //      Note: It is the responsibility of the programmer
                                 //      to ensure that a is not destroyed before the
                                 //      lambda is called.
auto b = f();                    // Call the lambda function. a is taken from the capture list and not passed here.

Lista de parámetros

() es la lista de parámetros , que es casi la misma que en las funciones normales. Si la lambda no toma argumentos, estos paréntesis se pueden omitir (excepto si necesita declarar la lambda mutable ). Estas dos lambdas son equivalentes:

auto call_foo  = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
C ++ 14

La lista de parámetros puede usar el tipo de marcador de posición auto lugar de los tipos reales. Al hacerlo, este argumento se comporta como un parámetro de plantilla de una plantilla de función. Las siguientes lambdas son equivalentes cuando desea ordenar un vector en código genérico:

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; }; 
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; }; 

Cuerpo de funcion

{} es el cuerpo , que es el mismo que en las funciones regulares.


Llamando a un lambda

El objeto de resultado de una expresión lambda es un cierre , que se puede llamar usando el operator() (como con otros objetos de función):

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::out << timesFive(2); // Prints 10

multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10

Tipo de retorno

De forma predeterminada, se deduce el tipo de retorno de una expresión lambda.

[](){ return true; };

En este caso el tipo de retorno es bool .

También puede especificar manualmente el tipo de retorno usando la siguiente sintaxis:

[]() -> bool { return true; };

Lambda mutable

Los objetos capturados por valor en la lambda son, por defecto, inmutables. Esto se debe a que el operator() del objeto de cierre generado es const de forma predeterminada.

auto func = [c = 0](){++c; std::cout << c;};  // fails to compile because ++c
                                              // tries to mutate the state of
                                              // the lambda.

Se puede permitir la modificación utilizando la palabra clave mutable , que hace que el operator() del objeto más cercano no sea const :

auto func = [c = 0]() mutable {++c; std::cout << c;};

Si se usa junto con el tipo de retorno, mutable viene antes.

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

Un ejemplo para ilustrar la utilidad de las lambdas.

Antes de C ++ 11:

C ++ 11
// Generic functor used for comparison
struct islessthan
{
    islessthan(int threshold) : _threshold(threshold) {}

    bool operator()(int value) const
    {
        return value < _threshold;
    }
private:
    int _threshold;
};

// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Desde C ++ 11:

C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

Especificando el tipo de retorno

Para las lambdas con una sola declaración de retorno, o múltiples declaraciones de retorno cuyas expresiones son del mismo tipo, el compilador puede deducir el tipo de retorno:

// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
    return value > 10;
}

Para las lambdas con múltiples declaraciones de retorno de diferentes tipos, el compilador no puede deducir el tipo de devolución:

// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

En este caso, debe especificar el tipo de retorno explícitamente:

// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

Las reglas para esto coinciden con las reglas para auto deducción de auto tipo. Lambdas sin tipos de retorno explícitamente especificados nunca devuelve referencias, por lo que si se desea un tipo de referencia, también debe especificarse explícitamente:

auto copy = [](X& x) { return x; };       // 'copy' returns an X, so copies its input
auto ref  = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

Captura por valor

Si especifica el nombre de la variable en la lista de captura, lambda la capturará por valor. Esto significa que el tipo de cierre generado para la lambda almacena una copia de la variable. Esto también requiere que el tipo de variable sea construible por copia :

int a = 0;

[a]() {
    return a;   // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);

[p]() {         // Compile error; `unique_ptr` is not copy-constructible
    return p->createWidget(); 
};

A partir de C ++ 14, es posible inicializar variables en el lugar. Esto permite mover solo tipos para ser capturados en la lambda.

C ++ 14
auto p = std::make_unique<T>(...);

[p = std::move(p)]() {
    return p->createWidget(); 
};

Aunque una lambda captura variables por valor cuando se les da su nombre, tales variables no pueden modificarse dentro del cuerpo lambda por defecto. Esto se debe a que el tipo de cierre coloca el cuerpo lambda en una declaración de operator() const .

La const aplica a los accesos a las variables miembro del tipo de cierre, y las variables capturadas que son miembros del cierre (todas las apariencias de lo contrario):

int a = 0;

[a]() {
    a = 2;      // Illegal, 'a' is accessed via `const`

    decltype(a) a1 = 1; 
    a1 = 2; // valid: variable 'a1' is not const
};

Para eliminar la const , debe especificar la palabra clave mutable en la lambda:

int a = 0;

[a]() mutable {
    a = 2;      // OK, 'a' can be modified
    return a;
};

Debido a que a se capturó por valor, cualquier modificación realizada al llamar a la lambda no afectará a . El valor de a se copió en la lambda cuando se construyó, por lo que la copia de a de la lambda está separada de la variable externa a .

int a = 5 ; 
auto plus5Val = [a] (void) { return a + 5 ; } ; 
auto plus5Ref = [&a] (void) {return a + 5 ; } ; 

a = 7 ; 
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"

Captura generalizada

C ++ 14

Lambdas puede capturar expresiones, en lugar de solo variables. Esto permite que las lambdas almacenen tipos de solo movimiento:

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
  p->SomeFunc();
};

Esto mueve la variable p externa a la variable de captura lambda, también llamada p . lamb ahora posee la memoria asignada por make_unique . Debido a que el cierre contiene un tipo que no se puede copiar, esto significa que el lamb no puede copiarse. Pero se puede mover:

auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.

Ahora lamb_move posee la memoria.


Tenga en cuenta que std::function<> requiere que los valores almacenados sean copiables. Puedes escribir tu propia std::function , que requiere solo movimiento , o simplemente puedes meter el lambda en un contenedor shared_ptr :

auto shared_lambda = [](auto&& f){
  return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
  (auto&&...args)->decltype(auto) {
    return (*spf)(decltype(args)(args)...);
  };
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

toma nuestro lambda de solo movimiento y rellena su estado en un puntero compartido y luego devuelve un lambda que se puede copiar y luego almacenar en una std::function o similar.


La captura generalizada utiliza auto deducción de tipo auto para el tipo de variable. Declarará estas capturas como valores por defecto, pero también pueden ser referencias:

int a = 0;

auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
  v += add; //Modifies `a`
};

lamb(20); //`a` becomes 20.

Generalize capture no necesita capturar una variable externa en absoluto. Puede capturar una expresión arbitraria:

auto lamb = [p = std::make_unique<T>(...)]()
{
    p->SomeFunc();
}

Esto es útil para dar a los lambdas valores arbitrarios que pueden mantener y potencialmente modificar, sin tener que declararlos externamente a la lambda. Por supuesto, eso solo es útil si no tiene la intención de acceder a esas variables después de que la lambda haya completado su trabajo.

Captura por referencia

Si precede el nombre de una variable local con un & , entonces la variable se capturará por referencia. Conceptualmente, esto significa que el tipo de cierre de la lambda tendrá una variable de referencia, inicializada como una referencia a la variable correspondiente desde fuera del alcance de la lambda. Cualquier uso de la variable en el cuerpo lambda se referirá a la variable original:

// Declare variable 'a'
int a = 0;

// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
    a = 1;
};

set();
assert(a == 1);

La palabra clave mutable no es necesaria, porque a sí mismo no es const .

Por supuesto, capturar por referencia significa que la lambda no debe escapar al alcance de las variables que captura. Por lo tanto, puede llamar a funciones que toman una función, pero no debe llamar a una función que almacenará el lambda más allá del alcance de sus referencias. Y no debes devolver la lambda.

Captura por defecto

De forma predeterminada, no se puede acceder a las variables locales que no se especifican explícitamente en la lista de captura desde dentro del cuerpo lambda. Sin embargo, es posible capturar implícitamente variables nombradas por el cuerpo lambda:

int a = 1;
int b = 2;

// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value

// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference

La captura explícita todavía se puede hacer junto con la captura implícita por defecto. La definición de captura explícita anulará la captura predeterminada:

int a = 0;
int b = 1;

[=, &b]() {
    a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
    b = 2; // OK; 'b' is captured by reference
};

Lambdas genericas

c ++ 14

Las funciones Lambda pueden tomar argumentos de tipos arbitrarios. Esto permite que un lambda sea más genérico:

auto twice = [](auto x){ return x+x; };

int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"

Esto se implementa en C ++ al hacer que el operator() de tipo de cierre operator() sobrecargue una función de plantilla. El siguiente tipo tiene un comportamiento equivalente al cierre lambda anterior:

struct _unique_lambda_type
{
  template<typename T>
  auto operator() (T x) const {return x + x;}
};

No todos los parámetros en un lambda genérico necesitan ser genéricos:

[](auto x, int y) {return x + y;}

Aquí, x se deduce en función del primer argumento de la función, mientras que y siempre será int .

Las lambdas genéricas también pueden tomar argumentos por referencia, usando las reglas habituales para auto y & . Si un parámetro genérico se toma como auto&& , esta es una referencia de reenvío al argumento pasado y no una referencia de rvalue :

auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.

Las funciones Lambda pueden ser variadas y perfectamente remiten sus argumentos:

auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};

o:

auto lam = [](auto&&... args){return f(decltype(args)(args)...);};

que solo funciona "correctamente" con variables de tipo auto&& .

Una razón importante para usar lambdas genéricas es para visitar la sintaxis.

boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
  std::cout << e;
});

Aquí estamos visitando de manera polimórfica; pero en otros contextos, los nombres del tipo que estamos pasando no son interesantes:

mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
  os << "hello world\n";
});

Repitiendo el tipo de std::ostream& is noise here; Sería como tener que mencionar el tipo de variable cada vez que la uses. Aquí estamos creando un visitante, pero no polimórfico; auto se utiliza por el mismo motivo por el que puede usar auto en un bucle for(:) .

Conversión a puntero de función.

Si la lista de capturas de una lambda está vacía, entonces la lambda tiene una conversión implícita a un puntero de función que toma los mismos argumentos y devuelve el mismo tipo de retorno:

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion

Dicha conversión también se puede hacer cumplir con el operador unario plus:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

Llamar a este puntero de función se comporta exactamente como invocar operator() en la lambda. Este puntero de función no depende en modo alguno de la existencia del cierre lambda de origen. Por lo tanto, puede sobrevivir el cierre lambda.

Esta característica es principalmente útil para usar lambdas con API que tratan con punteros de función, en lugar de objetos de función C ++.

C ++ 14

La conversión a un puntero de función también es posible para las lambdas genéricas con una lista de captura vacía. Si es necesario, la deducción de argumentos de la plantilla se utilizará para seleccionar la especialización correcta.

auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter;  // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;

Clase lambdas y captura de esta.

Una expresión lambda evaluada en la función miembro de una clase es implícitamente un amigo de esa clase:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    // definition of a member function
    void Test()
    {
        auto lamb = [](Foo &foo, int val)
        {
            // modification of a private member variable
            foo.i = val;
        };
        
        // lamb is allowed to access a private member, because it is a friend of Foo
        lamb(*this, 30);
    }
};

Tal lambda no es solo un amigo de esa clase, sino que tiene el mismo acceso que la clase en la que está declarada.

Lambdas puede capturar this puntero que representa la instancia de objeto en la que se activó la función externa. Esto se hace agregando this a la lista de captura:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture the this pointer by value
        auto lamb = [this](int val)
        {
            i = val;
        };
        
        lamb(30);
    }
};

Cuando this es capturado, la lambda puede utilizar nombres de los miembros de su clase que contiene como si se tratara de su clase que contiene. Así que un implícito this-> se aplica a tales miembros.

Tenga en cuenta que this se captura por valor, pero no por el valor del tipo. Es capturado por el valor de this , que es un puntero . Como tal, la lambda no posee this . Si el lambda out vive el tiempo de vida del objeto que lo creó, el lambda puede dejar de ser válido.

Esto también significa que la lambda puede modificar this sin ser declarado mutable . Es el puntero el que es const , no el objeto al que se apunta. Es decir, a menos que la función miembro externa fuera en sí misma una función const .

Además, tenga en cuenta que las cláusulas de captura predeterminadas, tanto [=] como [&] , también capturarán this implícitamente. Y ambos lo capturan por el valor del puntero. De hecho, es un error especificar this en la lista de captura cuando se da un valor predeterminado.

C ++ 17

Lambdas puede capturar una copia de this objeto, creado en el momento en que se crea la lambda. Esto se hace agregando *this a la lista de captura:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture a copy of the object given by the this pointer
        auto lamb = [*this](int val) mutable
        {
            i = val;
        };
        
        lamb(30); // does not change this->i
    }
};

Portar funciones lambda a C ++ 03 usando functores

Las funciones de Lambda en C ++ son azúcar sintáctica que proporcionan una sintaxis muy concisa para escribir functores . Como tal, se puede obtener una funcionalidad equivalente en C ++ 03 (aunque mucho más detallado) al convertir la función lambda en un functor:

// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
  // Use auto because the type of the lambda is unknown.
  auto lambda = [val, &ref](int arg1, int arg2) -> R {
    /* lambda-body */
    return R();
  };
  return lambda(12, 27);
}

// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
  // Capture list.
  T1 val;
  T2& ref;

public:
  // Constructor
  inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}

  // Functor body
  R operator()(int arg1, int arg2) const {
    /* lambda-body */
    return R();
  }
};

// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
  Functor functor(val, ref);
  return functor(12, 27);
}

// Make this a self-contained example.
int main() {
  T1 t1;
  T2 t2;
  use_functor(t1,t2);
  use_lambda(t1,t2);
  return 0;
}

Si la función lambda es mutable entonces haga que el operador de llamadas del funtor no sea constante, es decir:

R operator()(int arg1, int arg2) /*non-const*/ {
  /* lambda-body */
  return R();
}

Lambdas recursivas

Digamos que deseamos escribir el gcd() de Euclid gcd() como un lambda. Como una función, es:

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a%b);
}

Pero un lambda no puede ser recursivo, no tiene forma de invocarse. Un lambda no tiene nombre y usar this dentro del cuerpo de un lambda se refiere a un this capturado (asumiendo que el lambda se crea en el cuerpo de una función miembro, de lo contrario es un error). Entonces, ¿cómo resolvemos este problema?

Usa std::function

Podemos tener una captura lambda de una referencia a una std::function aún no construida:

std::function<int(int, int)> gcd = [&](int a, int b){
    return b == 0 ? a : gcd(b, a%b);
};

Esto funciona, pero debe usarse con moderación. Es lento (estamos usando el borrado de tipo ahora en lugar de una llamada de función directa), es frágil (copiar gcd o devolver gcd se romperá ya que la lambda se refiere al objeto original), y no funcionará con lambdas genéricos.

Utilizando dos punteros inteligentes:

auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
  [gcd_self](int a, int b){
    return b == 0 ? a : (**gcd_self)(b, a%b);
  };
};

Esto agrega mucha indirección (que es una sobrecarga), pero se puede copiar / devolver, y todas las copias comparten el estado. Le permite devolver el lambda y, por lo demás, es menos frágil que la solución anterior.

Usa un combinador en Y

Con la ayuda de una estructura de utilidad corta, podemos resolver todos estos problemas:

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)

podemos implementar nuestro gcd como:

auto gcd = make_y_combinator(
  [](auto&& gcd, int a, int b){
    return b == 0 ? a : gcd(b, a%b);
  }
);

El y_combinator es un concepto del cálculo lambda que te permite tener una recursión sin poder y_combinator hasta que estés definido. Este es exactamente el problema que tienen las lambdas.

Crea un lambda que toma "recurse" como su primer argumento. Cuando quieres recursionar, pasas los argumentos para recursionar.

El y_combinator luego devuelve un objeto de función que llama a esa función con sus argumentos, pero con un objeto "recurse" adecuado (es decir, el propio y_combinator ) como su primer argumento. Reenvía el resto de los argumentos con los que llama al y_combinator a la lambda también.

En breve:

auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
  // write body that processes some arguments
  // when you want to recurse, call recurse(some other arguments)
});

y tiene recursión en un lambda sin restricciones serias ni gastos generales significativos.

Usando lambdas para desempaquetar paquetes de parámetros en línea

C ++ 14

El paquete de parámetros que se desempaqueta tradicionalmente requiere escribir una función auxiliar cada vez que quiera hacerlo.

En este ejemplo de juguete:

template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
  using discard=int[];
  (void)discard{0,((void)(
    std::cout << Is << '\n' // here Is is a compile-time constant.
  ),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
  return print_indexes( std::make_index_sequence<I>{} );
}

El print_indexes_upto quiere crear y desempaquetar un paquete de parámetros de índices. Para hacerlo, debe llamar a una función auxiliar. Cada vez que desee desempaquetar un paquete de parámetros que haya creado, tendrá que crear una función auxiliar personalizada para hacerlo.

Esto se puede evitar con las lambdas.

Puede desempaquetar paquetes de parámetros en un conjunto de invocaciones de un lambda, como esto:

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    using discard=int[];
    (void)discard{0,(void(
      f( index<Is> )
    ),0)...};
  };
}

template<std::size_t N>
auto index_over(index_t<N> = {}) {
  return index_over( std::make_index_sequence<N>{} );
}
C ++ 17

Con expresiones de plegado, index_over() se puede simplificar para:

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    ((void)(f(index<Is>)), ...);
  };
}

Una vez que haya hecho eso, puede usar esto para reemplazar tener que desempaquetar manualmente los paquetes de parámetros con una segunda sobrecarga en otro código, permitiéndole desempaquetar los paquetes de parámetros "en línea":

template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
  using T = std::remove_reference_t<Tup>;
  using std::tuple_size;
  auto from_zero_to_N = index_over< tuple_size<T>{} >();

  from_zero_to_N(
    [&](auto i){
      using std::get;
      f( get<i>( std::forward<Tup>(tup) ) );
    }
  );
}

El auto i pasé a la lambda por index_over es un std::integral_constant<std::size_t, ???> . Esto tiene una conversión constexpr a std::size_t que no depende del estado de this , por lo que podemos usarlo como una constante de tiempo de compilación, como cuando lo pasamos a std::get<i> arriba.

Para volver al ejemplo de juguete en la parte superior, vuelva a escribirlo como:

template<std::size_t I>
void print_indexes_upto() {
  index_over(index<I>)([](auto i){
    std::cout << i << '\n'; // here i is a compile-time constant
  });
}

que es mucho más corto, y mantiene la lógica en el código que lo usa.

Ejemplo vivo para jugar.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow