Lambda-выражения (с C++11)
Создаёт замыкание: безымянный объект функции, способный захватывать переменные в области видимости.
Синтаксис
Lambda-выражения без явного списка параметров шаблона (возможно, не-генерические)
[захватывания ] front-attr (необязательно) (параметры ) спецификации (необязательно) исключение (необязательно)back-attr (необязательно) trailing-type (необязательно) требует (необязательно) { тело } | (1) | |
[захватывания ] { тело } | (2) | (до C++23) |
[захватывания ] front-attr (необязательно) trailing-type (необязательно) { тело } | (2) | (с C++23) |
[захватывания ] front-attr (необязательно) исключениеback-attr (необязательно) trailing-type (необязательно) { тело } | (3) | (с C++23) |
[захватывания ] front-attr (необязательно) спецификации исключение (необязательно)back-attr (необязательно) trailing-type (необязательно) { тело } | (4) | (с C++23) |
Lambda-выражения с явным списком параметров шаблона (всегда генерические) (с C++20)
[захватывания ] <tparams > t-требует (необязательно) front-attr (необязательно) (параметры ) спецификации (необязательно)исключение (необязательно) back-attr (необязательно) trailing-type (необязательно) требует (необязательно) { тело } | (1) | |
[захватывания ] <tparams > t-требует (необязательно) { тело } | (2) | (до C++23) |
[захватывания ] <tparams > t-требует (необязательно)front-attr (необязательно) trailing-type (необязательно) { тело } | (2) | (с C++23) |
[захватывания ] <tparams > t-требует (необязательно) front-attr (необязательно) исключениеback-attr (необязательно) trailing-type (необязательно) { тело } | (3) | (с C++23) |
[захватывания ] <tparams > t-требует (необязательно) front-attr (необязательно) спецификации исключение (необязательно)back-attr (необязательно) trailing-type (необязательно) { тело } | (4) | (с C++23) |
Объяснение
| captures | - | список захватов, разделённых запятыми, нулевой или более, необязательно начинающийся со capture-default. Подробное описание захватов см. ниже. Выражение лямбда может использовать переменную без захвата, если переменная
Выражение лямбда может считывать значение переменной без захвата, если переменная
|
||||||
| tparams | - | список параметров шаблона, разделённых запятыми, непустой, используемый для присвоения имён параметрам шаблона в лямбда-выражении общего вида (см. ClosureType::operator() ниже) |
||||||
| t-requires | - | добавляет ограничения к tparams
|
||||||
| front-attr | - | (с C++23) последовательность спецификаторов атрибутов применяется к operator() типа замыкания (и, следовательно, можно использовать атрибут [[noreturn]]) |
||||||
| params | - | список параметров operator() типа замыкания |
||||||
| specs | - | Список следующих спецификаторов, каждый спецификатор разрешён не более одного раза в каждой последовательности. Если не указано, объекты, захваченные по копированию, являются const в теле лямбда-выражения.
|
||||||
| exception | - | предоставляет динамическое спецификацию исключений или(до C++20) спецификатор noexcept для operator() типа замыкания |
||||||
| back-attr | - | последовательность спецификаторов атрибутов применяется к типу operator() типа замыкания (и, следовательно, нельзя использовать атрибут [[noreturn]]) |
||||||
| trailing-type | - | -> ret, где ret указывает тип возвращаемого значения |
||||||
| requires | - | (с C++20) добавляет ограничения к operator() типа замыкания |
||||||
| body | - | тело функции |
| Если | (с C++14) |
Переменная __func__ неявно определяется в начале body, с семантикой, как описано здесь.
Выражение лямбда является выражением prvalue уникального безымянного не-union не-агрегатного не-структурного типа класса, известного как тип замыкания, который объявляется (для целей ADL) в самом маленьком блоке области видимости, области видимости класса или области видимости пространства имён, содержащем выражение лямбда. Тип замыкания имеет следующие члены, их нельзя явно инициализировать, явно специализировать, или(с 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-type.
| Если trailing-type не указан, тип возвращаемого значения
| (до C++14) |
| Если trailing-type не указан или указан как | (с C++14) |
Если ключевое слово mutable не использовалось в спецификаторах лямбда-выражения, 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) |
Спецификация исключений exception в лямбда-выражении применяется к 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.
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) |
Эта функция пользовательского преобразования определена только если список захватов лямбда-выражения пустой. Это публичная, constexpr,(с C++17) не виртуальная, не явная, const 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) |
| Если не указаны захватчики, у типа замыкания есть конструктор по умолчанию по умолчанию. В противном случае у него нет конструктора по умолчанию (включая случай с захват-по-умолчанию, даже если он ничего фактически не захватывает). | (с C++20) |
Конструктор копирования и конструктор перемещения объявлены как по умолчанию и могут быть неявно определены в соответствии с обычными правилами для конструкторов копирования и конструкторов перемещения.
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) |
| Если не указаны захватчики, у типа замыкания есть оператор присваивания по копированию по умолчанию и оператор присваивания по перемещению по умолчанию. В противном случае у него есть удалённый оператор присваивания по копированию (включая случай с захват-по-умолчанию, даже если он ничего фактически не захватывает). | (с C++20) |
ClosureType::~ClosureType()
~ClosureType() = default; |
Деструктор неявно объявлен.
ClosureType::Захваты
T1 a; T2 b; ... |
Если лямбда-выражение захватывает что-либо по значению (неявно с оператором захвата [=] или явно с захватом, не содержащим символа &, например, [a, b, c]), тип замыкания включает неопределённые нестатические члены данных, объявленные в неопределённом порядке, которые содержат копии всех сущностей, которые были так захвачены.
Эти члены данных, соответствующие захватам без инициализаторов, непосредственно инициализируются при вычислении лямбда-выражения. Те, которые соответствуют захватам с инициализаторами, инициализируются так, как требует инициализатор (может быть копирующая или прямая инициализация). Если захвачен массив, элементы массива инициализируются непосредственно в порядке возрастания индекса. Порядок инициализации членов данных — порядок их объявления (который не определён).
Тип каждого члена данных — тип соответствующей захваченной сущности, за исключением случаев, когда сущность имеет тип ссылки (в этом случае ссылки на функции захватываются как ссылки lvalue на целевые функции, а ссылки на объекты захватываются как копии целевых объектов).
Для сущностей, которые захватываются по ссылке (с захватом по умолчанию [&] или с использованием символа &, например, [&a, &b, &c]), не определено, объявляются ли дополнительные члены данных в типе замыкания, но все такие дополнительные члены должны удовлетворять LiteralType(с C++17).
| Лямбда-выражения не допускаются в невычисляемых выражениях, аргументах шаблонов, объявлениях псевдонимов типов, объявлениях typedef и где угодно в объявлении функции (или шаблона функции), кроме тела функции и аргументов по умолчанию функции. | (до C++20) |
Захват лямбда-выражения
Список захватов — это список из нуля или более захватов, разделенных запятыми, и, необязательно, начинающийся со захват-по-умолчанию. Список захватов определяет внешние переменные, доступные внутри тела лямбда-функции. Единственные захваты по умолчанию —
-
&(неявно захватывает используемые переменные со сроком существования автоматического хранения по ссылке) и -
=(неявно захватывает используемые переменные со сроком существования автоматического хранения по значению).
Текущий объект (*this) может быть неявно захвачен, если присутствует любой из захватов по умолчанию. Если захвачен неявно, он всегда захватывается по ссылке, даже если захват по умолчанию =. Неявный захват *this при захвате по умолчанию = устарел.(с C++20)
Синтаксис отдельного захвата в списке захватов:
| идентификатор | (1) | |
идентификатор ... | (2) | |
| идентификатор инициализатор | (3) | (с C++14) |
& идентификатор | (4) | |
& идентификатор ... | (5) | |
& идентификатор инициализатор | (6) | (с C++14) |
this | (7) | |
* this | (8) | (с C++17) |
... идентификатор инициализатор | (9) | (с C++20) |
& ... идентификатор инициализатор | (10) | (с C++20) |
Если захват по умолчанию &, последующие простые захваты не должны начинаться с &.
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]
}Если захват по умолчанию =, последующие простые захваты должны начинаться с & или быть *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
}Только лямбда-выражения, определённые в области блока или в инициализаторе по умолчанию, могут иметь по умолчанию захват или захваты без инициализаторов. Для таких лямбда-выражений областью досягаемости считается набор окружающих областей до и включая самую внутреннюю окружающую функцию (и её параметры). Это включает вложенные области блока и области окружающих лямбда-выражений, если данная лямбда вложена.
Идентификатор в любом захвате без инициализатора (кроме захвата 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) |
Если список захватов содержит захват по умолчанию и не захватывает явно окружающий объект (как 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
}Если тело лямбда-выражения использует сущность, захваченную по копированию, обращение происходит к члену типа замыкания. Если сущность не используется, то обращение происходит к исходному объекту:
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
}();
}Если лямбда использует ссылку, захваченную по ссылке, используется объект, на который ссылается исходная ссылка, а не сама захваченная ссылка:
#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
}В теле лямбда-выражения с захватом по умолчанию =, тип любой захватываемой сущности такой, как будто она была захвачена (и, следовательно, часто добавляется константная квалификация, если лямбда не является мутабельной), даже если сущность является невычисляемым операндом и не захватывается (например, в 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&
};
}Любая сущность, захваченная лямбда-выражением (явным или неявным образом), используется лямбда-выражением (поэтому неявный захват вложенной лямбдой вызывает неявный захват в окружающем лямбда-выражении).
Все неявные переменные, захваченные лямбда-выражением, должны быть объявлены в области досягаемости лямбда-выражения.
Если лямбда захватывает окружающий объект (как 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) использует *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
};
};
}
};
}Члены классов не могут быть явно захвачены захватом без инициализатора (как упоминалось выше, в списке захватов разрешены только переменные):
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
} | (с C++11) |
Члены анонимных объединений не могут быть захвачены. Битовые поля могут быть захвачены только по копированию.
Если вложенная лямбда m2 захватывает что-то, что также захвачено непосредственно окружающим лямбда-выражением m1, то захват m2 преобразуется следующим образом:
- если окружающая лямбда
m1захватывает по копированию,m2захватывает нестатическое поле типа замыканияm1, а не исходную переменную или*this; еслиm1не мутабельная, нестатическое поле считается константным. - если окружающая лямбда
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
}Примечания
| Макрос проверки функций | Значение | Стандарт | Функция |
|---|---|---|---|
__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) | статический operator() для лямбда-выражений без захватов |
Правило для неявного захвата лямбда-выражений немного изменено отчётом об ошибке P0588R1. По состоянию на 2023-10 год некоторые основные реализации не полностью реализовали DR, и поэтому старое правило, которое обнаруживает использование ODR, всё ещё используется в некоторых случаях.
END_OF_DOCUMENT_MARKER| Старое правило до P0588R1 | ||
|---|---|---|
| Если список захватов имеет захват по умолчанию и не явно захватывает окружающий объект (как
|
Пример
Этот пример демонстрирует, как передать лямбда-выражение в общий алгоритм и как объекты, полученные из выражения лямбда, могут быть сохранены в 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++.
| DR | Применяется к | Поведение, как опубликовано | Корректное поведение |
|---|---|---|---|
| CWG 974 | C++11 | аргумент по умолчанию не разрешался в списке параметров выражения лямбда | разрешено |
| CWG 1249 | C++11 | неясно, считается ли захваченная переменная окружения не-мутирующего лямбда-выражения const или нет | считается const |
| CWG 1557 | C++11 | не был указан языковой интерфейс возвращаемого типа функции функции преобразования типа закрытия | он имеет языковой интерфейс C++ |
| CWG 1607 | C++11 | выражения лямбда могли появляться в сигнатурах функций и шаблонов функций | не разрешено |
| CWG 1612 | C++11 | члены анонимных объединений могли быть захвачены | не разрешено |
| CWG 1722 | C++11 | функция преобразования для лямбда-выражений без захватов имела неопределённую спецификацию исключений | функция преобразования является noexcept |
| CWG 1772 | C++11 | семантика __func__ в теле лямбда-выражения не была ясна | она относится к оператору() класса закрытия |
| CWG 1780 | C++14 | не было ясно, могут ли члены типов закрытия генерических лямбда-выражений быть явно инстанцированы или явно специализированы | ни то, ни другое не разрешено |
| CWG 1891 | C++11 | у закрытия был удалён конструктор по умолчанию и неявные конструкторы копирования/перемещения | нет конструктора по умолчанию и конструкторы копирования/перемещения по умолчанию |
| CWG 1937 | C++11 | что касается эффекта вызова результата функции преобразования, не было указано, на какой объект вызов её operator() имеет тот же эффект | на экземпляр типа закрытия, созданный по умолчанию |
| CWG 1973 | C++11 | список параметров типа закрытия operator() мог ссылаться на список параметров, указанный в trailing-type | может ссылаться только на params |
| CWG 2011 | C++11 | для ссылки, захваченной по ссылке, не было указано, на какую сущность ссылается идентификатор захвата | он ссылается на первоначально ссылаемую сущность |
| CWG 2095 | C++11 | поведение захвата ссылок на значения функций копированием не было ясно | определено |
| CWG 2211 | C++11 | поведение было неопределенным, если у захвата было такое же имя, как и у параметра | программа в этом случае неправильна |
| CWG 2358 | C++14 | лямбда-выражения, используемые в аргументах по умолчанию, должны были быть без захватов, даже если все захваты инициализированы выражениями, которые могут быть в аргументах по умолчанию | разрешить такие лямбда-выражения с захватами |
| CWG 2509 | C++17 | каждый спецификатор мог иметь несколько повторений в последовательности спецификаторов | каждый спецификатор может встречаться только один раз в последовательности спецификаторов |
| CWG 2542 | C++11 | не было ясно, является ли тип закрытия структурным | он не является структурным |
| 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/w/cpp/language/lambda