Лямбда-выражения (начиная с C++11)
Создаёт замыкание (безымянный функциональный объект, способный захватывать переменные из области видимости).
Синтаксис
Лямбда-выражения без явного списка параметров шаблона (возможно, не обобщённые)
[captures ] front-attr (необязательно) (params ) specs (необязательно) except (необязательно)back-attr (необязательно) trailing (необязательно) requires (необязательно) contract-specs (необязательно) { body } | (1) | |
[captures ] { body } | (2) | (до C++23) |
[captures ] front-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (2) | (начиная с C++23) |
[captures ] front-attr (необязательно) exceptback-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (3) | (начиная с C++23) |
[captures ] front-attr (необязательно) specs except (необязательно)back-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (4) | (начиная с C++23) |
Лямбда-выражения с явным списком параметров шаблона (всегда обобщённые) (начиная с C++20)
[captures ] <tparams > t-requires (необязательно)front-attr (необязательно) (params ) specs (необязательно) except (необязательно)back-attr (необязательно) trailing (необязательно) requires (необязательно) contract-specs (необязательно) { body } | (1) | |
[captures ] <tparams > t-requires (необязательно) { body } | (2) | (до C++23) |
[captures ] <tparams > t-requires (необязательно)front-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (2) | (начиная с C++23) |
[captures ] <tparams > t-requires (необязательно) front-attr (необязательно) exceptback-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (3) | (начиная с C++23) |
[captures ] <tparams > t-requires (необязательно) front-attr (необязательно) specs except (необязательно)back-attr (необязательно) trailing (необязательно) contract-specs (необязательно) { body } | (4) | (начиная с C++23) |
Пояснение
| captures | - | Задаёт сущности, которые будут захвачены. | ||||||||||||
| tparams | - | Непустой список параметров шаблона, разделённых запятыми, используемый для задания имён параметрам шаблона обобщённой лямбды (см. ClosureType::operator() ниже). |
||||||||||||
| t-requires | - | Добавляет ограничения для tparams.
|
||||||||||||
| front-attr | - |
(начиная с C++23) Последовательность спецификаторов атрибутов применяется к operator() типа замыкания (поэтому можно использовать атрибут [[noreturn]]). |
||||||||||||
| params | - |
Список параметров operator() типа замыкания.
|
||||||||||||
| specs | - | Список следующих спецификаторов; каждый спецификатор может встречаться в каждой последовательности не более одного раза.
|
||||||||||||
| except | - | Задаёт динамическую спецификацию исключений или(до C++17) спецификатор noexcept для operator() типа замыкания. |
||||||||||||
| back-attr | - |
Последовательность спецификаторов атрибутов применяется к типу operator() типа замыкания (поэтому атрибут [[noreturn]] использовать нельзя). |
||||||||||||
| trailing | - |
-> ret, где ret задаёт возвращаемый тип. |
||||||||||||
| requires | - |
(начиная с C++20) Добавляет ограничения для operator() типа замыкания. |
||||||||||||
| contract-specs | - |
(начиная с C++26) Список спецификаторов контрактов функции для operator() типа замыкания. |
||||||||||||
| body | - | Тело функции. |
| Если | (начиная с C++14) |
Переменная __func__ неявно определяется в начале body со смыслом, описанным здесь.
Тип замыкания
Лямбда-выражение является выражением prvalue уникального безымянного неклассового типа, не являющегося объединением или агрегатом; этот тип называется типом замыкания и объявляется (для целей ADL) в наименьшей области видимости блока, класса или пространства имён, содержащей лямбда-выражение.
| Тип замыкания является структурным тогда и только тогда, когда captures пуст. | (начиная с C++20) |
Тип замыкания имеет следующие члены; их нельзя явно инстанцировать, явно специализировать или(начиная с C++14) указывать в объявлении friend:
ClosureType::operator()(params)
ret operator()(params) { body }
| (могут присутствовать static и const, см. ниже) | |
template<template-params>
ret operator()(params) { body }
| (начиная с C++14) (обобщённая лямбда; могут присутствовать static и const, см. ниже) |
Выполняет тело лямбда-выражения при вызове. При обращении к переменной используется её захваченная копия (для сущностей, захваченных по значению) или исходный объект (для сущностей, захваченных по ссылке).
Список параметров operator() — это params, если он задан, иначе список параметров пуст.
Возвращаемый тип operator() — это тип, указанный в trailing .
Если trailing не задан, возвращаемый тип operator() автоматически выводится.[1]
Если в спецификаторах лямбды не использовано ключевое слово mutable или присутствует явный параметр объекта(начиная с C++23), cv-квалификатор operator() — const, а объекты, захваченные по значению, нельзя изменять изнутри этой operator(). Явный квалификатор const не допускается. operator() никогда не является виртуальной и не может иметь квалификатор volatile.
|
| (начиная с C++17) |
|
| (начиная с C++20) |
|
| (начиная с C++23) |
| Для каждого параметра в params, тип которого задан как auto, в template-params в порядке появления добавляется вымышленный параметр шаблона. Вымышленный параметр шаблона может быть пакетом параметров, если соответствующий параметр функции-члена в params является пакетом параметров функции. // generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
return [=](auto&&... ts) // generic lambda, ts is a parameter pack
{
printer(std::forward<decltype(ts)>(ts)...);
// nullary lambda (takes no parameters):
return [=] { printer(ts...); };
};
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
std::cout << v1 << v2 << v3;
});
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q(); // outputs 1a3.14
| (начиная с C++14) |
| Если в определении лямбды используется явный список параметров шаблона, этот список параметров шаблона применяется к // generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
return foo(std::forward<Ts>(ts)...);
};
| (начиная с C++20) |
Спецификация исключений except в лямбда-выражении применяется к operator().
Для целей поиска имён, определения типа и значения указателя this и доступа к нестатическим членам класса тело operator() типа замыкания рассматривается в контексте лямбда-выражения.
struct X
{
int x, y;
int operator()(int);
void f()
{
// the context of the following lambda is the member function X::f
[=]() -> int
{
return operator()(this->x + y); // X::operator()(this->x + (*this).y)
// this has type X*
};
}
};
Висячие ссылки
Если сущность, не являющаяся ссылкой, захвачена по ссылке — неявно или явно — и operator() объекта-замыкания вызывается после завершения времени жизни этой сущности, поведение не определено. Замыкания C++ не продлевают время жизни объектов, захваченных по ссылке.
То же относится ко времени жизни текущего объекта *this, захваченного посредством this.
- Хотя вывод возвращаемого типа функции введён в C++14, это правило применяется для вывода возвращаемого типа лямбды уже в C++11.
ClosureType::operator ret(*)(params)()
| лямбда без захватов, не являющаяся обобщённой | ||
using F = ret(*)(params); operator F() const noexcept; | (до C++17) | |
using F = ret(*)(params); constexpr operator F() const noexcept; | (начиная с C++17) | |
| обобщённая лямбда без захватов | ||
template<template-params> using fptr_t = /* see below */; template<template-params> operator fptr_t<template-params>() const noexcept; |
(начиная с C++14) (до C++17) | |
template<template-params> using fptr_t = /* see below */; template<template-params> constexpr operator fptr_t<template-params>() const noexcept; | (начиная с C++17) |
Эта пользовательская функция преобразования определяется только в том случае, если у лямбда-выражения нет captures и нет явного параметра объекта(начиная с C++23). Это открытая, constexpr-,(начиная с C++17) невиртуальная, неявная, константная функция-член noexcept объекта-замыкания.
| Эта функция является немедленно вызываемой функцией, если оператор вызова функции (или специализация для обобщённых лямбд) является немедленно вызываемой функцией. | (начиная с C++20) |
| Для обобщённой лямбды без захватов определена пользовательская функция-шаблон преобразования с тем же списком вымышленных параметров шаблона, что и у void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {} // #1
void h(char (*)(int)) {} // #2
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // error: not convertible
h(glambda); // OK: calls #1 since #2 is not convertible
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
| (начиная с C++14) |
| Значение, возвращаемое функцией преобразования, — это указатель на функцию со связыванием языка C++, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа замыкания для экземпляра типа замыкания, созданного по умолчанию. | (до C++14) |
| Значение, возвращаемое функцией (шаблоном) преобразования, — это указатель на функцию со связыванием языка C++, которая при вызове имеет тот же эффект, что и:
|
(начиная с C++14) (до C++23) |
| Значение, возвращаемое функцией (шаблоном) преобразования, представляет собой
| (начиная с C++23) |
| Эта функция является constexpr, если оператор вызова функции (или специализация для обобщённых лямбд) является constexpr. auto Fwd = [](int(*fp)(int), auto a) { return fp(a); };
auto C = [](auto a) { return a; };
static_assert(Fwd(C, 3) == 3); // OK
auto NC = [](auto a) { static int s; return a; };
static_assert(Fwd(NC, 3) == 3); // error: no specialization can be
// constexpr because of static s
Если | (начиная с C++17) |
ClosureType::ClosureType()
ClosureType() = default; | (начиная с C++20) (только если захваты не указаны) | |
ClosureType(const ClosureType&) = default; | ||
ClosureType(ClosureType&&) = default; |
| Типы замыканий не удовлетворяют требованиям DefaultConstructible. У типов замыканий нет конструктора по умолчанию. | (до C++20) |
| Если captures не указаны, тип замыкания имеет конструктор по умолчанию, объявленный как default. В противном случае у него нет конструктора по умолчанию (в том числе если задан capture-default , даже если фактически ничего не захватывается). | (начиная с C++20) |
Конструктор копирования и конструктор перемещения объявляются как default и могут быть неявно определены согласно обычным правилам для конструкторов копирования и конструкторов перемещения.
ClosureType::operator=(const ClosureType&)
ClosureType& operator=(const ClosureType&) = delete; | (до C++20) | |
ClosureType& operator=(const ClosureType&) = default; ClosureType& operator=(ClosureType&&) = default; | (начиная с C++20) (только если захваты не указаны) | |
ClosureType& operator=(const ClosureType&) = delete; | (начиная с C++20) (в противном случае) |
| Оператор присваивания копированием определяется как удалённый (оператор присваивания перемещением не объявляется). Типы замыканий не удовлетворяют требованиям CopyAssignable. | (до C++20) |
| Если captures не указаны, тип замыкания имеет оператор присваивания копированием и оператор присваивания перемещением, объявленные как default. В противном случае у него есть удалённый оператор присваивания копированием (в том числе если задан capture-default , даже если фактически ничего не захватывается). | (начиная с C++20) |
ClosureType::~ClosureType()
~ClosureType() = default; |
Деструктор объявляется неявно.
ClosureType::Captures
T1 a; T2 b; ... |
Если лямбда-выражение захватывает что-либо по значению (неявно — с помощью предложения захвата [=] или явно — с помощью захвата, не содержащего символ &, например [a, b, c]), тип замыкания включает безымянные нестатические члены данных, объявленные в неопределённом порядке и хранящие копии всех захваченных таким образом сущностей.
Члены данных, соответствующие захватам без инициализаторов, инициализируются напрямую при вычислении лямбда-выражения. Члены данных, соответствующие захватам с инициализаторами, инициализируются согласно требованиям инициализатора (копирующей или прямой инициализацией). Если захватывается массив, его элементы инициализируются напрямую в порядке возрастания индексов. Члены данных инициализируются в порядке их объявления (который не определён).
Тип каждого члена данных совпадает с типом соответствующей захваченной сущности, за исключением случая, когда сущность имеет ссылочный тип (в этом случае ссылки на функции захватываются как ссылки lvalue на соответствующие функции, а ссылки на объекты захватываются путём копирования соответствующих объектов).
Для сущностей, захваченных по ссылке (с помощью capture-default [&] или при использовании символа &, например [&a, &b, &c]), не определено, объявляются ли в типе замыкания дополнительные члены данных, однако любые такие дополнительные члены должны удовлетворять требованиям LiteralType(начиная с C++17).
| Лямбда-выражения не допускаются в невычисляемых выражениях, аргументах шаблонов, объявлениях псевдонимов, объявлениях typedef и в любом месте объявления функции (или шаблона функции), кроме тела функции и её аргументов по умолчанию. | (до C++20) |
Захват в лямбда-выражении
captures определяет внешние переменные, доступные из тела лямбда-функции. Его синтаксис определяется следующим образом:
| capture-default | (1) | |
| capture-list | (2) | |
capture-default , capture-list | (3) |
| capture-default | - | одно из & и = |
| capture-list | - | список capture ов, разделённых запятыми |
Синтаксис capture определяется следующим образом:
| identifier | (1) | |
identifier ... | (2) | |
| identifier initializer | (3) | (начиная с C++14) |
& identifier | (4) | |
& identifier ... | (5) | |
& identifier initializer | (6) | (начиная с C++14) |
this | (7) | |
* this | (8) | (начиная с C++17) |
... identifier initializer | (9) | (начиная с C++20) |
& ... identifier initializer | (10) | (начиная с C++20) |
Если capture-default — &, последующие простые захваты не должны начинаться с &.
struct S2 { void f(int i); };
void S2::f(int i)
{
[&] {}; // OK: by-reference capture default
[&, i] {}; // OK: by-reference capture, except i is captured by copy
[&, &i] {}; // Error: by-reference capture when by-reference is the default
[&, this] {}; // OK, equivalent to [&]
[&, this, i] {}; // OK, equivalent to [&, i]
}
Если capture-default — =, последующие простые захваты должны начинаться с & или быть *this(начиная с C++17) или this(начиная с C++20).
struct S2 { void f(int i); };
void S2::f(int i)
{
[=] {}; // OK: by-copy capture default
[=, &i] {}; // OK: by-copy capture, except i is captured by reference
[=, *this] {}; // until C++17: Error: invalid syntax
// since C++17: OK: captures the enclosing S2 by copy
[=, this] {}; // until C++20: Error: this when = is the default
// since C++20: OK, same as [=]
}
Каждый захват может встречаться только один раз, а его имя должно отличаться от имени любого параметра:
struct S2 { void f(int i); };
void S2::f(int i)
{
[i, i] {}; // Error: i repeated
[this, *this] {}; // Error: "this" repeated (C++17)
[i] (int i) {}; // Error: parameter and capture have the same name
}
Лямбда-выражение может использовать переменную без её захвата, если переменная
- является нелокальной или имеет статическую длительность хранения либо длительность хранения потока выполнения (в этом случае переменную нельзя захватить), или
- является ссылкой, инициализированной константным выражением.
Лямбда-выражение может считывать значение переменной без её захвата, если переменная
- имеет целочисленный или перечислимый тип const без volatile и была инициализирована константным выражением, или
- является constexpr и не имеет изменяемых членов.
Текущий объект (*this) может быть захвачен неявно, если указано значение по умолчанию для захвата. При неявном захвате он всегда захватывается по ссылке, даже если значением по умолчанию для захвата является =. Неявный захват *this, если значением по умолчанию для захвата является =, объявлен устаревшим.(начиная с C++20)
Только лямбда-выражения, удовлетворяющие одному из следующих условий, могут иметь capture-default или capture без инициализаторов:
- Его самая внутренняя охватывающая область видимости является блочной областью видимости.
- Оно находится в инициализаторе члена по умолчанию, а его самая внутренняя охватывающая область видимости является соответствующей областью видимости класса.
| (начиная с C++26) |
Для такого лямбда-выражения охватывающая область определяется как множество охватывающих областей видимости вплоть до самой внутренней охватывающей функции включительно (а также её параметров). Сюда входят вложенные блочные области видимости и области видимости охватывающих лямбда-выражений, если данное лямбда-выражение вложено.
Поиск identifier в любом захвате без инициализатора (кроме захвата this) выполняется с помощью обычного поиска неквалифицированного имени в охватывающей области лямбда-выражения. Результатом поиска должна быть переменная с автоматической длительностью хранения, объявленная в охватывающей области, либо структурированное связывание, соответствующая переменная которого удовлетворяет этим требованиям(начиная с C++20). Сущность захватывается явно.
| Захват с инициализатором, называемый захватом с инициализацией, действует так, как если бы он объявлял и явно захватывал переменную со спецификатором типа
Это позволяет захватывать типы, допускающие только перемещение, например с помощью захвата Это также позволяет захватывать по константной ссылке, используя int x = 4;
auto y = [&r = x, x = x + 1]() -> int
{
r += 2;
return x * x;
}(); // updates ::x to 6 and initializes y to 25.
| (начиная с C++14) |
Если captures содержит capture-default и явно не захватывает охватывающий объект (как this или *this) либо автоматическую переменную, пригодную для использования по правилу ODR в теле лямбда-выражения, либо структурированное связывание, соответствующая переменная которого имеет автоматическую длительность хранения(начиная с C++20), эта сущность захватывается неявно, если она названа в потенциально вычисляемом выражении (в том числе когда перед использованием нестатического члена класса неявно добавляется this->).
При определении неявных захватов typeid никогда не считается превращающим свои операнды в невычисляемые.
| Сущности могут захватываться неявно, даже если они названы только в отброшенном операторе после инстанцирования тела лямбда-выражения. | (начиная с C++17) |
void f(int, const int (&)[2] = {}) {} // #1
void f(const int&, const int (&)[1]) {} // #2
struct NoncopyableLiteralType
{
constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
int n_;
};
void test()
{
const int x = 17;
auto l0 = []{ f(x); }; // OK: calls #1, does not capture x
auto g0 = [](auto a) { f(x); }; // same as above
auto l1 = [=]{ f(x); }; // OK: captures x (since P0588R1) and calls #1
// the capture can be optimized away
auto g1 = [=](auto a) { f(x); }; // same as above
auto ltid = [=]{ typeid(x); }; // OK: captures x (since P0588R1)
// even though x is unevaluated
// the capture can be optimized away
auto g2 = [=](auto a)
{
int selector[sizeof(a) == 1 ? 1 : 2] = {};
f(x, selector); // OK: is a dependent expression, so captures x
};
auto g3 = [=](auto a)
{
typeid(a + x); // captures x regardless of
// whether a + x is an unevaluated operand
};
constexpr NoncopyableLiteralType w{42};
auto l4 = []{ return w.n_; }; // OK: w is not odr-used, capture is unnecessary
// auto l5 = [=]{ return w.n_; }; // error: w needs to be captured by copy
}
Если тело лямбда-выражения выполняет использование по правилу ODR сущности, захваченной по значению, обращение происходит к члену типа замыкания. Если сущность не используется по правилу ODR, обращение происходит к исходному объекту:
void f(const int*);
void g()
{
const int N = 10;
[=]
{
int arr[N]; // not an odr-use: refers to g's const int N
f(&N); // odr-use: causes N to be captured (by copy)
// &N is the address of the closure object's member N, not g's N
}();
}
Если лямбда-выражение использует по правилу ODR ссылку, захваченную по ссылке, оно использует объект, на который ссылается исходная ссылка, а не саму захваченную ссылку:
#include <iostream>
auto make_function(int& x)
{
return [&] { std::cout << x << '\n'; };
}
int main()
{
int i = 3;
auto f = make_function(i); // the use of x in f binds directly to i
i = 5;
f(); // OK: prints 5
}
В теле лямбда-выражения со значением по умолчанию для захвата = тип любой захватываемой сущности определяется так, как если бы она была захвачена (поэтому часто добавляется квалификатор const, если лямбда-выражение не является mutable), даже если сущность находится в невычисляемом операнде и не захвачена (например, в decltype):
void f3()
{
float x, &r = x;
[=]
{ // x and r are not captured (appearance in a decltype operand is not an odr-use)
decltype(x) y1; // y1 has type float
decltype((x)) y2 = y1; // y2 has type float const& because this lambda
// is not mutable and x is an lvalue
decltype(r) r1 = y1; // r1 has type float& (transformation not considered)
decltype((r)) r2 = y2; // r2 has type float const&
};
}
Любая сущность, захваченная лямбда-выражением (неявно или явно), используется этим лямбда-выражением по правилу ODR (поэтому неявный захват вложенным лямбда-выражением вызывает неявный захват во внешнем лямбда-выражении).
Все неявно захваченные переменные должны быть объявлены в охватывающей области лямбда-выражения.
Если лямбда-выражение захватывает охватывающий объект (как this или *this), ближайшая охватывающая функция должна быть нестатической функцией-членом либо лямбда-выражение должно находиться в инициализаторе члена по умолчанию:
struct s2
{
double ohseven = .007;
auto f() // nearest enclosing function for the following two lambdas
{
return [this] // capture the enclosing s2 by reference
{
return [*this] // capture the enclosing s2 by copy (C++17)
{
return ohseven; // OK
}
}();
}
auto g()
{
return [] // capture nothing
{
return [*this] {}; // error: *this not captured by outer lambda expression
}();
}
};
Если лямбда-выражение (или специализация оператора вызова функции обобщённой лямбды)(начиная с C++14) использует по правилу ODR *this или любую переменную с автоматической длительностью хранения, эта переменная должна быть захвачена лямбда-выражением.
void f1(int i)
{
int const N = 20;
auto m1 = [=]
{
int const M = 30;
auto m2 = [i]
{
int x[N][M]; // N and M are not odr-used
// (ok that they are not captured)
x[0][0] = i; // i is explicitly captured by m2
// and implicitly captured by m1
};
};
struct s1 // local class within f1()
{
int f;
void work(int n) // non-static member function
{
int m = n * n;
int j = 40;
auto m3 = [this, m]
{
auto m4 = [&, j] // error: j is not captured by m3
{
int x = n; // error: n is implicitly captured by m4
// but not captured by m3
x += m; // OK: m is implicitly captured by m4
// and explicitly captured by m3
x += i; // error: i is outside of the reaching scope
// (which ends at work())
x += f; // OK: this is captured implicitly by m4
// and explicitly captured by m3
};
};
}
};
}
Члены класса нельзя захватывать явно посредством захвата без инициализатора (как упоминалось выше, в capture-list допускаются только переменные):
class S
{
int x = 0;
void f()
{
int i = 0;
// auto l1 = [i, x] { use(i, x); }; // error: x is not a variable
auto l2 = [i, x = x] { use(i, x); }; // OK, copy capture
i = 1; x = 1; l2(); // calls use(0,0)
auto l3 = [i, &x = x] { use(i, x); }; // OK, reference capture
i = 2; x = 2; l3(); // calls use(1,2)
}
};
Когда лямбда-выражение захватывает член посредством неявного захвата по значению, оно не создаёт копию этой переменной-члена: использование переменной-члена m рассматривается как выражение (*this).m, а *this всегда неявно захватывается по ссылке:
class S
{
int x = 0;
void f()
{
int i = 0;
auto l1 = [=] { use(i, x); }; // captures a copy of i and
// a copy of the this pointer
i = 1; x = 1; l1(); // calls use(0, 1), as if
// i by copy and x by reference
auto l2 = [i, this] { use(i, x); }; // same as above, made explicit
i = 2; x = 2; l2(); // calls use(1, 2), as if
// i by copy and x by reference
auto l3 = [&] { use(i, x); }; // captures i by reference and
// a copy of the this pointer
i = 3; x = 2; l3(); // calls use(3, 2), as if
// i and x are both by reference
auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this,
// including a copy of x
i = 4; x = 4; l4(); // calls use(3, 2), as if
// i and x are both by copy
}
};
Если лямбда-выражение находится в аргументе по умолчанию, оно не может явно или неявно захватывать что-либо, если только все захваты не имеют инициализаторов, удовлетворяющих ограничениям для выражения в аргументе по умолчанию(начиная с C++14):
void f2()
{
int i = 1;
void g1( int = [i] { return i; }() ); // error: captures something
void g2( int = [i] { return 0; }() ); // error: captures something
void g3( int = [=] { return i; }() ); // error: captures something
void g4( int = [=] { return 0; }() ); // OK: capture-less
void g5( int = [] { return sizeof i; }() ); // OK: capture-less
// C++14
void g6( int = [x = 1] { return x; }() ); // OK: 1 can appear
// in a default argument
void g7( int = [x = i] { return x; }() ); // error: i cannot appear
// in a default argument
}
Члены анонимных объединений нельзя захватывать. Битовые поля можно захватывать только по значению.
Если вложенное лямбда-выражение m2 захватывает что-либо, что также захватывается непосредственно охватывающим лямбда-выражением m1, захват m2 преобразуется следующим образом:
- если охватывающее лямбда-выражение
m1захватывает по значению,m2захватывает нестатический член типа замыканияm1, а не исходную переменную или*this; еслиm1не является mutable, нестатический член данных считается имеющим квалификатор const. - если охватывающее лямбда-выражение
m1захватывает по ссылке,m2захватывает исходную переменную или*this.
#include <iostream>
int main()
{
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable
{
auto m2 = [a, b, &c]() mutable
{
std::cout << a << b << c << '\n';
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2();
};
a = 2; b = 2; c = 2;
m1(); // calls m2() and prints 123
std::cout << a << b << c << '\n'; // prints 234
} | Если лямбда-выражение что-либо захватывает, тип явного параметра объекта (если он есть) оператора вызова функции может быть только
struct C
{
template<typename T>
C(T);
};
void func(int i)
{
int x = [=](this auto&&) { return i; }(); // OK
int y = [=](this C) { return i; }(); // error
int z = [](this C) { return 42; }(); // OK
auto lambda = [n = 42] (this auto self) { return n; };
using Closure = decltype(lambda);
struct D : private Closure {
D(Closure l) : Closure(l) {}
using Closure::operator();
friend Closure;
};
D{lambda}(); // error
} | (начиная с C++23) |
Примечания
| Макрос проверки поддержки функции | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_lambdas | 200907L | (C++11) | Лямбда-выражения |
__cpp_generic_lambdas | 201304L | (C++14) | Обобщённые лямбда-выражения |
201707L | (C++20) | Явный список параметров шаблона для обобщённых лямбда-выражений | |
__cpp_init_captures | 201304L | (C++14) | Захват с инициализацией в лямбда-выражении |
201803L | (C++20) | Разрешение развёртки пакета в захвате с инициализацией лямбда-выражения | |
__cpp_capture_star_this | 201603L | (C++17) | Захват *this лямбда-выражением по значению с помощью [=, *this] |
__cpp_constexpr | 201603L | (C++17) | Лямбда-выражение constexpr |
__cpp_static_call_operator | 202207L | (C++23) | static operator() для лямбда-выражений без захватов |
Правило неявного захвата в лямбда-выражениях было немного изменено отчётом об исправлении P0588R1. По состоянию на 2023-10 некоторые основные реализации ещё не полностью реализовали это исправление, поэтому в некоторых случаях всё ещё применяется старое правило, выявляющее использование по правилу ODR.
| Старое правило до P0588R1 | ||
|---|---|---|
| Если captures содержит capture-default и явно не захватывает охватывающий объект (как
|
Пример
В этом примере показано, как передать лямбда-выражение обобщённому алгоритму и как объекты, полученные в результате лямбда-выражения, могут храниться в объектах std::function.
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> c{1, 2, 3, 4, 5, 6, 7};
int x = 5;
c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
std::cout << "c: ";
std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
std::cout << '\n';
// the type of a closure cannot be named, but can be inferred with auto
// since C++14, lambda could own default arguments
auto func1 = [](int i = 6) { return i + 4; };
std::cout << "func1: " << func1() << '\n';
// like all callable objects, closures can be captured in std::function
// (this may incur unnecessary overhead)
std::function<int(int)> func2 = [](int i) { return i + 4; };
std::cout << "func2: " << func2(6) << '\n';
constexpr int fib_max {8};
std::cout << "Emulate `recursive lambda` calls:\nFibonacci numbers: ";
auto nth_fibonacci = [](int n)
{
std::function<int(int, int, int)> fib = [&](int n, int a, int b)
{
return n ? fib(n - 1, a + b, a) : b;
};
return fib(n, 0, 1);
};
for (int i{1}; i <= fib_max; ++i)
std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n");
std::cout << "Alternative approach to lambda recursion:\nFibonacci numbers: ";
auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int
{
return n ? self(self, n - 1, a + b, a) : b;
};
for (int i{1}; i <= fib_max; ++i)
std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n");
#ifdef __cpp_explicit_this_parameter
std::cout << "C++23 approach to lambda recursion:\n";
auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int
{
return n ? self(n - 1, a + b, a) : b;
};
for (int i{1}; i <= fib_max; ++i)
std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n");
#endif
} Возможный вывод:
c: 5 6 7 func1: 10 func2: 10 Emulate `recursive lambda` calls: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13 Alternative approach to lambda recursion: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| Отчёт об ошибке | Применён к | Опубликованное поведение | Исправленное поведение |
|---|---|---|---|
| CWG 974 | C++11 | аргумент по умолчанию не допускался в списке параметров лямбда-выражения | разрешён |
|
CWG 1048 (N3638) | C++11 | тип возвращаемого значения можно было вывести только для тел лямбда-выражений, содержащих единственный оператор return | улучшен вывод типа возвращаемого значения |
| CWG 1249 | C++11 | не было ясно, считается ли захваченный член охватывающего лямбда-выражения без mutable константным | считается константным |
| CWG 1557 | C++11 | языковая связь возвращаемого типа функции преобразования типа замыкания не была определена | имеет языковую связь C++ |
| CWG 1607 | C++11 | лямбда-выражения могли находиться в сигнатурах функций и шаблонов функций | не допускаются |
| CWG 1612 | C++11 | члены анонимных объединений могли захватываться | не допускается |
| CWG 1722 | C++11 | спецификация исключений функции преобразования лямбда-выражений без захватов не была определена | функция преобразования имеет noexcept |
| CWG 1772 | C++11 | семантика __func__ в теле лямбда-выражения была неясна | ссылается на operator() класса замыкания |
| CWG 1780 | C++14 | было неясно, можно ли явно инстанцировать или явно специализировать члены типов замыкания обобщённых лямбда-выражений | не допускается ни то, ни другое |
| CWG 1891 | C++11 | замыкание имело удалённый конструктор по умолчанию и неявные конструкторы копирования/перемещения | нет конструктора по умолчанию; конструкторы копирования/перемещения объявлены defaulted |
| CWG 1937 | C++11 | для эффекта вызова результата функции преобразования не было определено, вызов какого объекта его operator() даёт такой же эффект | экземпляра типа замыкания, созданного конструктором по умолчанию |
| CWG 1973 | C++11 | список параметров operator() типа замыканиямог ссылаться на список параметров, указанный в trailing | может ссылаться только на params |
| CWG 2011 | C++11 | для ссылки, захваченной по ссылке, не было определено, на какую сущность указывает идентификатор захвата | указывает на исходную сущность, на которую ссылались |
| CWG 2095 | C++11 | поведение при захвате rvalue-ссылок на функции по значению было неясно | прояснено |
| CWG 2211 | C++11 | поведение не было определено, если захват имел то же имя, что и параметр | в этом случае программа некорректна |
| CWG 2358 | C++14 | лямбда-выражения в аргументах по умолчанию должны были быть без захватов, даже если все захваты инициализировались выражениями, допустимыми в аргументах по умолчанию | разрешены такие лямбда-выражения с захватами |
| CWG 2509 | C++17 | каждый спецификатор мог встречаться в последовательности спецификаторов несколько раз | каждый спецификатор может встречаться в последовательности спецификаторов не более одного раза |
| CWG 2561 | C++23 | лямбда-выражения с явным параметром объекта могли иметь функцию преобразования в нежелательный тип указателя на функцию | такая функция преобразования отсутствует |
| CWG 2881 | C++23 | operator() с явным параметром мог инстанцироваться для производного класса при непубличном или неоднозначном наследовании | сделано некорректным |
| P0588R1 | C++11 | правило неявного захвата лямбда-выражением выявляло использование по правилу ODR | обнаружение упрощено |
См. также
спецификатор auto (C++11) | задаёт тип, выводимый из выражения |
|
(C++11) | копируемая обёртка для любого вызываемого объекта, допускающего копирование (шаблон класса) |
|
(C++23) | обёртка, допускающая только перемещение, для любого вызываемого объекта, поддерживающего квалификаторы заданной сигнатуры вызова (шаблон класса) |
Внешние ссылки
| Вложенная функция — функция, определённая внутри другой (охватывающей) функции. |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/lambda