Partilhar via


Sintaxe da expressão lambda

Esse artigo demonstra a sintaxe e os elementos estruturais das expressões lambda. Para obter uma descrição das expressões lambda, consulte Expressões lambda em C++.

Gramática da expressão lambda

A definição a seguir do padrão ISO C++11 mostra a gramática de uma expressão lambda. (Itens marcados com o subscrito opt são opcionais.)

        lambda-introducer lambda-declaratoropt compound-statement

Os seguintes componentes de sintaxe são mais especificados:

lambda-introducer:
        [ lambda capturaaceitação ]
lambda-capture:
        capture-default
        capture-list
        capture-default , capture-list
capture-default:
        &
        =
capture-list:
        capture ...opt
        capture-list , capture ...opt
capture:
        identificador
        & identificador
        this
lambda-declarator:
        ( cláusula de declaração de parâmetro ) mutableaceitação
                exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

Visual Studio oferece suporte ao C++ 11 padrão sintaxe da expressão lambda e funcionalidade, com as seguintes exceções:

  • Como todas as outras classes, lambdas não obtêm construtores de movimentação e operadores de atribuição de movimentação gerados automaticamente. Para obter mais informações sobre o suporte a comportamentos de referência rvalue, consulte a seção "Referências de Rvalue" Suporte a recursos do C++11 (C++ moderno).

  • O opcional attribute-specifier-seq não é compatível com essa versão.

O Visual Studio inclui esses recursos além da funcionalidade lambda do padrão C++11:

  • As lambdas sem monitoração de estado que podem ser convertidas em omni para ponteiros de função usam convenções de chamada arbitrárias.

  • Tipos de retorno deduzidos automaticamente para corpos lambda mais complicados que { return expression; }, desde que todos retornem instruções do mesmo tipo. (Essa funcionalidade é parte do C++14 padrão proposto.)

Propriedades de expressões lambda

Essa ilustração mapeia a gramática para um exemplo:

Elementos estruturais de uma expressão lambda

  1. lambda-introducer ( Também conhecido como cláusula de captura)

  2. lambda-introducer (Também conhecido como lista de parâmetros)

  3. mutable (Também conhecido como especificação mutável)

  4. exception-specification (Também conhecido como especificação de exceção)

  5. trailing-return-type (Também conhecido como tipo de retorno)

  6. compound-statement (Também conhecido como corpo lambda)

Cláusula capture

Uma expressão lambda é essencialmente uma classe, um construtor e um operador de chamada de função. Assim que definir uma classe, é necessário decidir se na lambda o objeto resultante captura variáveis por valor, por referência ou se não captura de modo algum. Se uma expressão lambda precisa acessar parâmetros de função e variáveis locais, eles devem ser capturados. A cláusula de captura (lambda-introducer na sintaxe padrão) especifica se o corpo da expressão lambda pode acessar variáveis no escopo delimitador por valor ou por referência. Variáveis que têm o prefixo E comercial (&) são acessadas por referência e variáveis que não têm o prefixo são acessadas por valor.

Uma cláusula de captura vazia, [ ], indica que o corpo da expressão lambda não acessa variáveis no escopo delimitador.

Você pode usar o modo de captura padrão (capture-default na sintaxe padrão) para capturar variáveis não especificadas por valor ou por referência. Você especifica o modo de captura padrão ao usar & ou = como o primeiro elemento da cláusula de captura. O elemento & informa o corpo da expressão lambda para acessar variáveis não especificadas por referência. O elemento = informa o corpo da expressão lambda para acessar variáveis não especificadas por valor. Por exemplo, se um corpo de lambda acessar a variável externa total por referência e a variável externa factor por valor, as seguintes cláusulas de captura serão equivalentes:

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

Um conceito errôneo comum sobre capture-default é o de que todas as variáveis no escopo são capturadas, sendo usadas ou não na lambda. Esse não é o caso, somente as variáveis que são mencionadas na lambda são capturadas quando capture-default é utilizado.

Se uma cláusula de captura inclui um capture-default &, então nenhum identifier em um capture da cláusula de captura pode ter o formato & identifier. Da mesma forma, se uma cláusula de captura inclui um capture-default =, então nenhum capture da cláusula de captura pode ter o formato = identifier. Um identificador ou this não podem aparecer mais de uma vez em uma cláusula capture. O trecho de código a seguir ilustra alguns exemplos.

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
}

Um capture seguido por reticências é uma expansão do pacote, conforme mostrado neste exemplo de modelo variadic:

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

É possível usar expressões lambda no corpo de um método de classe. Passe o ponteiro this para a cláusula capture para oferecer acesso aos métodos e aos membros de dados da classe delimitadora. Para obter um exemplo que mostra como usar expressões lambda com métodos de classe, consulte "Exemplo: usando um Lambda expressão em um método" Exemplos de expressões lambda.

Ao usar a cláusula de captura, nós recomendamos que você mantenha esses pontos em mente, especialmente ao usar lambdas com multithreading:

  • As capturas de referência podem ser usadas para modificar variáveis externas, mas as capturas de valor não. (mutable permite cópias sejam modificadas, mas não originais.)

  • As capturas de referência refletem atualizações para variáveis externas, mas as capturas de valor não.

  • As capturas de referência introduzem uma dependência de tempo de vida, mas as capturas de valor não possuem dependências de tempo de vida.

