C++ ¿Qué es una expresión lambda?


Ejemplo

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; });