C++ Cos'è un'espressione lambda?


Esempio

Un'espressione lambda fornisce un modo conciso per creare oggetti funzione semplici. Un'espressione lambda è un prvalore il cui oggetto risultato è chiamato oggetto chiusura , che si comporta come un oggetto funzione.

Il nome "espressione lambda" deriva dal lambda calcolo , che è un formalismo matematico inventato negli anni '30 dalla Chiesa di Alonzo per indagare sulle questioni relative alla logica e alla computabilità. Il calcolo Lambda ha costituito la base di LISP , un linguaggio di programmazione funzionale. Rispetto al lambda calcolo e al LISP, le espressioni lambda in C ++ condividono le proprietà di essere senza nome e di catturare variabili dal contesto circostante, ma non hanno la capacità di operare e restituire funzioni.

Un'espressione lambda viene spesso utilizzata come argomento per le funzioni che accettano un oggetto chiamabile. Ciò può essere più semplice della creazione di una funzione con nome, che verrebbe utilizzata solo quando passata come argomento. In tali casi, le espressioni lambda sono generalmente preferite perché consentono di definire gli oggetti funzione in linea.

Un lambda consiste tipicamente di tre parti: una lista di cattura [] , un elenco di parametri facoltativo () e un corpo {} , che possono essere tutti vuoti:

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

Elenco di acquisizione

[] è la lista di cattura . Per impostazione predefinita, non è possibile accedere alle variabili dello scope che racchiude un lambda. Catturare una variabile lo rende accessibile all'interno della lambda, sia come copia che come riferimento . Le variabili catturate diventano una parte del lambda; al contrario degli argomenti della funzione, non devono essere passati quando si chiama il 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 dei parametri

() è la lista dei parametri , che è quasi la stessa di quella delle normali funzioni. Se il lambda non accetta argomenti, queste parentesi possono essere omesse (eccetto se è necessario dichiarare il mutable lambda). Questi due lambda sono equivalenti:

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

L'elenco dei parametri può utilizzare il tipo di segnaposto auto posto dei tipi effettivi. In questo modo, questo argomento si comporta come un parametro di modello di un modello di funzione. I seguenti lambda sono equivalenti quando si desidera ordinare un vettore in codice generico:

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

Corpo della funzione

{} è il corpo , che è lo stesso delle normali funzioni.


Chiamando un lambda

L'oggetto risultato di un'espressione lambda è una chiusura , che può essere chiamata usando l' operator() (come con altri oggetti funzione):

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 di reso

Per impostazione predefinita, viene dedotto il tipo di ritorno di un'espressione lambda.

[](){ return true; };

In questo caso il tipo di bool è bool .

Puoi anche specificare manualmente il tipo di ritorno usando la seguente sintassi:

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

Mutevole Lambda

Gli oggetti catturati dal valore nel lambda sono di default immutabili. Questo perché l' operator() dell'oggetto di chiusura generato è const per impostazione predefinita.

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

La modifica può essere consentita usando la parola chiave mutable , che rende non- const l' operator() dell'oggetto più vicino operator() :

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

Se usato insieme al tipo restituito, il mutable viene prima di esso.

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

Un esempio per illustrare l'utilità di lambda

Prima del 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));

Dal momento che 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; });