Lambda 表达式 (C++11 起)
构造一个 闭包(一个能够捕获作用域内变量的未命名函数对象)。
目录 |
[编辑] 语法
[编辑] 不带显式模板参数列表的 Lambda 表达式(可能为非泛型)
[ 捕获列表 ] 前置属性 (可选) ( 参数列表 ) 说明符 (可选) 异常说明 (可选)后置属性 (可选) 尾随返回类型 (可选) requires 子句 (可选) 契约说明符 (可选) { 函数体 } |
(1) | ||||||||
[ 捕获列表 ] { 函数体 } |
(2) | (直至 C++23) | |||||||
[ 捕获列表 ] 前置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(2) | (C++23 起) | |||||||
[ 捕获列表 ] 前置属性 (可选) 异常说明后置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(3) | (C++23 起) | |||||||
[ 捕获列表 ] 前置属性 (可选) 说明符 异常说明 (可选)后置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(4) | (C++23 起) | |||||||
[编辑] 带显式模板参数列表的 Lambda 表达式(总是泛型) (C++20 起)
[ 捕获列表 ] < 模板参数列表 > 模板约束 (可选)前置属性 (可选) ( 参数列表 ) 说明符 (可选) 异常说明 (可选)后置属性 (可选) 尾随返回类型 (可选) requires 子句 (可选) 契约说明符 (可选) { 函数体 } |
(1) | ||||||||
[ 捕获列表 ] < 模板参数列表 > 模板约束 (可选) { 函数体 } |
(2) | (直至 C++23) | |||||||
[ 捕获列表 ] < 模板参数列表 > 模板约束 (可选)前置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(2) | (C++23 起) | |||||||
[ 捕获列表 ] < 模板参数列表 > 模板约束 (可选) 前置属性 (可选) 异常说明后置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(3) | (C++23 起) | |||||||
[ 捕获列表 ] < 模板参数列表 > 模板约束 (可选) 前置属性 (可选) 说明符 异常说明 (可选)后置属性 (可选) 尾随返回类型 (可选) 契约说明符 (可选) { 函数体 } |
(4) | (C++23 起) | |||||||
[编辑] 解释
捕获列表 | - | 指定要捕获的实体。 | ||||||||||||
模板参数列表 | - | 非空的逗号分隔的模板参数列表,用于为泛型 lambda 的模板参数提供名称(见下方的 ClosureType::operator() )。 | ||||||||||||
模板约束 | - | 为 模板参数列表 添加约束。
| ||||||||||||
前置属性 | - | (C++23 起) 属性说明符序列应用于闭包类型的 operator()(因此可以使用 [[noreturn]] 属性)。 | ||||||||||||
参数列表 | - | 闭包类型的 operator() 的参数列表。
| ||||||||||||
说明符 | - | 以下说明符的列表,每个说明符在每个序列中最多允许出现一次。
| ||||||||||||
异常规范 | - | 提供 动态异常说明 或(C++20 前) 闭包类型的 operator() 的 noexcept 说明符。 | ||||||||||||
后置属性 | - | 一个属性说明符序列应用于闭包类型的 operator() 的类型(因此不能使用 [[noreturn]] 属性)。 | ||||||||||||
尾随返回类型 | - | -> 返回类型,其中 返回类型 指定返回类型。 | ||||||||||||
requires 子句 | - | (C++20 起) 为闭包类型的 operator() 添加约束。 | ||||||||||||
契约说明符 | - | (C++26 起) 闭包类型的 operator() 的函数契约说明符列表。 | ||||||||||||
函数体 | - | 函数体。 |
如果参数类型使用 |
(C++14 起) |
变量 __func__ 在 函数体 的开头隐式定义,其语义如此处所述。
[编辑] 闭包类型
lambda 表达式是一个右值表达式,其类型为唯一的未命名非联合非聚合类类型,称为_闭包类型_,它被声明在包含 lambda 表达式的最小块作用域、类作用域或命名空间作用域中(用于 ADL)。
当且仅当 捕获列表 为空时,闭包类型是结构化类型。 |
(C++20 起) |
闭包类型具有以下成员,它们不能被显式实例化、显式特化,或(C++14 起)在友元声明中命名。
ClosureType::operator()(参数列表)
返回类型 operator()(参数列表) { 函数体 } |
(static 和 const 可能存在,见下文) | |
template<模板参数> 返回类型 operator()(参数列表) { 函数体 } |
(C++14 起) (泛型 lambda,static 和 const 可能存在,见下文) |
|
调用时执行 lambda 表达式的函数体。访问变量时,访问其捕获的副本(对于按值捕获的实体),或原始对象(对于按引用捕获的实体)。
如果提供了参数列表,则 operator() 的参数列表为 参数列表,否则参数列表为空。
operator() 的返回类型是 尾随返回类型 中指定的类型。
如果未提供 尾随返回类型,则 operator() 的返回类型会自动推导。[1]
除非在 lambda 说明符中使用了关键字 mutable,或者存在显式对象参数(C++23 起),否则 operator() 的 cv 限定符为 const,并且通过复制捕获的对象在此 operator() 内部不可修改。不允许显式 const 限定符。operator() 永远不是虚函数,也不能有 volatile 限定符。
如果 operator() 满足constexpr 函数的要求,则它总是 constexpr。如果 lambda 说明符中使用了关键字 constexpr,它也是 constexpr。 |
(C++17 起) |
如果 lambda 说明符中使用了关键字 consteval,则 operator() 是一个立即函数。 |
(C++20 起) |
如果 lambda 说明符中使用了关键字 static,则 operator() 是一个静态成员函数。 如果 参数列表 包含一个显式对象参数,则 operator() 是一个显式对象成员函数。 |
(C++23 起) |
对于 参数列表 中类型指定为 auto 的每个参数,一个虚构的模板参数会按出现的顺序添加到 模板参数 中。如果 参数列表 对应的函数成员是函数参数包,则虚构的模板参数可能是一个参数包。 |
(C++14 起) |
如果 lambda 定义使用显式模板参数列表,则该模板参数列表会与 operator() 一起使用。对于 参数列表 中类型指定为 auto 的每个参数,一个额外的虚构模板参数会附加到该模板参数列表的末尾。 |
(C++20 起) |
lambda 表达式上的异常说明 异常说明 适用于 operator()。
为了进行名称查找、确定this 指针的类型和值,以及访问非静态类成员,闭包类型的 operator() 的函数体在 lambda 表达式的上下文中被考虑。
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 中,它的规则可用于 lambda 返回类型推导。
ClosureType::operator 返回类型(*)(参数列表)()
无捕获非泛型 lambda |
||
using F = 返回类型(*)(参数列表); operator F() const noexcept; |
(C++17 前) | |
using F = 返回类型(*)(参数列表); constexpr operator F() const noexcept; |
(C++17 起) | |
无捕获泛型 lambda |
||
template<模板参数> using fptr_t = /* 见下文 */; template<模板参数> |
(C++14 起) (C++17 前) |
|
template<模板参数> using fptr_t = /* 见下文 */; template<模板参数> |
(C++17 起) | |
此用户定义转换函数仅在 lambda 表达式没有 捕获列表 且没有显式对象参数时才定义(C++23 起)。它是闭包对象的公共、constexpr、(C++17 起)非虚、非显式、const noexcept 成员函数。
如果函数调用运算符(或泛型 lambda 的特化)是立即函数,则此函数是立即函数。 |
(C++20 起) |
无捕获泛型 lambda 具有一个用户定义转换函数模板,其模板参数列表与 operator() 相同。 |
(C++14 起) |
转换函数返回的值是指向具有 C++ 语言链接的函数的指针,当调用时,其效果与在闭包类型的默认构造实例上调用闭包类型的函数调用运算符相同。 |
(C++14 前) |
转换函数(模板)返回的值是指向具有 C++ 语言链接的函数的指针,当调用时,其效果与以下情况相同
|
(C++14 起) (直至 C++23) |
转换函数(模板)返回的值是 |
(C++23 起) |
如果函数调用运算符(或泛型 lambda 的特化)是 constexpr,则此函数是 constexpr。 如果闭包对象的 operator() 具有非抛出异常说明,则此函数返回的指针具有指向 noexcept 函数的类型。 |
(C++17 起) |
ClosureType::ClosureType()
ClosureType() = default; |
(C++20 起) (仅当未指定捕获时) |
|
ClosureType(const ClosureType&) = default; |
||
ClosureType(ClosureType&&) = default; |
||
闭包类型不是可默认构造的。闭包类型没有默认构造函数。 |
(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 起) (否则) |
|
复制赋值运算符被定义为已删除(并且未声明移动赋值运算符)。闭包类型不是可复制赋值的。 |
(C++20 前) |
如果未指定 捕获列表,则闭包类型具有默认的复制赋值运算符和默认的移动赋值运算符。否则,它具有已删除的复制赋值运算符(这包括存在 捕获默认 的情况,即使它实际上没有捕获任何东西)。 |
(C++20 起) |
ClosureType::~ClosureType()
~ClosureType() = default; |
||
析构函数是隐式声明的。
ClosureType::捕获
T1 a; T2 b; |
||
如果 lambda 表达式按值捕获任何内容(无论是通过捕获子句 [=]
隐式捕获,还是通过不包含字符 & 的捕获(例如 [a, b, c]
)显式捕获),则闭包类型包含未命名非静态数据成员,以未指定顺序声明,它们持有所有如此捕获的实体的副本。
那些没有初始化器的捕获对应的数据成员在 lambda 表达式求值时直接初始化。那些带有初始化器的捕获对应的数据成员根据初始化器要求进行初始化(可以是复制初始化或直接初始化)。如果捕获了数组,数组元素按索引递增顺序直接初始化。数据成员的初始化顺序是它们声明的顺序(未指定)。
每个数据成员的类型是相应捕获实体的类型,除非实体具有引用类型(在这种情况下,函数引用被捕获为对引用函数的左值引用,对象引用被捕获为引用对象的副本)。
对于按引用捕获的实体(使用 捕获默认 [&]
或使用字符 &,例如 [&a, &b, &c]
),是否在闭包类型中声明额外的数据成员是未指定的,但任何此类额外成员必须满足字面量类型(C++17 起)。
Lambda 表达式不允许出现在未求值表达式、模板参数、别名声明、typedef 声明中,以及函数(或函数模板)声明中的任何位置,除了函数体和函数的默认参数。 |
(C++20 前) |
[编辑] Lambda 捕获
捕获列表 定义了可从 lambda 函数体内部访问的外部变量。其语法定义如下:
捕获默认 | (1) | ||||||||
捕获列表 | (2) | ||||||||
捕获默认 , 捕获列表 |
(3) | ||||||||
捕获默认 | - | & 和 = 之一 |
捕获列表 | - | 逗号分隔的 捕获 列表 |
捕获 的语法定义如下:
标识符 | (1) | ||||||||
标识符 ... |
(2) | ||||||||
标识符 初始化器 | (3) | (C++14 起) | |||||||
& 标识符 |
(4) | ||||||||
& 标识符 ... |
(5) | ||||||||
& 标识符 初始化器 |
(6) | (C++14 起) | |||||||
this
|
(7) | ||||||||
* this |
(8) | (C++17 起) | |||||||
... 标识符 初始化器 |
(9) | (C++20 起) | |||||||
& ... 标识符 初始化器 |
(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 表达式可以使用变量而无需捕获它:
如果变量满足以下条件,lambda 表达式可以读取变量的值而无需捕获它:
- 它具有 const 非 volatile 整型或枚举类型,并且已用常量表达式初始化,或者
- 它是 constexpr 且没有可变成员。
如果存在任何捕获默认值,则当前对象(*this)可以被隐式捕获。如果隐式捕获,它总是按引用捕获,即使捕获默认值为 =
。当捕获默认值为 =
时隐式捕获 *this 已弃用。(C++20 起)
只有满足以下任何条件的 lambda 表达式才能具有 capture-default 或不带初始化器的 capture:
(C++26 起) |
对于此类 lambda 表达式,可达作用域定义为包含直到并包括最内层函数(及其参数)的所有封闭作用域的集合。这包括嵌套的块作用域以及如果此 lambda 是嵌套的则包含的 lambda 的作用域。
任何不带初始化器(除了 this
捕获)的捕获中的 identifier 使用通常的非限定名称查找在 lambda 的可达作用域中查找。查找结果必须是在可达作用域中声明的具有自动存储期的变量,或者是一个结构化绑定,其对应的变量满足此类要求(C++20 起)。该实体是显式捕获的。
带有初始化器的捕获,称为初始化捕获,其行为就像它声明并显式捕获了一个用类型说明符
这用于通过诸如 x = std::move(x) 的捕获来捕获仅移动类型。 这也使得通过 const 引用捕获成为可能,例如 &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. |
(C++14 起) |
如果 captures 包含 capture-default 并且没有显式捕获封闭对象(作为 this 或 *this),或者在 lambda 函数体中可 ODR 使用的自动变量,或者一个对应的变量具有自动存储期的结构化绑定(C++20 起),则如果该实体在表达式(包括在使用非静态类成员之前隐式添加 this-> 时)的潜在求值表达式中命名,则它会隐式捕获该实体。
为了确定隐式捕获,typeid
从不被视为使其操作数未求值。
即使实体只在 lambda 函数体实例化后被丢弃语句中命名,也可能被隐式捕获。 |
(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-使用该实体,则访问的是原始对象。
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 }
在捕获默认值为 =
的 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-使用(因此,嵌套 lambda 的隐式捕获会触发封闭 lambda 中的隐式捕获)。
所有隐式捕获的变量必须在 lambda 的可达作用域内声明。
如果 lambda 捕获了封闭对象(作为 this 或 *this),则最近的封闭函数必须是非静态成员函数,或者 lambda 必须在默认成员初始化器中。
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-使用 *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 中只允许变量)
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 表达式出现在默认实参中,则它不能显式或隐式捕获任何东西,除非所有捕获都具有满足默认实参中出现的表达式的限制条件的初始化器(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 }
如果嵌套 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 起) |
[编辑] 注意
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_lambdas |
200907L |
(C++11) | Lambda 表达式 |
__cpp_generic_lambdas |
201304L |
(C++14) | 泛型 lambda 表达式 |
201707L |
(C++20) | 泛型 lambda 的显式模板参数列表 | |
__cpp_init_captures |
201304L |
(C++14) | Lambda 初始化捕获 |
201803L |
(C++20) | 允许在 lambda 初始化捕获中使用包扩展 | |
__cpp_capture_star_this |
201603L |
(C++17) | Lambda 通过值捕获 *this 作为 [=, *this] |
__cpp_constexpr |
201603L |
(C++17) | constexpr lambda |
__cpp_static_call_operator |
202207L |
(C++23) | 无捕获 lambda 的 static operator() |
缺陷报告 P0588R1 稍微改变了隐式 lambda 捕获的规则。截至 2023 年 10 月,一些主要实现尚未完全实现该 DR,因此在某些情况下仍使用检测ODR-使用的旧规则。
P0588R1 之前的旧规则 | ||
---|---|---|
如果 captures 具有 capture-default 并且没有显式捕获封闭对象(作为
|
[编辑] 示例
此示例展示了如何将 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 }
可能的输出
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 | 默认实参不允许出现在 lambda 表达式的参数列表中 |
允许 |
CWG 1048 (N3638) |
C++11 | 返回类型只能推断出包含一个 return 语句的 lambda 函数体的返回类型 仅包含一个 return 语句的 lambda 函数体 |
改进了返回 类型推导 |
CWG 1249 | C++11 | 不清楚封闭的非 mutable lambda 的被捕获成员是否被认为是 const 不清楚封闭的非 mutable lambda 的被捕获成员是否被认为是 const |
被认为是 const |
CWG 1557 | C++11 | 未指定闭包类型的转换函数返回的函数类型的语言链接 未指定闭包类型的转换函数返回的函数类型的语言链接 |
它具有 C++ 语言链接 |
CWG 1607 | C++11 | lambda 表达式可以出现在 函数和函数模板签名中 |
不允许 |
CWG 1612 | C++11 | 匿名联合的成员可以被捕获 | 不允许 |
CWG 1722 | C++11 | 无捕获 lambda 的转换函数 具有未指定的异常规范 |
转换函数 是 noexcept |
CWG 1772 | C++11 | lambda 函数体中 __func__ 的语义不明确 | 它引用闭包 类的 operator() |
CWG 1780 | C++14 | 不清楚泛型 lambda 闭包类型的成员是否可以显式实例化或显式特化 不清楚泛型 lambda 闭包类型的成员是否可以显式实例化或显式特化 |
两者都不允许 |
CWG 1891 | C++11 | 闭包具有已删除的默认构造函数 和隐式复制/移动构造函数 |
没有默认和默认的 复制/移动构造函数 |
CWG 1937 | C++11 | 关于调用转换函数结果的效果,未指定 调用其 operator() 对哪个对象具有相同的效果 在哪个对象上调用其 operator() 具有相同的效果 |
在一个默认构造的 闭包类型实例上 |
CWG 1973 | C++11 | 闭包类型的 operator() 的参数列表 可以引用 trailing 中给定的参数列表 |
只能引用 到 params |
CWG 2011 | C++11 | 对于按引用捕获的引用,未指定 捕获的标识符引用哪个实体 |
它引用原始的 引用实体 |
CWG 2095 | C++11 | 按值捕获函数右值引用的行为不明确 按值捕获函数右值引用的行为不明确 |
已明确 |
CWG 2211 | C++11 | 如果捕获的名称与参数名称相同,则行为未指定 如果捕获的名称与参数名称相同,则行为未指定 |
程序格式错误 在这种情况下,枚举是病态的 |
CWG 2358 | C++14 | 出现在默认实参中的 lambda 表达式必须是无捕获的,即使所有捕获都用可以出现在默认实参中的表达式初始化 必须是无捕获的,即使所有捕获都用可以出现在默认实参中的表达式初始化 即使所有捕获都用可以出现在默认实参中的表达式初始化 |
允许此类 lambda 带有捕获的表达式 |
CWG 2509 | C++17 | 每个说明符在说明符序列中可以有多个 出现次数 |
每个说明符最多只能 在说明符序列中出现一次 在说明符序列中出现一次 |
CWG 2561 | C++23 | 具有显式对象参数的 lambda 可以具有到不期望的函数指针类型的转换函数 具有显式对象参数的 lambda 可以具有到不期望的函数指针类型的转换函数 |
它没有这样的 转换函数 |
CWG 2881 | C++23 | 当继承不是公开的或有歧义时,可以为派生类实例化带有显式参数的 operator() 当继承不是公开的或有歧义时,可以为派生类实例化带有显式参数的 operator() |
导致格式错误 |
P0588R1 | C++11 | 隐式 lambda 捕获的规则检测 ODR-使用 | 检测被简化 |
[编辑] 另见
auto 说明符 (C++11) |
指定从表达式推导的类型 |
(C++11) |
任何可复制构造的可调用对象的包装器 (类模板) |
(C++23) |
任何支持给定调用签名中限定符的可调用对象的仅移动包装器 (类模板) |
[编辑] 外部链接
嵌套函数 - 在另一个(封闭)函数内部定义的函数。 |