Lista de Parâmetro

Uma lista de parâmetros (lambda declarator na sintaxe padrão) é opcional e se parece com a lista de parâmetros de uma função.

Uma expressão lambda pode usar outra expressão lambda como seu argumento. Para obter mais informações, consulte "Expressões Lambda de ordem superior" no tópico Exemplos de expressões lambda.

Como uma lista de parâmetros é opcional, você pode omitir os parênteses vazios se você não passar argumentos para a expressão lambda e seu lambda-declarator: não contém especificação de exceção, tipo à direita de retorno, ou mutable.

Especificação mutável

Geralmente, o operador de chamada de função de uma lambda é constante por valor, mas o uso da palavra-chave mutable cancela esse efeito. Membros de dados mutáveis não são produzidos. A especificação mutável permite que o corpo de uma expressão lambda modifique variáveis capturadas por valor. Alguns dos exemplos, mais adiante neste artigo, mostram como usar mutable.

Especificação de exceção

É possível usar a especificação de exceção throw() para indicar que a expressão lambda não lança nenhuma exceção. Assim como em funções regulares, o compilador Visual C++ gera o aviso C4297 caso uma expressão lambda declare a especificação de exceção throw() e o corpo lambda lance uma exceção, conforme apresentado a seguir:

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

Para obter mais informações, consulte Especificações de exceção.

Tipo de Retorno

O tipo de retorno de uma expressão lambda é deduzido automaticamente. Você não precisa expressar a palavra-chave auto, a menos que especifique um trailing-return-type. O trailing-return-type se parece com a parte return-type de um método ou de uma função comum. No entanto, o tipo de retorno deve seguir a lista de parâmetros e você deve incluir a palavra-chave trailing-return-type -> antes do tipo de retorno.

É possível omitir a parte return-type de uma expressão lambda se o corpo lambda contiver apenas uma instrução de retorno ou se a expressão lambda não retornar um valor. Se o corpo lambda contém uma instrução de retorno, o compilador deduzirá o tipo de retorno do tipo da expressão de retorno. Caso contrário, o compilador deduzirá que o tipo de retorno é void. Considere os trechos de código do exemplo a seguir que ilustram esse princípio.

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

Uma expressão lambda pode gerar outra expressão lambda como seu valor de retorno. Para obter mais informações, consulte "Expressões Lambda de ordem superior" Exemplos de expressões lambda.

Corpo lambda

A parte do corpo lambda (compound-statement na sintaxe padrão) de uma expressão lambda pode conter tudo o que o corpo de um método ou uma função comum pode conter. O corpo de uma função comum e de uma expressão lambda pode acessar os seguintes tipos de variáveis:

  • Parâmetros

  • Variáveis declaradas localmente

  • Membros de dados de classe, quando declarados dentro de uma classe e quando this for capturado

  • Qualquer variável que possui a duração de armazenamento estático como, por exemplo, variáveis globais

Além disso, uma expressão lambda pode acessar variáveis capturadas no escopo delimitador. Uma variável será capturada explicitamente se aparecer na cláusula capture da expressão lambda. Caso contrário, a variável será capturada implicitamente. O corpo da expressão lambda usa o modo padrão de captura para acessar as variáveis capturadas implicitamente.

O exemplo a seguir contém uma expressão lambda que captura explicitamente a variável n por valor e que captura implicitamente a variável m por referência:

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

Saída:

              
            

Como a variável n é capturada pelo valor, seu valor permanece 0 após a chamada para a expressão lambda. A especificação mutable permite que n seja modificada na lambda.

Embora uma expressão lambda possa capturar apenas variáveis que tenham a duração automática de armazenamento, você pode usar variáveis que tenham a duração de armazenamento estático no corpo de uma expressão lambda. O exemplo a seguir usa a função generate e uma expressão lambda para atribuir um valor para cada elemento em um objeto vector. A expressão lambda modifica a variável estática para gerar o valor do próximo elemento.

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
}

Para obter mais informações, consulte generate.

O exemplo de código a seguir usa a função do exemplo anterior e adiciona um exemplo de uma expressão lambda que usa o algoritmo STL generate_n. Essa expressão lambda atribui um elemento de um objeto vector à soma dos dois elementos anteriores. A palavra-chave mutable é usada de modo que o corpo da expressão lambda possa modificar suas cópias das variáveis externas x e y, que a expressão lambda captura por valor. Uma vez que a expressão lambda captura as variáveis originais x e y por valor, seus valores permanecem 1 depois que a lambda é executada.

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

Saída:

              
            

Para obter mais informações, consulte generate_n.

Modificadores específicos da Microsoft

Se você está usando um modificador específico da Microsoft como __declspec, é possível inseri-lo em uma expressão lambda imediatamente após o parameter-declaration-clause— por exemplo:

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

Para determinar se um modificador tem suporte por lambdas, consulte o artigo sobre isso na seção de documentação dos Modificadores específicos da Microsoft.

Consulte também

Referência

Expressões lambda em C++

Exemplos de expressões lambda

generate

generate_n

for_each

Especificações de exceção

Aviso do compilador (nível 1) C4297

Modificadores específicos da Microsoft