Синтаксис лямбда-выражений
В этой статье демонстрируется синтаксис и структурные элементы лямбда-выражений. Описание лямбда-выражений см. в разделе Лямбда-выражения в 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.)
Свойства лямбда-выражений
На этом рисунке грамматика сопоставляется с примером:
lambda-introducer (иначе — предложение фиксации)
lambda-declarator (иначе — список параметров)
mutable (иначе — отключаемая спецификация)
exception-specification (иначе — спецификация исключения)
trailing-return-type (иначе — возвращаемый тип)
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; };
Сведения о том, как определить поддерживается ли модификатор лямбда-выражениями, см. в соответствующей статье в разделе Модификаторы, используемые в системах Майкрософт данного документа.