Condividi tramite


Sintassi delle espressioni lambda

In questo argomento vengono descritti la sintassi e gli elementi strutturali delle espressioni lambda. Per una descrizione delle espressioni lambda, vedere Espressioni lambda in C++.

Grammatica dell'espressione lambda

La definizione seguente, dallo standard ISO C++11, illustra la grammatica di un'espressione lambda. Gli elementi contrassegnati con il pedice opt sono facoltativi.

        lambda-introducer lambda-declaratoropt compound-statement

Questi componenti sintattici sono ulteriormente scomposti nel modo seguente:

lambda-introducer:
        [ lambda-captureopt ]
lambda-capture:
        capture-default
        capture-list
        capture-default , capture-list
capture-default:
        &
        =
capture-list:
        capture ...opt
        capture-list , capture ...opt
capture:
        identifier
        & identifier
        this
lambda-declarator:
        ( parameter-declaration-clause ) mutableopt
                exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

Visual Studio supporta la sintassi e la funzionalità per le espressioni lambda dello standard C++11, con le eccezioni seguenti:

  • Le espressioni lambda, come tutte le altre classi, non ottengono operatori di assegnazione di spostamento e costruttori di spostamento generati automaticamente. Per altre informazioni sul supporto per i comportamenti dei riferimenti rvalue, vedere la sezione "Riferimenti rvalue" in Supporto delle funzionalità C++11 (C++ moderno).

  • L'elemento attribute-specifier-seq facoltativo non è supportato in questa versione.

Visual Studio aggiunge le funzionalità seguenti alla funzionalità lambda dello standard C++11:

  • Le espressioni lambda senza stato sono convertibili in qualsiasi tipo di puntatori funzione che usano convenzioni di chiamata arbitrarie.

  • I tipi restituiti vengono automaticamente dedotti per i corpi delle espressioni lambda più complessi di { return expression; }, purché tutte le istruzioni return presentino lo stesso tipo. Questa funzionalità fa parte dello standard C++14 proposto.

Proprietà delle espressioni lambda

L'immagine seguente associa la grammatica a un esempio:

Elementi strutturali di un'espressione lambda

  1. lambda-introducer (anche definito come clausola di acquisizione)

  2. lambda declarator (anche definito elenco dei parametri)

  3. mutable (anche definito specifica modificabile)

  4. exception-specification (anche definito specifica di eccezione)

  5. trailing-return-type (anche definito tipo restituito)

  6. compound-statement (anche definito corpo dell'espressione lambda)

Clausola di acquisizione

Un'espressione lambda è essenzialmente una classe, un costruttore e un operatore di chiamata di funzione. Proprio come quando si definisce una classe, è necessario decidere se l'oggetto risultante deve acquisire variabili per valore, per riferimento o non acquisirne affatto. Se un'espressione lambda deve accedere alle variabili locali e ai parametri di funzione, questi devono essere acquisiti. La clausola di acquisizione (lambda-introducer nella sintassi standard) specifica se il corpo dell'espressione lambda può accedere alle variabili nell'ambito che le contiene per valore o per riferimento. le variabili che hanno come prefisso una e commerciale (&) sono accessibili per riferimento, mentre le variabili che ne sono prive sono accessibili per valore.

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

È possibile usare la modalità di acquisizione predefinita (capture-default nella sintassi standard) per acquisire variabili non specificate per valore o per riferimento. Specificare la modalità di acquisizione predefinita usando & o = come primo elemento della clausola di acquisizione. L'elemento & indica al corpo dell'espressione lambda di accedere alle variabili non specificate per riferimento. L'elemento = indica al corpo dell'espressione lambda di accedere alle variabili non specificate per valore. 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]
[factor, &]
[=, &total]
[&total, =]

Un equivoco comune su capture-default è la convinzione che tutte le variabili contenute nell'ambito vengano acquisite a prescindere dal loro uso o meno all'interno dell'espressione lambda. Questo infatti non è vero: quando si usa capture-default, vengono acquisite le sole variabili menzionate nell'espressione lambda.

Se la clausola di acquisizione include un elemento capture-default &, nessun identifier in un elemento capture di tale clausola di acquisizione potrà avere il formato & identifier. Allo stesso modo, se la clausola di acquisizione include un elemento capture-default =, nessun capture di quella clausola potrà avere il formato = identifier. Un identificatore o un elemento this non può apparire più di una volta in una clausola di acquisizione. Nel frammento di codice seguente vengono illustrati 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
    [i, i]{};    // ERROR: i repeated
}

Un elemento capture seguito dai puntini di sospensione è un'espansione del pacchetto, illustrata nell'esempio di modello variadic seguente:

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

È possibile usare le espressioni lambda nel corpo di un metodo della classe. Passare il puntatore this alla clausola di acquisizione per fornire l'accesso ai metodi e ai membri dati della classe che li contengono. Per un esempio sull'uso delle espressioni lambda con i metodi della classe, vedere "Utilizzo di un'espressione lambda in un metodo" nell'argomento Esempi di espressioni lambda.

