Поделиться через


Синтаксис лямбда-выражений

В этой статье демонстрируется синтаксис и структурные элементы лямбда-выражений. Описание лямбда-выражений см. в разделе Лямбда-выражения в C++.

Грамматика лямбда-выражений

В следующем определении из стандарта ISO C++11 показана грамматика лямбда-выражения (элементы, помеченные подстрочным индексом opt, необязательны).

        lambda-introducer lambda-declaratoropt compound-statement

Эти компоненты синтаксиса можно далее указать следующим образом:

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:
        идентификатор
        & идентификатор
        this
lambda-declarator:
        ( parameter-declaration-clause ) mutableopt
                exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

Visual Studio поддерживает синтаксис и функции лямбда-выражений стандарта C++11 со следующими исключениями:

  • Как и все другие классы, лямбда-выражения не получают автоматически созданные конструкторы перемещения и операторы присваивания с перемещением. Дополнительные сведения о поддержке поведения ссылок rvalue см. в подразделе "Ссылки Rvalue" раздела Поддержка функций C++11 (современный C++).

  • Необязательный параметр attribute-specifier-seq не поддерживается в этой версии.

Visual Studio включает следующие функции в дополнение к функциям лямбда-выражений стандарта C++11:

  • Не имеющие состояний лямбда-выражения являются универсально преобразуемыми в указатели функций с произвольными соглашениями о вызовах.

  • Автоматически выведенные возвращаемые типы для тела лямбда-выражений, которые сложнее, чем { return expression; }, при условии, что все возвращаемые выражения относятся к одному типу. (Эта функция является частью предложенного стандарта C++14.)

Свойства лямбда-выражений

На этом рисунке грамматика сопоставляется с примером:

Structural elements of a lambda expression

  1. lambda-introducer (иначе — предложение фиксации)

  2. lambda-declarator (иначе — список параметров)

  3. mutable (иначе — отключаемая спецификация)

  4. exception-specification (иначе — спецификация исключения)

  5. trailing-return-type (иначе — возвращаемый тип)

  6. compound-statement (иначе — тело лямбда-выражения)

Предложение фиксации

Лямбда-выражение по сути представляет собой класс, конструктор и оператор вызова функции. Как и при определении класса, в лямбда-выражениях необходимо решить, будет ли полученный объект фиксировать переменные по значению или по ссылке либо вообще не будет выполнять фиксацию. Если лямбда-выражению требуется получать доступ к локальным переменным и параметрам функции, они должны быть зафиксированы. Предложение фиксации (lambda-introducer в синтаксисе стандарта) указывает, может ли тело лямбда-выражения осуществлять доступ к переменным во внешней области по значению или по ссылке. Доступ к переменным с префиксом с амперсандом (&) осуществляется по ссылке, а к переменным без префикса — по значению.

Пустое предложение фиксации ([ ]) показывает, что тело лямбда-выражения не осуществляет доступ к переменным во внешней области видимости.

Можно использовать режим фиксации по умолчанию (capture-default в синтаксисе стандарта), чтобы выполнять фиксацию неуказанных переменных по значению или по ссылке. Режим фиксации по умолчанию задается путем использования & или = в качестве первого элемента предложения фиксации. Элемент & задает доступ тела лямбда-выражения к неуказанным переменным по ссылке. Элемент = задает доступ тела лямбда-выражения к неуказанным переменным по значению. Например, если тело лямбда-выражения осуществляет доступ к внешней переменной total по ссылке, а к внешней переменной factor по значению, следующие предложения фиксации эквивалентны:

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

Распространенное заблуждение о capture-default заключается в том, что все переменные в области фиксируются независимо от того, используются они в лямбда-выражении или нет. Это не так. При использовании capture-default фиксируются только те переменные, которые упомянуты в лямбда-выражении.

Если предложение фиксации включает capture-default &, ни один identifier в параметре capture этого предложения фиксации не может иметь форму & identifier. Аналогично, если предложение фиксации включает capture-default =, ни один параметр capture этого предложения фиксации не может иметь форму = identifier. Идентификатор или this не могут использоваться более одного раза в предложении захвата. В следующем фрагменте кода приводится несколько примеров.

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
}

capture с последующим многоточием является расширением пакета, как показано в следующем примере шаблона с переменным числом аргументов:

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

Лямбда-выражения можно использовать в теле метода класса. Передайте указатель this предложению захвата, чтобы предоставить доступ к методам и членам данных включающего класса. Пример использования лямбда-выражений с методами класса см. в примере "Использование лямбда-выражения в методе" в разделе Примеры лямбда-выражений.

При использовании предложения фиксации рекомендуется помнить об этих важных аспектах, особенно при использовании лямбда-выражений с многопоточностью:

  • Фиксацию ссылок можно использовать для изменения переменных снаружи, тогда как фиксацию значений нельзя. (mutable позволяет изменять копии, но не оригиналы.)

  • Фиксация ссылок отражает изменение переменных снаружи, тогда как фиксация значений — нет.

  • Фиксация ссылки вводит зависимость от времени существования, тогда как фиксация значения не обладает зависимостями от времени существования.

Список параметров

