Espressioni lambda in C++

In C++11 e versioni successive, un'espressione lambda, spesso denominata lambda, è un modo pratico per definire un oggetto funzione anonimo (una chiusura) nella posizione in cui viene richiamata o passata come argomento a una funzione. In genere, le espressioni lambda vengono usate per incapsulare alcune righe di codice passate agli algoritmi o alle funzioni asincrone. Questo articolo definisce le espressioni lambda e le confronta con altre tecniche di programmazione. Descrive i vantaggi e fornisce alcuni esempi di base.

Parti di un'espressione lambda

Di seguito è riportato un'espressione lambda semplice passata come terzo argomento alla std::sort() funzione:

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

Questa figura mostra le parti della sintassi lambda:

Diagram that identifies the various parts of a lambda expression.

L'esempio di espressione lambda è [=]() modificabile throw() -> int { return x+y; } [=] è la clausola capture; noto anche come lambda-introducer nella specifica C++. Le parentesi sono relative all'elenco di parametri. La parola chiave modificabile è facoltativa. throw() è la specifica di eccezione facoltativa. -> int è il tipo restituito finale facoltativo. Il corpo lambda è costituito dall'istruzione all'interno delle parentesi graffe o restituito x+y; Queste informazioni sono illustrate in modo più dettagliato dopo l'immagine.

  1. clausola capture (nota anche come lambda-introducer nella specifica C++).

  2. elenco di parametri Facoltativo. (noto anche come dichiaratore lambda)

  3. specifica modificabile Facoltativo.

  4. specifica dell'eccezione Facoltativo.

  5. tipo restituito finale Facoltativo.

  6. corpo lambda.

Clausola Capture

Un'espressione lambda può introdurre nuove variabili nel corpo (in C++14) e può anche accedere, o acquisire, variabili dall'ambito circostante. Un'espressione lambda inizia con la clausola capture. Specifica quali variabili vengono acquisite e se l'acquisizione è per valore o per riferimento. Le variabili con prefisso e commerciale (&) sono accessibili in base al riferimento e alle variabili a cui non è possibile accedervi in base al valore.

Una clausola di acquisizione vuota, [ ], indica che il corpo dell'espressione lambda non accede a variabili nell'ambito che lo contiene.

È possibile usare una modalità di acquisizione predefinita per indicare come acquisire qualsiasi variabile esterna a cui viene fatto riferimento nel corpo lambda: [&] significa che tutte le variabili a cui si fa riferimento vengono acquisite per riferimento e [=] significa che vengono acquisite per valore. È possibile usare una modalità di acquisizione predefinita e quindi specificare esplicitamente la modalità opposta per variabili specifiche. Se ad esempio il corpo di un'espressione lambda accede alla variabile esterna total per riferimento e alla variabile esterna factor per valore, le seguenti clausole di acquisizione sono equivalenti:

[&total, factor]
[factor, &total]
[&, factor]
[=, &total]

Solo le variabili indicate nel corpo lambda vengono acquisite quando viene usato un valore capture-default.

Se una clausola capture include un valore capture-default &, non è possibile che nessun identificatore in un'acquisizione di tale clausola capture abbia il formato &identifier. Analogamente, se la clausola capture include un valore capture-default =, non è possibile acquisire tale clausola capture nel formato =identifier. Un identificatore o this non può essere visualizzato più volte in una clausola capture. Il frammento di codice seguente illustra alcuni esempi:

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};      // OK
    [&, &i]{};     // ERROR: i preceded by & when & is the default
    [=, this]{};   // ERROR: this when = is the default
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

Un'acquisizione seguita dai puntini di sospensione è un'espansione pack, come illustrato in questo esempio di modello variadic:

template<class... Args>
void f(Args... args) {
    auto x = [args...] { return g(args...); };
    x();
}

Per usare espressioni lambda nel corpo di una funzione membro della classe, passare il this puntatore alla clausola capture per fornire l'accesso alle funzioni membro e ai membri dati della classe contenitore.

Visual Studio 2017 versione 15.3 e successive (disponibile in /std:c++17 modalità e versioni successive): il this puntatore può essere acquisito per valore specificando *this nella clausola capture. Capture by value copia l'intera chiusura in ogni sito di chiamata in cui viene richiamata l'espressione lambda. Una chiusura è l'oggetto funzione anonimo che incapsula l'espressione lambda. L'acquisizione per valore è utile quando l'espressione lambda viene eseguita in operazioni parallele o asincrone. È particolarmente utile in alcune architetture hardware, ad esempio NUMA.

Per un esempio che illustra come usare espressioni lambda con funzioni membro di classe, vedere "Esempio: Uso di un'espressione lambda in un metodo" in Esempi di espressioni lambda.

