Condividi tramite


Lambda Expression Syntax

This article demonstrates the syntax and structural elements of lambda expressions. For a description of lambda expressions, see Lambda Expressions in C++.

Lambda Expression Grammar

The following definition from the ISO C++11 Standard shows the grammar of a lambda expression. (Items marked with the opt subscript are optional.)

        lambda-introducer lambda-declaratoroptcompound-statement

These syntax components are further specified:

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-specificationoptattribute-specifier-seqopttrailing-return-typeopt

Visual Studio supports C++11 Standard lambda expression syntax and functionality, with these exceptions:

  • Like all other classes, lambdas don't get automatically generated move constructors and move assignment operators. For more information about support for rvalue reference behaviors, see the "Rvalue References" section in Support For C++11 Features (Modern C++).

  • The optional attribute-specifier-seq is not supported in this version.

Visual Studio includes these features in addition to C++11 Standard lambda functionality:

  • Stateless lambdas, which are omni-convertible to function pointers that use arbitrary calling conventions.

  • Automatically deduced return types for lambda bodies that are more complicated than { return expression; }, as long as all return statements have the same type. (This functionality is part of the proposed C++14 Standard.)

Properties of Lambda Expressions

This illustration maps the grammar to an example:

Structural elements of a lambda expression

  1. lambda-introducer (Also known as the capture clause)

  2. lambda declarator (Also known as the parameter list)

  3. mutable (Also known as the mutable specification)

  4. exception-specification (Also known as the exception specification)

  5. trailing-return-type (Also known as the return type)

  6. compound-statement (Also known as the lambda body)

Capture Clause

A lambda expression is essentially a class, a constructor, and a function call operator. Just as when you define a class, in a lambda you must decide whether the resulting object captures variables by value, by reference, or not at all. If a lambda expression has to access local variables and function parameters, they must be captured. The capture clause (lambda-introducer in the Standard syntax) specifies whether the body of the lambda expression can access variables in the enclosing scope by value or by reference. Variables that have the ampersand (&) prefix are accessed by reference and variables that do not have it are accessed by value.

An empty capture clause, [ ], indicates that the body of the lambda expression accesses no variables in the enclosing scope.

You can use the default capture mode (capture-default in the Standard syntax) to capture unspecified variables either by value or by reference. You specify the default capture mode by using & or = as the first element of the capture clause. The & element tells the body of the lambda expression to access unspecified variables by reference. The = element tells the lambda body to access unspecified variables by value. For example, if a lambda body accesses the external variable total by reference and the external variable factor by value, then the following capture clauses are equivalent:

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

A common misconception about capture-default is that all variables in the scope are captured whether they are used in the lambda or not. This is not the case—only variables that are mentioned in the lambda are captured when a capture-default is used.

If a capture clause includes a capture-default&, then no identifier in a capture of that capture clause can have the form & identifier. Likewise, if the capture clause includes a capture-default=, then no capture of that capture clause can have the form = identifier. An identifier or this cannot appear more than once in a capture clause. The following code snippet illustrates some examples.

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
}

A capture followed by an ellipsis is a pack expansion, as shown in this variadic template example:

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

You can use lambda expressions in the body of a class method. Pass the this pointer to the capture clause to provide access to the methods and data members of the enclosing class. For an example that shows how to use lambda expressions with class methods, see "Example: Using a Lambda Expression in a Method" in Examples of Lambda Expressions.

When you use the capture clause, we recommend that you keep these points in mind, particularly when you use lambdas with multithreading:

  • Reference captures can be used to modify variables outside, but value captures cannot. (mutable allows copies to be modified, but not originals.)

  • Reference captures reflect updates to variables outside, but value captures do not.

  • Reference captures introduce a lifetime dependency, but value captures have no lifetime dependencies.

Parameter List

A parameter list (lambda declarator in the Standard syntax) is optional and resembles the parameter list for a function.

A lambda expression can take another lambda expression as its argument. For more information, see "Higher-Order Lambda Expressions" in the topic Examples of Lambda Expressions.

Because a parameter list is optional, you can omit the empty parentheses if you do not pass arguments to the lambda expression and its lambda-declarator: does not contain exception-specification, trailing-return-type, or mutable.

Mutable Specification

Typically, a lambda's function call operator is const-by-value, but use of the mutable keyword cancels this out. It does not produce mutable data members. The mutable specification enables the body of a lambda expression to modify variables that are captured by value. Some of the examples later in this article show how to use mutable.

Exception Specification

You can use the throw() exception specification to indicate that the lambda expression does not throw any exceptions. As with ordinary functions, the Visual C++ compiler generates warning C4297 if a lambda expression declares the throw() exception specification and the lambda body throws an exception, as shown here:

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

For more information, see Exception Specifications.

Return Type

The return type of a lambda expression is automatically deduced. You don't have to express the auto keyword unless you specify a trailing-return-type. The trailing-return-type resembles the return-type part of an ordinary method or function. However, the return type must follow the parameter list, and you must include the trailing-return-type keyword -> before the return type.

You can omit the return-type part of a lambda expression if the lambda body contains just one return statement or the expression does not return a value. If the lambda body contains one return statement, the compiler deduces the return type from the type of the return expression. Otherwise, the compiler deduces the return type to be void. Consider the following example code snippets that illustrate this principle.

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

A lambda expression can produce another lambda expression as its return value. For more information, see "Higher-Order Lambda Expressions" in Examples of Lambda Expressions.

Lambda Body

The lambda body (compound-statement in the Standard syntax) of a lambda expression can contain anything that the body of an ordinary method or function can contain. The body of both an ordinary function and a lambda expression can access these kinds of variables:

  • Parameters

  • Locally-declared variables

  • Class data members, when declared inside a class and this is captured

  • Any variable that has static storage duration—for example, global variables

In addition, a lambda expression can access variables that it captures from the enclosing scope. A variable is explicitly captured if it appears in the capture clause of the lambda expression. Otherwise, the variable is implicitly captured. The body of the lambda expression uses the default capture mode to access variables that are implicitly captured.

The following example contains a lambda expression that explicitly captures the variable n by value and implicitly captures the variable m by reference:

// 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:

5
0

Because the variable n is captured by value, its value remains 0 after the call to the lambda expression. The mutable specification allows n to be modified within the lambda.

Although a lambda expression can only capture variables that have automatic storage duration, you can use variables that have static storage duration in the body of a lambda expression. The following example uses the generate function and a lambda expression to assign a value to each element in a vector object. The lambda expression modifies the static variable to generate the value of the next element.

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
}

For more information, see generate.

The following code example uses the function from the previous example, and adds an example of a lambda expression that uses the STL algorithm generate_n. This lambda expression assigns an element of a vector object to the sum of the previous two elements. The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y, which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.

// 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:

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

For more information, see generate_n.

Microsoft-Specific Modifiers

If you are using a Microsoft-specific modifier such as __declspec, you can insert it into a lambda expression immediately after the parameter-declaration-clause—for example:

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

To determine whether a modifier is supported by lambdas, see the article about it in the Microsoft-Specific Modifiers section of the documentation.

See Also

Reference

Lambda Expressions in C++

Examples of Lambda Expressions

generate

generate_n

for_each

Exception Specifications

Compiler Warning (level 1) C4297

Microsoft-Specific Modifiers