Список параметров (lambda declarator в синтаксисе стандарта) является необязательным и похож на список параметров для функции.

Лямбда-выражение может принимать другое лямбда-выражение в качестве своего аргумента. Дополнительные сведения см. в подразделе "Лямбда-выражения высшего порядка" раздела Примеры лямбда-выражений.

Поскольку список параметров является необязательным, можно опустить пустые скобки, если аргументы не передаются в лямбда-выражение и lambda-declarator: не содержит элементы exception-specification, trailing-return-type или mutable.

Отключаемая спецификация

Как правило, оператор вызова функции лямбда-выражения является константой по значению, но ключевое слово mutable отменяет это. Он не создает изменяемые данные-члены. Отключаемая спецификация позволяет телу лямбда-выражения изменять переменные, захваченные по значению. Некоторые примеры далее в этой статье демонстрируют использование ключевого слова mutable.

Спецификация исключений

Можно использовать спецификацию исключений throw(), чтобы указать, что лямбда-выражение не создает исключений. Как и в случае с обычными функциями, компилятор Visual C++ создает предупреждение C4297, если лямбда-выражение объявляет спецификацию исключения throw(), и тело лямбда-выражения вызывает исключение, как показано ниже:

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

Для получения дополнительной информации см. Спецификации исключений.

Возвращаемый тип

Возвращаемый тип лямбда-выражения выводится автоматически. Использовать ключевое слово auto не нужно, если не указывается trailing-return-type. trailing-return-type похож на часть стандартного метода или функции, содержащую возвращаемый тип. Однако тип возвращаемого значения следует списку параметров, и необходимо включить ключевое слово -> элемента trailing-return-type перед типом возвращаемого значения.

Можно опустить часть возвращаемого типа лямбда-выражения, если тело лямбда-выражения содержит только один оператор return или лямбда-выражение не возвращает значение. Если тело лямбда-выражения содержит один оператор return, компилятор выводит тип возвращаемого значения из типа возвращаемого выражения. В противном случае компилятор выводит следующий тип возвращаемого значения: void. Рассмотрим следующие примеры кода, иллюстрирующие этот принцип.

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

Лямбда-выражение может создавать другое лямбда-выражение в качестве своего возвращаемого значения. Дополнительные сведения см. в подразделе "Лямбда-выражения высшего порядка" раздела Примеры лямбда-выражений.

Тело лямбда-выражения

Часть лямбда-выражения, содержащая его тело (compound-statement в синтаксисе стандарта), может содержать те же элементы, что и тело обычного метода или функции. Тело обычной функции и лямбда-выражения может осуществлять доступ к следующим типам переменных:

  • Параметры

  • Локально объявленные переменные

  • Данные-члены класса (при объявлении внутри класса и фиксации this)

  • Любая переменная, которая имеет статическую длительность хранения (например, глобальная переменная)

Кроме того, лямбда-выражение может осуществлять доступ к переменным, которые оно фиксирует из внешней области видимости. Переменная фиксируется явно, если она отображается в предложении фиксации лямбда-выражения. В противном случае переменная фиксируется неявно. Тело лямбда-выражения использует режим захвата по умолчанию для получения доступа к неявно зафиксированным переменным.

В следующем примере содержится лямбда-выражение, которое явно фиксирует переменную n по значению и неявно фиксирует переменную m по ссылке.

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

Результат

  

Поскольку переменная n фиксируется по значению, ее значение после вызова лямбда-выражения остается равным 0. Спецификация mutable позволяет изменять n внутри лямбда-выражения.

Несмотря на то что лямбда-выражение может фиксировать только переменные с автоматической длительностью хранения, в теле лямбда-выражения можно использовать переменные, которые имеют статическую длительность хранения. В следующем примере функция generate и лямбда-выражение используются для присвоения значения каждому элементу объекта vector. Лямбда-выражение изменяет статическую переменную для получения значения следующего элемента.

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
}

Для получения дополнительной информации см. создание.

В следующем примере кода используется функция из предыдущего примера и добавляется пример лямбда-выражения с алгоритмом STL generate_n. Это лямбда-выражение назначает элемент объекта vector сумме предыдущих двух элементов. Ключевое слово mutable используется, чтобы тело лямбда-выражения могло изменять соответствующие копии внешних переменных x и y, захваченные лямбда-выражением по значению. Поскольку лямбда-выражение захватывает исходные переменные x и y по значению, их значения остаются равными 1 после выполнения лямбда-выражения.

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

Результат

  

Для получения дополнительной информации см. generate_n.

Модификаторы, используемые в системах Майкрософт

Если вы используете модификаторы, характерные для систем Майкрософт, такие как __declspec, их можно вставить в лямбда-выражение сразу после parameter-declaration-clause, например:

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

Сведения о том, как определить поддерживается ли модификатор лямбда-выражениями, см. в соответствующей статье в разделе Модификаторы, используемые в системах Майкрософт данного документа.

См. также

Ссылки

Лямбда-выражения в C++

Примеры лямбда-выражений

создание

generate_n

for_each

Спецификации исключений

Предупреждение компилятора (уровень 1) C4297

Модификаторы, используемые в системах Microsoft