Quando si usa la clausola capture, è consigliabile tenere presenti questi punti, in particolare quando si usano espressioni lambda con multithreading:

  • Le acquisizioni di riferimenti possono essere usate per modificare le variabili esterne, ma le acquisizioni di valori non possono. (mutable consente di modificare le copie, ma non originali.

  • Le acquisizioni di riferimenti riflettono gli aggiornamenti alle variabili esterne, ma le acquisizioni di valori non lo fanno.

  • Le acquisizioni di riferimento introducono una dipendenza dalla durata, contrariamente alle acquisizioni di valore. È particolarmente importante quando l'espressione lambda viene eseguita in modo asincrono. Se si acquisisce un riferimento locale per riferimento in un'espressione lambda asincrona, tale locale potrebbe essere facilmente trascorso dal momento in cui viene eseguita l'espressione lambda. Il codice potrebbe causare una violazione di accesso in fase di esecuzione.

Acquisizione generalizzata (C++14)

In C++14 è possibile introdurre e inizializzare nuove variabili nella clausola capture, senza la necessità di avere tali variabili nell'ambito di inclusione della funzione lambda. L'inizializzazione può essere espressa come espressione arbitraria. Il tipo della nuova variabile viene dedotto dal tipo prodotto dall'espressione. Questa funzionalità consente di acquisire variabili di sola spostamento (ad esempio std::unique_ptr) dall'ambito circostante e di usarle in un'espressione lambda.

pNums = make_unique<vector<int>>(nums);
//...
      auto a = [ptr = move(pNums)]()
        {
           // use ptr
        };

Elenco parametri

Le espressioni lambda possono acquisire variabili e accettare parametri di input. Un elenco di parametri (dichiaratore lambda nella sintassi Standard) è facoltativo e nella maggior parte degli aspetti è simile all'elenco di parametri per una funzione.

auto y = [] (int first, int second)
{
    return first + second;
};

In C++14, se il tipo di parametro è generico, è possibile usare la auto parola chiave come identificatore di tipo. Questa parola chiave indica al compilatore di creare l'operatore di chiamata di funzione come modello. Ogni istanza di in un elenco di auto parametri equivale a un parametro di tipo distinto.

auto y = [] (auto first, auto second)
{
    return first + second;
};

Un'espressione lambda può accettare un'altra espressione lambda come argomento. Per altre informazioni, vedere "Espressioni lambda ordine superiore" nell'articolo Esempi di espressioni lambda.

Poiché un elenco di parametri è facoltativo, è possibile omettere le parentesi vuote se non si passano argomenti all'espressione lambda e il relativo dichiaratore lambda non contiene la specifica dell'eccezione, il tipo restituito finale o mutable.

Specifica modificabile

In genere, l'operatore di chiamata di funzione di un'espressione lambda è const-by-value, ma l'uso mutable della parola chiave annulla questo valore. Non produce membri dati modificabili. La mutable specifica consente al corpo di un'espressione lambda di modificare le variabili acquisite per valore. Alcuni degli esempi più avanti in questo articolo illustrano come usare mutable.

Specifica di eccezione

È possibile usare la specifica dell'eccezione noexcept per indicare che l'espressione lambda non genera eccezioni. Come per le funzioni comuni, il compilatore Microsoft C++ genera l'avviso C4297 se un'espressione lambda dichiara la noexcept specifica dell'eccezione e il corpo lambda genera un'eccezione, come illustrato di seguito:

// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
   []() noexcept { throw 5; }();
}

Per altre informazioni, vedere Specifiche delle eccezioni (throw).For more information, see Exception specifications (throw).

Tipo restituito

Il tipo restituito di un'espressione lambda viene dedotto automaticamente. Non è necessario usare la auto parola chiave a meno che non si specifichi un tipo restituito finale. Il tipo restituito finale è simile alla parte di tipo restituito di una funzione o di una funzione membro normale. Tuttavia, il tipo restituito deve seguire l'elenco di parametri ed è necessario includere la parola chiave di trailing-return-type -> prima del tipo restituito.

È possibile omettere la parte di tipo restituito di un'espressione lambda se il corpo lambda contiene solo un'istruzione return. In alternativa, se l'espressione non restituisce un valore. Se il corpo dell'espressione lambda contiene un'istruzione return, il compilatore deduce il tipo restituito dal tipo dell'espressione restituita. In caso contrario, il compilatore deduce il tipo restituito come void. Si considerino i frammenti di codice di esempio seguenti che illustrano questo principio:

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing
                                  // return type from braced-init-list isn't valid

Un'espressione lambda può produrre un'altra espressione lambda come relativo valore restituito. Per altre informazioni, vedere "Espressioni lambda di ordine superiore" in Esempi di espressioni lambda.

Corpo lambda