Quando si usa la clausola di acquisizione, è consigliabile tenere in considerazione gli aspetti seguenti, in particolare quando si usano le espressioni lambda con il multithreading:

  • Le acquisizioni di riferimento possono essere usate per modificare le variabili all'esterno, ma le acquisizioni di valore non possono essere usate a tale scopo (mutable consente la modifica delle copie, ma non degli originali).

  • Le acquisizioni di riferimento riflettono gli aggiornamenti alle variabili all'esterno, contrariamente alle acquisizioni di valore.

  • Le acquisizioni di riferimento introducono una dipendenza dalla durata, contrariamente alle acquisizioni di valore.

Elenco dei parametri

L'elenco di parametri (lambda declarator nella sintassi standard) è facoltativo ed è simile all'elenco di parametri per una funzione.

Un'espressione lambda può accettare un'altra espressione lambda come argomento. Per altre informazioni, vedere la sezione "Funzioni lambda di ordine superiore" nell'argomento 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 lambda-declarator: non contiene exception-specification, trailing-return-type o mutable.

Specifica modificabile

Generalmente l'operatore di chiamata di funzione di un'espressione lambda è const-by-value, ma questo viene annullato dall'uso della parola chiave mutable. Non produce membri dati modificabili. La specifica modificabile consente al corpo di un'espressione lambda di modificare le variabili acquisite per valore. L'uso di mutable viene illustrato in alcuni degli esempi riportati più avanti in questo articolo.

Specifica di eccezione

È possibile usare la specifica di eccezione throw() per indicare che l'espressione lambda non generi alcuna eccezione. Come con le normali funzioni, il compilatore di Visual C++ genera l'avviso C4297 se un'espressione lambda dichiara la specifica di eccezione throw() e il relativo corpo genera un'eccezione, come illustrato nell'esempio seguente:

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

Per altre informazioni, vedere Specifiche di eccezioni.

Tipo restituito

Il tipo restituito di un'espressione lambda viene dedotto automaticamente. Non è necessario esprimere la parola chiave auto a meno che non venga specificato un trailing-return-type. trailing-return-type è simile alla parte del tipo restituito di un metodo o funzione ordinaria. 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 del tipo restituito di un'espressione lambda se il corpo dell'espressione lambda contiene una sola istruzione return o 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 che il tipo restituito è void. Vedere i frammenti di codice di esempio seguenti in cui viene illustrato 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 is not valid

Un'espressione lambda può produrre un'altra espressione lambda come relativo valore restituito. Per altre informazioni, vedere la sezione "Funzioni lambda di ordine superiore" nell'argomento Esempi di espressioni lambda.

Corpo dell'espressione lambda

Il corpo di un'espressione lambda (compound-statement nella sintassi standard) può contenere qualsiasi elemento che può essere contenuto nel corpo di un metodo o di una funzione ordinaria. Il corpo di una funzione ordinaria e quello di una funzione lambda possono entrambi accedere ai seguenti tipi di variabili:

  • Parametri

  • Variabili dichiarate a livello locale

  • Membri dati classe (quando dichiarati all'interno di classi, con l'acquisizione di this)

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

Inoltre, un'espressione lambda può accedere alle variabili che acquisisce dall'ambito che la contiene. Una variabile viene acquisita in modo esplicito se viene visualizzata nella clausola di acquisizione dell'espressione lambda. In caso contrario, la variabile viene acquisita in modo implicito. Il corpo dell'espressione lambda usa la modalità di acquisizione predefinita per accedere alle variabili acquisite in modo implicito.

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

Output:

  

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

Sebbene un'espressione lambda possa acquisire solo le variabili che hanno una durata dell'archiviazione automatica, nel corpo di questo tipo di espressioni è possibile usare variabili con durata dell'archiviazione statica. 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 is not thread-safe and is shown for illustration only
}

Per altre informazioni, vedere generare.

Nell'esempio di codice seguente viene usata la funzione dell'esempio precedente e viene aggiunto un esempio di espressione lambda con l'algoritmo STL generate_n. Questa espressione lambda assegna un elemento di un oggetto vector alla somma dei due elementi precedenti. La parola chiave mutable viene usata affinché il corpo dell'espressione lambda possa modificare le relative copie delle variabili esterne x e y, acquisite per valore dall'espressione stessa. 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 is not 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);
}

Output:

  

Per altre informazioni, vedere generate_n.

Modificatori specifici Microsoft

Se si usa un modificatore specifico Microsoft come __declspec, è 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 è supportato dalle espressioni lambda, vedere l'articolo nella sezione relativa ai modificatori specifici Microsoft della documentazione.

Vedere anche

Riferimenti

Espressioni lambda in C++

Esempi di espressioni lambda

generare

generate_n

for_each

Specifiche di eccezioni

Avviso del compilatore (livello 1) C4297

Modificatori specifici Microsoft