Lambda expressions (since C++11)
Создаёт closure (безымянный функциональный объект, способный захватывать переменные из области видимости).
Содержание |
Синтаксис
Лямбда-выражения без явного списка параметров шаблона (возможно, негенерические)
[
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
(необязательно)
except
back-attr (необязательно) trailing (необязательно) contract-specs (необязательно)
{
body
}
|
(3) | (начиная с C++23) | |||||||
[
captures
]
front-attr
(необязательно)
specs
except
(необязательно)
back-attr (необязательно) trailing (необязательно) contract-specs (необязательно)
{
body
}
|
(4) | (начиная с C++23) | |||||||
Лямбда-выражения с явным списком параметров шаблона (всегда обобщённые) (начиная с C++20)
[
захваты
] <
tparams
>
t-requires
(необязательно)
front-attr (необязательно)
(
параметры
)
спецификаторы
(необязательно)
except
(необязательно)
back-attr (необязательно) trailing (необязательно) requires (необязательно) contract-specs (необязательно)
{
тело
}
|
(1) | ||||||||
[
захваты
] <
tparams
>
t-requires
(необязательно)
{
тело
}
|
(2) | (до C++23) | |||||||
[
захваты
] <
tparams
>
t-requires
(необязательно)
front-attr (необязательно) trailing (необязательно) contract-specs (необязательно)
{
тело
}
|
(2) | (начиная с C++23) | |||||||
[
захваты
] <
tparams
>
t-requires
(необязательно)
front-attr
(необязательно)
except
back-attr (необязательно) trailing (необязательно) contract-specs (необязательно)
{
тело
}
|
(3) | (начиная с C++23) | |||||||
[
захваты
] <
tparams
>
t-requires
(необязательно)
front-attr
(необязательно)
спецификаторы
except
(необязательно)
back-attr (необязательно) trailing (необязательно) contract-specs (необязательно)
{
тело
}
|
(4) | (начиная с C++23) | |||||||
Объяснение
| captures | - | Определяет сущности, которые должны быть захвачены . | ||||||||||||
| tparams | - |
Непустой разделённый запятыми список
параметров шаблона
, используемый для предоставления имён параметрам шаблона обобщённой лямбды (см.
ClosureType::operator()
ниже).
|
||||||||||||
| t-requires | - |
Добавляет
constraints
к
tparams
.
|
||||||||||||
| front-attr | - |
(since C++23)
Последовательность спецификаторов
атрибутов
применяется к
operator
(
)
типа замыкания (и, следовательно, атрибут
[[
noreturn
]]
может быть использован).
|
||||||||||||
| params | - |
Список
параметров
для
operator
(
)
типа замыкания.
|
||||||||||||
| specs | - |
Список следующих спецификаторов, каждый спецификатор разрешён не более одного раза в каждой последовательности.
|
||||||||||||
| except | - | Предоставляет dynamic exception specification или (until C++20) спецификатор noexcept для operator ( ) типа замыкания. | ||||||||||||
| back-attr | - |
Последовательность спецификаторов атрибутов
применяется к типу
operator
(
)
замыкающего типа (и поэтому атрибут
[[
noreturn
]]
не может быть использован).
|
||||||||||||
| trailing | - |
->
ret
, где
ret
указывает тип возвращаемого значения.
|
||||||||||||
| requires | - | (since C++20) Добавляет constraints к operator ( ) типа замыкания. | ||||||||||||
| contract-specs | - | (since C++26) Список спецификаторов контракта функции для operator ( ) замыкающего типа. | ||||||||||||
| body | - | Тело функции. |
|
Если
|
(начиная с C++14) |
Переменная __func__ неявно определяется в начале тела функции , с семантикой, описанной здесь .
Тип замыкания
Лямбда-выражение является prvalue-выражением уникального безымянного не- union не- aggregate типа класса, известного как closure type , который объявляется (для целей ADL ) в наименьшей области видимости блока, класса или пространства имен, содержащей лямбда-выражение.
|
Тип замыкания является структурным типом тогда и только тогда, когда captures пуст. |
(since C++20) |
Тип замыкания имеет следующие члены, они не могут быть явно инстанцированы , явно специализированы , или (начиная с C++14) указаны в объявлении дружественной функции :
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 .
|
operator ( ) всегда является constexpr, если удовлетворяет требованиям constexpr-функции . Также является constexpr, если ключевое слово constexpr было использовано в спецификаторах лямбды. |
(начиная с C++17) |
|
operator ( ) является непосредственной функцией , если ключевое слово consteval было использовано в спецификаторах лямбды. |
(начиная с C++20) |
|
operator ( ) является статической функцией-членом , если ключевое слово static было использовано в спецификаторах лямбды. operator ( ) является функцией-членом с явным объектом , если params содержит явный объектный параметр. |
(начиная с C++23) |
|
Для каждого параметра в params , тип которого указан как auto , в template-params добавляется изобретенный параметр шаблона в порядке их появления. Изобретенный параметр шаблона может быть parameter pack , если соответствующий член функции в params является function parameter pack. |
(since C++14) |
|
Если определение лямбды использует явный список параметров шаблона, этот список параметров шаблона используется с operator ( ) . Для каждого параметра в params , тип которого указан как auto , в конец этого списка параметров шаблона добавляется дополнительный изобретенный параметр шаблона: |
(начиная с C++20) |
Спецификация исключений except в лямбда-выражении применяется к operator ( ) .
Для целей name lookup , определения типа и значения this pointer и для доступа к нестатическим членам класса, тело operator ( ) closure type рассматривается в контексте лямбда-выражения.
struct X { int x, y; int operator()(int); void f() { // контекст следующей лямбды - функция-член X::f [=]() -> int { return operator()(this->x + y); // X::operator()(this->x + (*this).y) // this имеет тип 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
=
/* см. ниже */
;
template
<
template
-
params
>
|
(начиная с C++14)
(до C++17) |
|
|
template
<
template
-
params
>
using
fptr_t
=
/* см. ниже */
;
template
<
template
-
params
>
|
(начиная с C++17) | |
Эта пользовательская функция преобразования определяется только в том случае, если лямбда-выражение не имеет захватов и не имеет явного параметра объекта (since C++23) . Это публичная, constexpr, (since C++17) невиртуальная, неявная, const noexcept функция-член объекта замыкания.
|
Эта функция является immediate function если оператор вызова функции (или специализация, для обобщенных лямбд) является immediate function. |
(since C++20) |
|
Универсальное лямбда-выражение без захвата имеет шаблонную функцию пользовательского преобразования с тем же вымышленным списком шаблонных параметров, что и operator ( ) . |
(начиная с C++14) |
|
Значение, возвращаемое функцией преобразования, является указателем на функцию с C++ language linkage , которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа замыкания на экземпляре типа замыкания, сконструированном по умолчанию. |
(до C++14) |
|
Значение, возвращаемое функцией преобразования (шаблоном), является указателем на функцию с C++ language linkage , которая при вызове имеет тот же эффект, что и:
|
(начиная с C++14)
(до C++23) |
|
Значение, возвращаемое функцией преобразования (шаблоном), является:
|
(начиная с C++23) |
|
Эта функция является constexpr, если оператор вызова функции (или специализация, для обобщенных лямбд) является constexpr. Если оператор замыкающего объекта operator ( ) имеет спецификацию исключения без генерации исключений, то указатель, возвращаемый этой функцией, имеет тип указателя на noexcept-функцию. |
(начиная с C++17) |
ClosureType:: ClosureType()
|
ClosureType
(
)
=
default
;
|
(начиная с C++20)
(только если не указаны захваты) |
|
|
ClosureType
(
const
ClosureType
&
)
=
default
;
|
||
|
ClosureType
(
ClosureType
&&
)
=
default
;
|
||
|
Типы замыканий не являются DefaultConstructible . Типы замыканий не имеют конструктора по умолчанию. |
(до C++20) |
|
Если не указаны захваты , тип замыкания имеет конструктор по умолчанию, заданный как default. В противном случае он не имеет конструктора по умолчанию (это включает случай, когда есть захват по умолчанию , даже если он фактически ничего не захватывает). |
(начиная с C++20) |
Конструктор копирования и конструктор перемещения объявлены как defaulted и могут быть неявно определены в соответствии с обычными правилами для конструкторов копирования и конструкторов перемещения .
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:: Captures
|
T1 a
;
T2 b
;
|
||
Если лямбда-выражение захватывает что-либо по копии (либо неявно с помощью спецификатора захвата
[=]
, либо явно с захватом, который не включает символ &, например
[a, b, c]
), то тип замыкания включает неименованные нестатические члены данных, объявленные в неуказанном порядке, которые содержат копии всех сущностей, захваченных таким образом.
Те члены данных, которые соответствуют захватам без инициализаторов, прямо инициализируются при вычислении лямбда-выражения. Те, которые соответствуют захватам с инициализаторами, инициализируются в соответствии с требованиями инициализатора (может быть копирующая или прямая инициализация). Если захватывается массив, элементы массива прямо инициализируются в порядке возрастания индекса. Порядок инициализации членов данных соответствует порядку их объявления (который не указан).
Тип каждого члена данных соответствует типу соответствующей захваченной сущности, за исключением случая, когда сущность имеет ссылочный тип (в этом случае ссылки на функции захватываются как lvalue-ссылки на указанные функции, а ссылки на объекты захватываются как копии указанных объектов).
Для сущностей, которые захватываются по ссылке (с помощью
capture-default
[&]
или при использовании символа &, например
[&a, &b, &c]
), не указано, объявляются ли дополнительные члены данных в типе замыкания
, но любые такие дополнительные члены должны удовлетворять требованиям
LiteralType
(начиная с C++17)
.
|
Лямбда-выражения не разрешены в невычисляемых выражениях , аргументах шаблонов , объявлениях псевдонимов , объявлениях typedef , а также где бы то ни было в объявлении функции (или шаблона функции) за исключением тела функции и аргументов по умолчанию функции. |
(до C++20) |
Захват лямбда-выражения
Захваты определяют внешние переменные, доступные внутри тела лямбда-функции. Их синтаксис определяется следующим образом:
| 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: захват по ссылке по умолчанию [&, i] {}; // OK: захват по ссылке, кроме i, который захватывается по копии [&, &i] {}; // Ошибка: захват по ссылке, когда захват по ссылке установлен по умолчанию [&, this] {}; // OK, эквивалентно [&] [&, this, i] {}; // OK, эквивалентно [&, i] }
Если
capture-default
имеет значение
=
, последующие простые захваты должны начинаться с
&
или быть
*this
(начиная с C++17)
или
this
(начиная с C++20)
.
struct S2 { void f(int i); }; void S2::f(int i) { [=] {}; // OK: захват по умолчанию по копии [=, &i] {}; // OK: захват по копии, за исключением i, который захватывается по ссылке [=, *this] {}; // до C++17: Ошибка: неверный синтаксис // начиная с C++17: OK: захватывает включающий объект S2 по копии [=, this] {}; // до C++20: Ошибка: this при = по умолчанию // начиная с C++20: OK, то же самое что [=] }
Любой захват может появляться только один раз, и его имя должно отличаться от любого имени параметра:
struct S2 { void f(int i); }; void S2::f(int i) { [i, i] {}; // Ошибка: повторение i [this, *this] {}; // Ошибка: повторение "this" (C++17) [i] (int i) {}; // Ошибка: параметр и захват имеют одинаковое имя }
Лямбда-выражение может использовать переменную без её захвата, если переменная
- является нелокальной переменной или имеет статическую или локальную для потока продолжительность хранения (в этом случае переменная не может быть захвачена), или
- является ссылкой, которая была инициализирована с помощью константного выражения .
Лямбда-выражение может читать значение переменной без её захвата, если переменная
- имеет константный не-volatile целочисленный или перечисляемый тип и была инициализирована с помощью константного выражения , или
- является constexpr и не имеет изменяемых членов.
Текущий объект (
*
this
) может быть неявно захвачен, если присутствует стандартный захват. Если он захвачен неявно, он всегда захватывается по ссылке, даже если стандартный захват имеет значение
=
.
Неявный захват
*
this
при стандартном захвате
=
устарел.
(начиная с C++20)
Только лямбда-выражения, удовлетворяющие любому из следующих условий, могут иметь capture-default или capture без инициализаторов:
- Его самая внутренняя охватывающая область видимости является областью видимости блока .
- Он появляется внутри инициализатора члена по умолчанию , и его самая внутренняя охватывающая область видимости является соответствующей областью видимости класса .
|
(начиная с C++26) |
Для такого лямбда-выражения область достижимости определяется как набор охватывающих областей видимости вплоть до и включая самую внутреннюю охватывающую функцию (и её параметры). Это включает вложенные области видимости блоков и области видимости охватывающих лямбда-выражений, если данная лямбда является вложенной.
Идентификатор
в любой capture-записи без инициализатора (кроме capture-записи
this
) ищется с использованием обычного
поиска имени без квалификации
в
охватывающей области видимости
лямбда-выражения. Результатом поиска должна быть
переменная
с автоматической продолжительностью хранения, объявленная в охватывающей области видимости
, либо
структурированная привязка
, соответствующая переменная которой удовлетворяет указанным требованиям
(начиная с C++20)
. Сущность является
явно захваченной
.
|
Захват с инициализатором, называемый
init-capture
, действует так, как если бы он объявлял и явно захватывал переменную, объявленную со спецификатором типа
Это используется для захвата типов, допускающих только перемещение, с помощью захвата, такого как x = std :: move ( x ) . Это также делает возможным захват по константной ссылке с помощью & cr = std:: as_const ( x ) или аналогичного. 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. |
(since C++14) |
Если captures имеет capture-default и не захватывает явно объемлющий объект (как this или * this ), или автоматическую переменную, которая является odr-usable в теле лямбды , или structured binding , соответствующая переменная которой имеет атомарную длительность хранения (since C++20) , она захватывает сущность неявно , если сущность упоминается в potentially-evaluated выражении внутри выражения (включая случаи, когда неявный this - > добавляется перед использованием нестатического члена класса).
Для определения неявных захватов,
typeid
никогда не считается операцией, делающей свои операнды невычисляемыми.
|
Сущности могут быть неявно захвачены, даже если они упоминаются только в отброшенной инструкции после инстанцирования тела лямбды. |
(since 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: вызывает #1, не захватывает x auto g0 = [](auto a) { f(x); }; // то же самое, что выше auto l1 = [=]{ f(x); }; // OK: захватывает x (начиная с P0588R1) и вызывает #1 // захват может быть оптимизирован auto g1 = [=](auto a) { f(x); }; // то же самое, что выше auto ltid = [=]{ typeid(x); }; // OK: захватывает x (начиная с P0588R1) // даже несмотря на то, что x не вычисляется // захват может быть оптимизирован auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2] = {}; f(x, selector); // OK: является зависимым выражением, поэтому захватывает x }; auto g3 = [=](auto a) { typeid(a + x); // захватывает x независимо от того, // является ли a + x невычисляемым операндом }; constexpr NoncopyableLiteralType w{42}; auto l4 = []{ return w.n_; }; // OK: w не используется как odr-выражение, захват не требуется // auto l5 = [=]{ return w.n_; }; // ошибка: w должен быть захвачен по копии }
Если тело лямбда-выражения odr-использует сущность, захваченную по значению, происходит обращение к члену типа замыкания. Если сущность не odr-используется, обращение происходит к исходному объекту:
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // не является odr-использованием: ссылается на const int N функции g f(&N); // odr-использование: приводит к захвату N (по копии) // &N является адресом члена N объекта замыкания, а не N функции g }(); }
Если лямбда ODR-использует ссылку, захваченную по ссылке, она использует объект, на который ссылается исходная ссылка, а не саму захваченную ссылку:
#include <iostream> auto make_function(int& x) { return [&] { std::cout << x << '\n'; }; } int main() { int i = 3; auto f = make_function(i); // использование x в f напрямую связывается с i i = 5; f(); // OK: выводит 5 }
В теле лямбды с захватом по умолчанию
=
, тип любой захватываемой сущности трактуется так, как если бы она была захвачена (и поэтому часто добавляется const-квалификация, если лямбда не
mutable
), даже если сущность находится в невычисляемом операнде и не захватывается (например, в
decltype
):
void f3() { float x, &r = x; [=] { // x и r не захватываются (появление в операнде decltype не является odr-использованием) decltype(x) y1; // y1 имеет тип float decltype((x)) y2 = y1; // y2 имеет тип float const&, потому что эта лямбда // не является mutable, а x - lvalue decltype(r) r1 = y1; // r1 имеет тип float& (преобразование не рассматривается) decltype((r)) r2 = y2; // r2 имеет тип float const& }; }
Любая сущность, захваченная лямбдой (неявно или явно), является odr-используемой лямбда-выражением (следовательно, неявный захват вложенной лямбдой вызывает неявный захват в охватывающей лямбде).
Все неявно захваченные переменные должны быть объявлены в пределах достижимой области видимости лямбда-выражения.
Если лямбда захватывает объемлющий объект (как this или * this ), то либо ближайшая объемлющая функция должна быть нестатической функцией-членом, либо лямбда должна находиться в инициализаторе элемента по умолчанию :
struct s2 { double ohseven = .007; auto f() // ближайшая охватывающая функция для следующих двух лямбда-выражений { return [this] // захват охватывающего s2 по ссылке { return [*this] // захват охватывающего s2 по копии (C++17) { return ohseven; // OK } }(); } auto g() { return [] // ничего не захватывает { return [*this] {}; // ошибка: *this не захвачен внешним лямбда-выражением }(); } };
Если лямбда-выражение (или специализация оператора вызова функции обобщённой лямбды) (since 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 и M не используются как odr-использование // (нормально, что они не захвачены) x[0][0] = i; // i явно захвачен m2 // и неявно захвачен m1 }; }; struct s1 // локальный класс внутри f1() { int f; void work(int n) // нестатическая функция-член { int m = n * n; int j = 40; auto m3 = [this, m] { auto m4 = [&, j] // ошибка: j не захвачен m3 { int x = n; // ошибка: n неявно захвачен m4 // но не захвачен m3 x += m; // OK: m неявно захвачен m4 // и явно захвачен m3 x += i; // ошибка: i находится вне области видимости // (которая заканчивается на work()) x += f; // OK: this неявно захвачен m4 // и явно захвачен m3 }; }; } }; }
Члены класса не могут быть захвачены явно в захвате без инициализатора (как упомянуто выше, только переменные разрешены в capture-list ):
class S { int x = 0; void f() { int i = 0; // auto l1 = [i, x] { use(i, x); }; // ошибка: x не является переменной auto l2 = [i, x = x] { use(i, x); }; // OK, захват по копии i = 1; x = 1; l2(); // вызывает use(0,0) auto l3 = [i, &x = x] { use(i, x); }; // OK, захват по ссылке i = 2; x = 2; l3(); // вызывает use(1,2) } };
Когда лямбда захватывает член с помощью неявного захвата по копии, она не создаёт копию этой переменной-члена: использование переменной-члена
m
трактуется как выражение
(
*
this
)
.
m
, и
*
this
всегда неявно захватывается по ссылке:
class S { int x = 0; void f() { int i = 0; auto l1 = [=] { use(i, x); }; // захватывает копию i и // копию указателя this i = 1; x = 1; l1(); // вызывает use(0, 1), как если бы // i по копии и x по ссылке auto l2 = [i, this] { use(i, x); }; // то же самое, что выше, явно указано i = 2; x = 2; l2(); // вызывает use(1, 2), как если бы // i по копии и x по ссылке auto l3 = [&] { use(i, x); }; // захватывает i по ссылке и // копию указателя this i = 3; x = 2; l3(); // вызывает use(3, 2), как если бы // i и x оба по ссылке auto l4 = [i, *this] { use(i, x); }; // создает копию *this, // включая копию x i = 4; x = 4; l4(); // вызывает use(3, 2), как если бы // i и x оба по копии } };
Если лямбда-выражение появляется в аргументе по умолчанию , оно не может явно или неявно захватывать что-либо , если только все захваты не имеют инициализаторов, удовлетворяющих ограничениям выражения, появляющегося в аргументе по умолчанию (начиная с C++14) :
void f2() { int i = 1; void g1( int = [i] { return i; }() ); // ошибка: захватывает что-то void g2( int = [i] { return 0; }() ); // ошибка: захватывает что-то void g3( int = [=] { return i; }() ); // ошибка: захватывает что-то void g4( int = [=] { return 0; }() ); // OK: без захвата void g5( int = [] { return sizeof i; }() ); // OK: без захвата // C++14 void g6( int = [x = 1] { return x; }() ); // OK: 1 может присутствовать // в аргументе по умолчанию void g7( int = [x = i] { return x; }() ); // ошибка: i не может присутствовать // в аргументе по умолчанию }
Члены анонимных объединений не могут быть захвачены. Битовые поля могут быть захвачены только по копии.
Если вложенная лямбда
m2
захватывает нечто, что также захватывается непосредственно охватывающей лямбдой
m1
, то захват
m2
преобразуется следующим образом:
-
если внешняя лямбда
m1захватывает по значению,m2захватывает нестатический член замыкающего типаm1, а не исходную переменную или * this ; еслиm1не является mutable, нестатический член данных считается константно-квалифицированным. -
если внешняя лямбда
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(); // вызывает m2() и выводит 123 std::cout << a << b << c << '\n'; // выводит 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 года некоторые основные реализации ещё не полностью внедрили этот отчёт, поэтому старое правило, которое определяет 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'; // тип замыкания не может быть назван, но может быть выведен с помощью auto // начиная с C++14, лямбда может иметь аргументы по умолчанию auto func1 = [](int i = 6) { return i + 4; }; std::cout << "func1: " << func1() << '\n'; // как и все вызываемые объекты, замыкания могут быть захвачены в std::function // (это может привести к излишним накладным расходам) std::function<int(int)> func2 = [](int i) { return i + 4; }; std::cout << "func2: " << func2(6) << '\n'; constexpr int fib_max {8}; std::cout << "Эмулировать вызовы `рекурсивных лямбда`:\nЧисла Фибоначчи: "; 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 << "Альтернативный подход к рекурсии лямбда-функций:\nЧисла Фибоначчи: "; 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 к рекурсии лямбда-функций:\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 Эмуляция вызовов `рекурсивных лямбда`: Числа Фибоначчи: 0, 1, 1, 2, 3, 5, 8, 13 Альтернативный подход к рекурсии лямбда-функций: Числа Фибоначчи: 0, 1, 1, 2, 3, 5, 8, 13
Отчёты о дефектах
Следующие отчеты об изменениях в поведении, содержащие описания дефектов, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 974 | C++11 |
аргументы по умолчанию не допускались в
списке параметров лямбда-выражения |
разрешено |
|
CWG 1048
( N3638 ) |
C++11 |
возвращаемый тип мог выводиться только для лямбда-
выражений, содержащих только один return оператор |
улучшено выведение
возвращаемого типа |
| CWG 1249 | C++11 |
неясно, считается ли захваченный член
объемлющей не-mutable лямбды 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__ в теле лямбды была неясна |
ссылается на operator()
класса замыкания |
| CWG 1780 | C++14 |
было неясно, могут ли члены типов замыканий обобщенных
лямбд быть явно инстанцированы или явно специализированы |
ни то, ни другое не разрешено |
| CWG 1891 | C++11 |
замыкание имело удаленный конструктор по умолчанию
и неявные конструкторы копирования/перемещения |
нет конструктора по умолчанию и
конструкторы копирования/перемещения по умолчанию |
| 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)
|
перемещаемая обёртка для любого вызываемого объекта, поддерживающего квалификаторы в заданной сигнатуре вызова
(шаблон класса) |
Внешние ссылки
| Вложенная функция - функция, которая определена внутри другой ( охватывающей ) функции. |