Il corpo lambda di un'espressione lambda è un'istruzione composta. Può contenere qualsiasi elemento consentito nel corpo di una funzione o di una funzione membro normale. Il corpo di una funzione ordinaria e quello di una espressione lambda possono entrambi accedere ai seguenti tipi di variabili:

  • Variabili acquisite dall'ambito che le contiene, come illustrato in precedenza.

  • Parametri.

  • Variabili dichiarate in locale.

  • Membri dati della classe, se dichiarati all'interno di una classe e this acquisiti.

  • Qualsiasi variabile con durata di archiviazione statica, ad esempio variabili globali.

L'esempio seguente contiene un'espressione lambda che acquisisce in modo esplicito la variabile n per valore e acquisisce in modo implicito la variabile m per riferimento:

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}
5
0

Poiché la variabile n viene acquisita per valore, il relativo valore rimane 0 dopo la chiamata all'espressione lambda. La mutable specifica consente di n essere modificata all'interno dell'espressione lambda.

Un'espressione lambda può acquisire solo variabili con durata di archiviazione automatica. È tuttavia possibile usare variabili con durata di archiviazione statica nel corpo di un'espressione lambda. Nell'esempio seguente vengono usate la funzione generate e un'espressione lambda per assegnare un valore a ogni elemento di un oggetto vector. L'espressione lambda modifica la variabile statica per generare il valore dell'elemento successivo.

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}

Per altre informazioni, vedere Generate.

Nell'esempio di codice seguente viene usata la funzione dell'esempio precedente e viene aggiunto un esempio di espressione lambda che usa l'algoritmo generate_ndella libreria standard C++. Questa espressione lambda assegna un elemento di un oggetto vector alla somma dei due elementi precedenti. La mutable parola chiave viene usata in modo che il corpo dell'espressione lambda possa modificare le relative copie delle variabili x esterne e y, che l'espressione lambda acquisisce per valore. Poiché l'espressione lambda acquisisce le variabili originali x e y per valore, i relativi valori rimangono 1 dopo l'esecuzione dell'espressione lambda.

// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename C> void print(const string& s, const C& c) {
    cout << s;

    for (const auto& e : c) {
        cout << e << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}

int main()
{
    // The number of elements in the vector.
    const int elementCount = 9;

    // Create a vector object with each element set to 1.
    vector<int> v(elementCount, 1);

    // These variables hold the previous two elements of the vector.
    int x = 1;
    int y = 1;

    // Sets each element in the vector to the sum of the
    // previous two elements.
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // lambda is the 3rd parameter
        // Generate current value.
        int n = x + y;
        // Update previous two values.
        x = y;
        y = n;
        return n;
    });
    print("vector v after call to generate_n() with lambda: ", v);

    // Print the local variables x and y.
    // The values of x and y hold their initial values because
    // they are captured by value.
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(v);
    print("vector v after 1st call to fillVector(): ", v);
    // Fill the vector with the next sequence of numbers
    fillVector(v);
    print("vector v after 2nd call to fillVector(): ", v);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

Per altre informazioni, vedere generate_n.

constexpr Espressioni lambda

Visual Studio 2017 versione 15.3 e successive (disponibile in /std:c++17 modalità e versioni successive): è possibile dichiarare un'espressione lambda come constexpr (o usarla in un'espressione costante) quando l'inizializzazione di ogni membro dati acquisito o introdotto è consentita all'interno di un'espressione costante.

    int y = 32;
    auto answer = [y]() constexpr
    {
        int x = 10;
        return y + x;
    };

    constexpr int Increment(int n)
    {
        return [n] { return n + 1; }();
    }

Un'espressione lambda è implicitamente constexpr se il risultato soddisfa i requisiti di una constexpr funzione:

    auto answer = [](int n)
    {
        return 32 + n;
    };

    constexpr int response = answer(10);

Se un'espressione lambda è implicitamente o esplicitamente constexpr, la conversione in un puntatore a funzione produce una constexpr funzione:

    auto Increment = [](int n)
    {
        return n + 1;
    };

    constexpr int(*inc)(int) = Increment;

Specifico di Microsoft

Le espressioni lambda non sono supportate nelle entità gestite CLR (Common Language Runtime) seguenti: ref class, ref struct, value classo value struct.

Se si usa un modificatore specifico di Microsoft, __declspecad esempio , è possibile inserirlo in un'espressione lambda immediatamente dopo .parameter-declaration-clause Ad esempio:

auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };

Per determinare se un modificatore specifico è supportato dalle espressioni lambda, vedere l'articolo sul modificatore nella sezione Modificatori specifici di Microsoft.

Visual Studio supporta la funzionalità lambda standard C++11 e le espressioni lambda senza stato. Un'espressione lambda senza stato è convertibile in un puntatore a funzione che usa una convenzione di chiamata arbitraria.

Vedi anche

Riferimenti al linguaggio C++
Oggetti funzione della libreria standard C++
Chiamata di funzione
for_each