Lambda 表达式 (自 C++11 起)
构造一个闭包(一个能够捕获作用域内变量的未命名函数对象)。
内容 |
[编辑] 语法
[编辑] 没有显式模板形参列表的 Lambda 表达式(可能为非泛型)
[ 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 起) | |||||||
[编辑] 具有显式模板形参列表的 Lambda 表达式(始终为泛型) (自 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 | - | 一个非空的逗号分隔的模板形参列表,用于为泛型 lambda 的模板形参提供名称(参见下面的 ClosureType::operator() )。 | ||||||||||||
t-requires | - | 为 tparams 添加约束。
| ||||||||||||
front-attr | - | (自 C++23 起) 应用于闭包类型的 属性说明符序列(因此可以使用 [[noreturn]] 属性)。 | ||||||||||||
params | - | 闭包类型的 operator() 的形参列表。
| ||||||||||||
specs | - | 以下说明符的列表,每个说明符在每个序列中最多允许一次。
| ||||||||||||
如果 captures 不为空,或者存在显式对象形参,则不能使用。 | - | except | ||||||||||||
为闭包类型的 operator() 提供动态异常规范 或(直到 C++20) noexcept 说明符。 | - | back-attr | ||||||||||||
应用于闭包类型的 operator() 类型的 属性说明符序列(因此不能使用 [[noreturn]] 属性)。
|
- | trailing | ||||||||||||
-> ret,其中 ret 指定返回类型。
|
- | requires | ||||||||||||
(自 C++20 起) 为闭包类型的 operator() 添加约束。 | - | contract-specs | ||||||||||||
(自 C++26 起) 闭包类型的 operator() 的函数契约说明符列表。 | - | body |
函数体。 |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 |
(自 C++14 起)
变量 __func__ 在 body 的开头隐式定义,其语义如此处所述。
[编辑] 闭包类型
lambda 表达式是一个 prvalue 表达式,其类型是唯一的未命名非联合体非聚合体类类型,称为闭包类型,它在包含 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明(为了 ADL 的目的)。 |
(自 C++20 起) |
当且仅当 captures 为空时,闭包类型才是结构化类型。
闭包类型具有以下成员,它们不能显式实例化、显式特化或(自 C++14 起)在 友元声明中命名
ClosureType::operator()(params) |
ret operator()(params) { body } | |
(static 和 const 可能存在,见下文) ClosureType::operator()(params) |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 template<template-params> |
|
(泛型 lambda,static 和 const 可能存在,见下文)
当调用时,执行 lambda 表达式的主体。当访问变量时,访问其捕获的副本(对于按复制捕获的实体),或原始对象(对于按引用捕获的实体)。
如果提供了 params,则 operator() 的形参列表为 params,否则形参列表为空。
operator() 的返回类型是在 trailing 中指定的类型。
如果未提供 trailing,则 operator() 的返回类型会自动推导。[1]
除非在 lambda 说明符中使用了关键字 mutable,或者存在显式对象形参(自 C++23 起),否则 operator() 的 cv 限定符为 const,并且从 operator() 内部无法修改按复制捕获的对象。不允许显式 const 限定符。operator() 永远不是虚函数,并且不能具有 volatile 限定符。 |
(自 C++17 起) |
如果 operator() 满足 constexpr 函数的要求,则它始终是 constexpr。如果在 lambda 说明符中使用了关键字 constexpr,它也是 constexpr。 |
(自 C++20 起) |
如果在 lambda 说明符中使用了关键字 consteval,则 operator() 是一个 立即函数。 如果在 lambda 说明符中使用了关键字 static,则 operator() 是一个 静态成员函数。 |
(自 C++23 起) |
如果 params 包含显式对象形参,则 operator() 是一个 显式对象成员函数。 |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 |
对于 params 中类型指定为 auto 的每个形参,都会按照出现顺序将一个发明的模板形参添加到 template-params。如果 params 的相应函数成员是一个函数形参包,则发明的模板形参可以是形参包。 |
(自 C++20 起) |
如果 lambda 定义使用显式模板形参列表,则该模板形参列表与 operator() 一起使用。对于 params 中类型指定为 auto 的每个形参,都会将一个额外的发明的模板形参附加到该模板形参列表的末尾
lambda 表达式上的异常规范 except 应用于 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* }; } };
为了 名称查找、确定 this 指针的类型和值以及访问非静态类成员,闭包类型的 operator() 的主体被视为在 lambda 表达式的上下文中。
悬垂引用
如果一个非引用实体被隐式或显式地按引用捕获,并且在实体的生命周期结束后调用闭包对象的 operator(),则会发生未定义的行为。C++ 闭包不会延长按引用捕获的对象的生命周期。
- 这同样适用于通过
this
捕获的当前 *this 对象的生命周期。
↑ 虽然函数返回类型推导是在 C++14 中引入的,但其规则在 C++11 中可用于 lambda 返回类型推导。
ClosureType::operator ret(*)(params)() |
||
无捕获非泛型 lambda using F = ret(*)(params); |
operator F() const noexcept; | |
无捕获非泛型 lambda (直到 C++17) |
(自 C++17 起) | |
constexpr operator F() const noexcept; |
||
无捕获泛型 lambda (static 和 const 可能存在,见下文) |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 operator F() const noexcept; |
|
无捕获泛型 lambda (static 和 const 可能存在,见下文) |
(自 C++17 起) | |
constexpr operator fptr_t<template-params>() const noexcept;
仅当 lambda 表达式没有 captures且没有显式对象形参(自 C++23 起)时,才定义此用户定义的转换函数。它是闭包对象的 public、constexpr、(自 C++17 起)非虚函数、非显式、const noexcept 成员函数。 |
(自 C++20 起) |
如果函数调用运算符(或泛型 lambda 的特化)是一个立即函数,则此函数是一个立即函数。 |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 |
泛型无捕获 lambda 具有一个用户定义的转换函数模板,其发明的模板形参列表与 operator() 相同。 |
转换函数返回的值是指向具有 C++ 语言链接的函数的指针,当调用该函数时,其效果与对闭包类型的默认构造实例调用闭包类型的函数调用运算符的效果相同。 |
转换函数(模板)返回的值是一个指向具有 C++ 语言链接的函数的指针,当调用该函数时,其效果与以下操作相同:
|
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。(直到 C++23) |
转换函数(模板)返回的值是: |
(自 C++23 起) |
如果函数调用运算符(或泛型 lambda 的特化版本)是 constexpr,则此函数是 constexpr。 如果闭包对象的 operator() 具有非抛出异常规范,则由此函数返回的指针类型为指向 noexcept 函数的指针。 |
(自 C++17 起) |
ClosureType::ClosureType()
ClosureType() = default; |
(自 C++20 起) (仅当未指定捕获时) |
|
ClosureType(const ClosureType&) = default; |
||
ClosureType(ClosureType&&) = default; |
||
闭包类型不是 默认可构造 (DefaultConstructible) 的。闭包类型没有默认构造函数。 |
(直到 C++20) |
如果未指定 捕获 (captures),则闭包类型具有默认的默认构造函数。否则,它没有默认构造函数(这包括存在 捕获默认 (capture-default) 的情况,即使它实际上没有捕获任何内容)。 |
(自 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) |
如果未指定 捕获 (captures),则闭包类型具有默认的复制赋值运算符和默认的移动赋值运算符。否则,它具有已删除的复制赋值运算符(这包括存在 捕获默认 (capture-default) 的情况,即使它实际上没有捕获任何内容)。 |
(自 C++20 起) |
ClosureType::~ClosureType()
~ClosureType() = default; |
||
析构函数是隐式声明的。
ClosureType::捕获 (Captures)
T1 a; T2 b; |
||
如果 lambda 表达式通过复制捕获任何内容(无论是使用捕获子句 [=]
隐式捕获,还是使用不包含字符 & 的捕获显式捕获,例如 [a, b, c]
),则闭包类型包含未命名的非静态数据成员,这些成员以未指定的顺序声明,用于保存所有被捕获实体的副本。
那些对应于没有初始值设定项的捕获的数据成员在 lambda 表达式求值时进行 直接初始化 (direct-initialized)。那些对应于带有初始值设定项的捕获的数据成员按照初始值设定项的要求进行初始化(可能是复制初始化或直接初始化)。如果捕获的是数组,则数组元素按索引递增的顺序进行直接初始化。数据成员的初始化顺序是它们声明的顺序(未指定)。
每个数据成员的类型是相应被捕获实体的类型,除非实体具有引用类型(在这种情况下,对函数的引用被捕获为对被引用函数的左值引用,而对对象的引用被捕获为被引用对象的副本)。
对于通过引用捕获的实体(使用 捕获默认 (capture-default) [&]
或使用字符 &,例如 [&a, &b, &c]
),是否在闭包类型中声明了额外的数据成员是未指定的,但任何此类额外成员都必须满足 字面类型 (LiteralType) 的要求(自 C++17 起)。
Lambda 表达式不允许出现在 未求值表达式 (unevaluated expressions)、模板参数 (template arguments)、别名声明 (alias declarations)、typedef 声明 (typedef declarations) 中,以及函数(或函数模板)声明中的任何位置,函数体和函数的 默认参数 (default arguments) 除外。 |
(直到 C++20) |
[编辑] Lambda 捕获 (Lambda capture)
捕获 (captures) 定义了可以从 lambda 函数体内部访问的外部变量。其语法定义如下:
捕获默认 (capture-default) | (1) | ||||||||
捕获列表 (capture-list) | (2) | ||||||||
捕获默认 (capture-default) , 捕获列表 (capture-list) |
(3) | ||||||||
捕获默认 (capture-default) | - | & 和 = 之一 |
捕获列表 (capture-list) | - | 以逗号分隔的 捕获 (capture) 列表 |
捕获 (capture) 的语法定义如下:
标识符 (identifier) | (1) | ||||||||
标识符 (identifier) ... |
(2) | ||||||||
标识符 (identifier) 初始值设定项 (initializer) | (3) | 如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。
| |||||||
& 标识符 (identifier) |
(4) | ||||||||
& 标识符 (identifier) ... |
(5) | ||||||||
& 标识符 (identifier) 初始值设定项 (initializer) |
(6) | 如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。
| |||||||
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 }
如果变量满足以下条件,则 lambda 表达式可以使用变量而无需捕获它:
- 是非局部变量,或者具有静态或线程局部 存储期 (storage duration)(在这种情况下,变量无法被捕获),或者
- 是已使用 常量表达式 (constant expression) 初始化的引用。
如果变量满足以下条件,则 lambda 表达式可以读取变量的值而无需捕获它:
- 具有 const 非易失性整型或枚举类型,并且已使用 常量表达式 (constant expression) 初始化,或者
- 是 constexpr 并且没有 mutable 成员。
如果存在任何捕获默认 (capture default),则可以隐式捕获当前对象 (*this)。如果隐式捕获,则始终按引用捕获,即使捕获默认 (capture default) 为 =
。当捕获默认 (capture default) 为 =
时,隐式捕获 *this 已被弃用。(自 C++20 起)
只有满足以下任何条件的 lambda 表达式才能具有 捕获默认 (capture-default) 或没有初始值设定项的 捕获 (capture):
- 其最内层的 外围作用域 (enclosing scope) 是一个 块作用域 (block scope)。
- 它出现在 默认成员初始值设定项 (default member initializer) 中,并且其最内层的外围作用域是相应的 类作用域 (class scope)。
|
(自 C++26 起) |
对于此类 lambda 表达式,可达作用域 (reaching scope) 定义为外围作用域的集合,直至并包括最内层的外围函数(及其参数)。这包括嵌套的块作用域和外围 lambda 的作用域(如果此 lambda 是嵌套的)。
任何没有初始值设定项的捕获(this
捕获除外)中的 标识符 (identifier) 都在 lambda 的 可达作用域 (reaching scope) 中使用常用的 非限定名称查找 (unqualified name lookup) 进行查找。查找结果必须是在可达作用域中声明的具有自动存储期 (automatic storage duration) 的 变量 (variable),或者是 结构化绑定 (structured binding),其对应的变量满足此类要求(自 C++20 起)。该实体被显式捕获 (explicitly captured)。
带有初始值设定项的捕获,称为 初始化捕获 (init-capture),其行为就好像它声明并显式捕获了一个使用类型说明符
这用于捕获仅移动类型 (move-only types),例如使用 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. |
如果在形参类型中使用了 auto 或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda。 |
如果 捕获 (captures) 具有 捕获默认 (capture-default),并且没有显式捕获外围对象(如 this 或 *this),或者 lambda 主体中 ODR 可用 (odr-usable) 的自动变量,或者是 结构化绑定 (structured binding),其对应的变量具有原子存储期 (atomic storage duration)(自 C++20 起),则如果在表达式内的 潜在求值 (potentially-evaluated) 表达式中命名了该实体(包括在非静态类成员的使用之前添加隐式 this-> 时),它会隐式捕获该实体。
为了确定隐式捕获的目的,typeid
永远不会被认为使其操作数未求值 (unevaluated)。
即使实体仅在 lambda 主体实例化后的 丢弃语句 (discarded statement) 中被命名,也可能被隐式捕获。 |
(自 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 }
如果 lambda 的主体 ODR 使用 (odr-uses) 了按值复制捕获的实体,则访问闭包类型的成员。如果它没有 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 }(); }
如果 lambda 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 }
在具有捕获默认 (capture default) =
的 lambda 主体中,任何可捕获实体的类型都好像已被捕获(因此,如果 lambda 不是 mutable,则通常会添加 const 限定),即使实体位于未求值操作数中且未被捕获(例如,在 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& }; }
任何被 lambda 捕获的实体(隐式或显式)都由 lambda 表达式 ODR 使用 (odr-used)(因此,嵌套 lambda 的隐式捕获会触发外围 lambda 中的隐式捕获)。
所有隐式捕获的变量都必须在 lambda 的 可达作用域 (reaching scope) 内声明。
如果 lambda 捕获了外围对象(如 this 或 *this),则最近的外围函数必须是非静态成员函数,或者 lambda 必须在 默认成员初始值设定项 (default member initializer) 中。
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 }(); } };
如果 lambda 表达式(或泛型 lambda 的函数调用运算符的特化版本)(自 C++14 起) ODR 使用 (ODR-uses) *this 或任何具有自动存储期的变量,则它必须由 lambda 表达式捕获。
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) 中只允许使用 变量 (variables))。
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) } };
当 lambda 使用隐式按值复制捕获来捕获成员时,它不会复制该成员变量:成员变量 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 } };
如果 lambda 表达式出现在 默认参数 (default argument) 中,则它不能显式或隐式捕获任何内容,除非所有捕获都具有满足出现在默认参数中的表达式约束的初始值设定项(自 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 }
匿名联合 (anonymous unions) 的成员不能被捕获。位域 (Bit-fields) 只能通过复制捕获。
如果嵌套的 lambda m2
捕获了也被立即外围的 lambda m1
捕获的内容,则 m2
的捕获将按如下方式转换:
- 如果外围 lambda
m1
按值复制捕获,则m2
捕获的是m1
的闭包类型的非静态成员,而不是原始变量或 *this;如果m1
不是 mutable,则非静态数据成员被认为是 const 限定的。 - 如果外围 lambda
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 }
如果 lambda 捕获了任何内容,则函数调用运算符的显式对象参数(如果有)的类型只能是:
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 起) |
[编辑] 注释 (Notes)
特性测试宏 (Feature-test macro) | 值 (Value) | 标准 (Std) | 特性 (Feature) |
---|---|---|---|
__cpp_lambdas |
200907L |
(C++11) | Lambda 表达式 (Lambda expressions) |
__cpp_generic_lambdas |
201304L |
(C++14) | 泛型 Lambda 表达式 (Generic lambda expressions) |
201707L |
(C++20) | 泛型 Lambda 的显式模板参数列表 (Explicit template parameter list for generic lambdas) | |
__cpp_init_captures |
201304L |
(C++14) | Lambda 初始化捕获 (Lambda init-capture) |
201803L |
(C++20) | 允许在 Lambda 初始化捕获中使用包展开 (Allow pack expansion in lambda init-capture) | |
__cpp_capture_star_this |
201603L |
(C++17) | 按值复制捕获 *this 的 Lambda 捕获,如 [=, *this] |
__cpp_constexpr |
201603L |
(C++17) | constexpr lambda |
__cpp_static_call_operator |
202207L |
(C++23) | 无捕获 lambda 的静态 operator() |
缺陷报告 P0588R1 对隐式 lambda 捕获的规则进行了轻微更改。截至 2023-10,一些主要的实现尚未完全实现 DR,因此在某些情况下仍然使用检测 ODR 使用 (odr-using) 的旧规则。
P0588R1 之前的旧规则 | ||
---|---|---|
如果 捕获 (captures) 具有 捕获默认 (capture-default),并且没有显式捕获外围对象(如
|
[编辑] 示例 (Example)
此示例展示了如何将 lambda 传递给泛型算法,以及如何将 lambda 表达式产生的对象存储在 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 }
可能的输出 (Possible output)
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
[编辑] 缺陷报告 (Defect reports)
以下行为变更缺陷报告已追溯应用于之前发布的 C++ 标准。
DR | 应用于 (Applied to) | 已发布时的行为 (Behavior as published) | 正确行为 (Correct behavior) |
---|---|---|---|
CWG 974 | C++11 | 默认参数在 lambda 表达式的参数列表中是不允许的 |
允许 (allowed) |
CWG 1048 (N3638) |
C++11 | 返回类型只能为 lambda 推导出来 主体仅包含一个 return 语句 |
改进了返回 类型推导 (type deduction) |
CWG 1249 | C++11 | 不清楚是否捕获的成员 外围的非 mutable lambda 被认为是 const |
被认为是 const |
CWG 1557 | C++11 | 返回的函数类型的语言链接 闭包类型的转换函数未指定 |
它具有 C++ 语言链接 (language linkage) |
CWG 1607 | C++11 | lambda 表达式可以出现在 函数和函数模板签名中 |
不允许 (not allowed) |
CWG 1612 | C++11 | 匿名联合的成员可以被捕获 | 不允许 (not allowed) |
CWG 1722 | C++11 | 无捕获 lambda 的转换函数 具有未指定的异常规范 |
转换函数 (conversion function) 是 noexcept |
CWG 1772 | C++11 | lambda 主体中 __func__ 的语义不明确 | 它指的是闭包 类的 operator() |
CWG 1780 | C++14 | 不清楚泛型的闭包类型的成员是否 lambda 可以被显式实例化或显式特化 |
两者都不允许 (neither is allowed) |
CWG 1891 | C++11 | 闭包具有已删除的默认构造函数 和隐式复制/移动构造函数 |
没有默认的和默认的 (no default and defaulted) 复制/移动构造函数 (copy/move constructors) |
CWG 1937 | C++11 | 至于调用结果的效果 转换函数,未指定在哪个 对象上调用其 operator() 具有相同的效果 |
在默认构造的 闭包类型的实例上 |
CWG 1973 | C++11 | 闭包类型的 operator() 的参数列表 可以引用在 尾随 (trailing) 中给出的参数列表 |
只能引用 (can only refer) 参数 (params) |
CWG 2011 | C++11 | 对于按引用捕获的引用,未指定 捕获的标识符引用哪个实体 |
它指的是最初 引用的实体 (referenced entity) |
CWG 2095 | C++11 | 捕获右值引用的行为 通过复制函数不明确 |
已明确 (made clear) |
CWG 2211 | C++11 | 如果捕获 与参数同名,则行为未指定 |
程序是不良的 (ill-) 在这种情况下形成的 (formed in this case) |
CWG 2358 | C++14 | 出现在默认参数中的 lambda 表达式 即使所有捕获都使用以下项初始化,也必须是无捕获的 可以出现在默认参数中的表达式 |
允许这样的 lambda (allow such lambda) 带有捕获的表达式 (expressions with captures) |
CWG 2509 | C++17 | 每个说明符可以有多个 在说明符序列中出现 (occurrences in the specifier sequence) |
每个说明符只能 (each specifier can only) 在说明符序列中最多出现一次 (appear at most once in) 说明符序列 (the specifier sequence) |
CWG 2561 | C++23 | 具有显式对象参数的 lambda 可能具有 到不需要的函数指针类型的转换函数 |
它没有这样的 (it does not have such) 转换函数 (a conversion funtion) |
CWG 2881 | C++23 | 带有显式参数的 operator() 可以为 派生类实例化,当继承不是 public 或 ambiguous 时 |
变为病态 (made ill-formed) |
P0588R1 | C++11 | 隐式 lambda 捕获的规则检测到 odr-use | 检测已简化 (the detection is simplified) |
[编辑] 参见 (See also)
auto 说明符 (specifier) (C++11) |
指定从表达式推导出的类型 |
(C++11) |
任何可复制构造的可调用对象的 Copyable 包装器 (copyable wrapper) (类模板)(class template) |
(C++23) |
任何可调用对象的仅移动包装器 (move-only wrapper),该对象在给定的调用签名中支持限定符 (qualifiers) (类模板)(class template) |
[编辑] 外部链接 (External links)
嵌套函数 (Nested function) - 在另一个(外围)函数中定义的函数。 |