lambda 表达式 (C++11 起)

来自cppreference.com
< cpp‎ | language
 
 
C++ 语言
 
表达式
概述
值类别(左值 lvalue、右值 rvalue、亡值 xvalue)
求值顺序(序列点)
常量表达式
潜在求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=ba+=ba-=ba*=ba/=ba%=ba&=ba|=ba^=ba<<=ba>>=b
自增与自减++a--aa++a--
算术运算符+a-aa+ba-ba*ba/ba%b~aa&ba|ba^ba<<ba>>b
逻辑运算符a||ba&&b!a
比较运算符a==ba!=ba<ba>ba<=ba>=ba<=>b(C++20)
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
折叠表达式(C++17)
运算符的代用表示
优先级和结合性
运算符重载
默认比较(C++20)
类型转换
隐式转换
一般算术转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 
 

构造闭包:能够捕获作用域中的变量的无名函数对象。

语法

[ 捕获 ] ( 形参列表 )(可选) lambda说明符 { 函数体 } (1) (C++23 前)
[ 捕获 ] 属性 ( 形参列表 )(可选) lambda说明符 { 函数体 } (1) (C++23 起)
[ 捕获 ] < 模板形参 > 约束(可选) ( 形参列表 )(可选) lambda说明符 { 函数体 } (2) (C++20 起)
(C++23 前)
[ 捕获 ] < 模板形参 > 约束(可选) 属性(可选) ( 形参列表 )(可选) lambda说明符 { 函数体 } (2) (C++23 起)
1) 完整声明。
2) 与 1) 相同,但指定泛型 lambda 并显式提供模板形参列表。

解释

捕获 - 包含零或更多个捕获符的逗号分隔列表,可以 默认捕获符 起始。

有关捕获符的详细描述,见下文。 如果变量满足下列条件,那么 lambda 表达式在使用它前不需要先捕获:

  • 该变量是非局部变量,或具有静态或线程局部存储期(此时无法捕获该变量),或者
  • 该变量是以常量表达式初始化的引用。

如果变量满足下列条件,那么 lambda 表达式在读取它的值前不需要先捕获:

  • 该变量具有 const 而非 volatile 的整型或枚举类型,并已经用常量表达式初始化,或者
  • 该变量是 constexpr 的且没有 mutable 成员。
模板形参 - 模板形参列表,用于为泛型 lambda 提供各模板形参的名字(见下文的 闭包类型::operator())。与在模板声明中相似,模板形参列表可以后附 requires 子句,它指定各模板实参上的约束

模板形参列表不能为空(不允许 <>)。

形参列表 - 形参列表,如同在具名函数中。省略时,函数不接收实参,如同形参列表是 ()。
lambda说明符 - 说明符异常说明属性尾随返回类型约束 按前述顺序组成,每个组分均可选。
说明符 - 可选的说明符的序列。不提供说明符时复制捕获的对象在 lambda 体内是 const 的。可以使用下列说明符:
  • mutable:允许 函数体 修改复制捕获的对象,以及调用它们的非 const 成员函数。
  • constexpr:显式指定函数调用运算符或运算符模板的任意特化为 constexpr 函数。如果没有此说明符但函数调用运算符或任意给定的运算符模板特化恰好满足针对 constexpr 函数的所有要求,那么它也会是 constexpr
(C++17 起)
  • consteval:指定函数调用运算符或任意给定的运算符模板特化为立即函数。不能同时使用 constevalconstexpr
(C++20 起)
  • static:指定函数调用运算符或任意给定的运算符模板特化为静态成员函数。不能同时使用 mutablestatic,并且使用 static捕获 必须为空。
(C++23 起)
异常说明 - 为闭包类型的 operator() 提供动态异常说明 (C++20 前) noexcept 说明符
属性 - 可选的属性说明序列

属性说明序列适用于闭包类型的函数调用运算符或运算符模板的类型。任何指定的属性都不涉及函数调用运算符或运算符模板,而只涉及其自身(例如,不能使用 [[noreturn]] 属性。)。

(C++23 前)

如果属性说明序列出现在参数列表、lambda 说明符或异常说明之前,则它适用于函数调用运算符或运算符模板(因此可以使用 [[noreturn]] 属性)。 否则,它适用于它自身。

(C++23 起)
尾随返回类型 - -> 返回类型,其中 返回类型 指定返回类型。如果没有 尾随返回类型,那么闭包的 operator() 的返回类型从 return 语句推导,如同对于声明返回类型为 auto 的函数的推导一样。
约束 - (C++20 起)向闭包类型的 operator() 添加约束。
函数体 - 函数体。

当以 auto 为形参类型或显式提供模板形参列表 (C++20 起)时,该 lambda 是泛型 lambda

(C++14 起)

变量 __func__函数体 的开头隐式定义,它的语义可以参考这里

lambda 表达式是纯右值表达式,它的类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),它(对于 实参依赖查找 而言)在含有该 lambda 表达式的最小块作用域、类作用域或命名空间作用域声明。闭包类型有下列成员,它们不能显式实例化,被显式特化,或 (C++14 起)友元声明中指名:

闭包类型::operator()(形参)

返回类型 operator()(形参) { 函数体 }
(static 和 const 可能会出现,见下文)
template<模板形参>
返回类型 operator()(形参) { 函数体 }
(C++14 起)
(泛型 lambda,static 和 const 可能会出现,见下文)

当被调用时,执行 lambda 表达式的函数体。当访问变量时,访问的是它被捕获的副本(对于以复制捕获的实体)或原对象(对于以引用捕获的实体)。

除非 lambda 表达式中使用了关键词 mutable,否则函数调用运算符或运算符模板的 cv 限定符都会是 const,并且无法从这个 operator() 的内部修改以复制捕获的对象。函数调用运算符或运算符模板始终始终非虚,并且 cv 限定符不会是 volatile

如果函数调用运算符或任意给定的运算符模板特化满足针对 constexpr 函数的要求,那么它始终是 constexpr 的。如果关键词 constexpr 用于 lambda 声明,那么它也是 constexpr 的。

(C++17 起)

如果在 lambda 表达式中使用关键词 consteval,那么函数调用运算符或任意给定的运算符模板特化是立即函数

(C++20 起)

如果在 lambda 表达式中使用关键词 static,那么函数调用运算符或任意给定的运算符模板特化是静态成员函数

(C++23 起)


对于 形参 中每个类型指定为 auto 的参数,以它们的出现顺序向 模板形参 中添加一个虚设的模板形参。当虚设的模板形参所对应的 形参 中的函数形参是函数形参包时,它可以是形参包

// 泛型 lambda,operator() 是有两个形参的模板
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // ok
 
// 泛型 lambda,operator() 是有一个形参的模板
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // 泛型 lambda,ts 是形参包
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // 零元 lambda (不接受形参)
        return [=] { printer(ts...); };
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
 
auto q = p(1, 'a', 3.14); // 输出 1a3.14
q();                      // 输出 1a3.14
(C++14 起)

如果 lambda 定义使用了显式的模板形参列表,该列表会用于 operator()。对于 形参 中的每个类型指定为 auto 的形参都会向模板形参列表追加一个虚设的模板形参:

// 泛型 lambda,operator() 是一个拥有两个(模板)形参的模板
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
 
// 泛型 lambda,operator() 是一个拥有一个形参包的模板
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(C++20 起)

lambda 表达式上的异常说明 异常说明 应用于函数调用运算符或运算符模板。

对于名字查找、确定 this 指针的类型和值以及对于访问非静态类成员而言,闭包类型的函数调用运算符的函数体被认为处于 lambda 表达式的语境中。

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // 下列 lambda 的语境是成员函数 X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this 拥有类型 X*
        };
    }
};

悬垂引用

如果以引用隐式或显式捕获非引用实体,而在该实体的生存期结束之后调用闭包对象的函数调用运算符或运算符模板特化,那么会发生未定义行为。C++ 的闭包并不延长以引用捕获的对象的生存期。

这同样适用于由 this 捕获对当前 *this 对象。

闭包类型::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<模板形参>

operator fptr_t<模板形参>() const noexcept;
(C++14 起)
(C++17 前)
template<模板形参> using fptr_t = /*见下文*/;

template<模板形参>

constexpr operator fptr_t<模板形参>() const noexcept;
(C++17 起)

只有在 lambda 表达式的捕获符列表为空时才定义这个用户定义转换函数。它是闭包对象的公开、constexpr、 (C++17 起)非虚、非 explicit、const noexcept 成员函数。

如果函数调用运算符(或对于泛型 lambda 的函数调用运算符特化)是立即函数,那么此函数是立即函数

(C++20 起)


泛型无捕获 lambda 拥有一个用户定义的转换函数模板,它具有与函数调用运算符模板相同的虚设模板形参列表。如果它的返回类型为空或 auto,那么将由函数模板特化上的返回类型推导获得,而它会以转换函数模板的模板实参推导获得。

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); // 错误:不可转换
h(glambda);  // OK:调用 #1,因为 #2 不可转换
 
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(C++14 起)


这个转换函数返回一个指向具有 C++ 语言连接的函数指针,调用该函数的效果与在默认构造的闭包类型实例上调用闭包类型的函数调用运算符的效果相同。

(C++14 前)

这个转换函数(模板)返回一个指向具有 C++ 语言连接的函数指针,调用该函数的效果与下列效果相同:

  • 对于非泛型 lambda,在默认构造的闭包类型实例上调用闭包类型的函数调用运算符。
  • 对于泛型 lambda,在默认构造的闭包类型实例上调用函数调用运算符模板的泛型 lambda 对应特化。
(C++14 起)
(C++23 前)

这个转换函数(模板):

  • operator() 是静态的情况下返回一个指向具有 C++ 语言连接的该 operator() 的指针。
  • 否则返回一个指向具有 C++ 语言连接的函数指针,调用该函数的效果与下列效果相同:
    • 对于非泛型 lambda,在默认构造的闭包类型实例上调用闭包类型的函数调用运算符。
    • 对于泛型 lambda,在默认构造的闭包类型实例上调用函数调用运算符模板的泛型 lambda 对应特化。
(C++23 起)


如果函数调用运算符(或对于泛型 lambda 是它的特化)是 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); // 错误:因为 static s 而不能为 constexpr 的特化

如果闭包对象的 operator() 具有无抛出异常说明,那么此函数返回的指针具有指向 noexcept 函数的指针类型。 (C++20 前)

(C++17 起)

闭包类型::闭包类型()

闭包类型() = default;
(C++20 起)
(仅当未指定任何捕获时)
闭包类型(const 闭包类型&) = default;
闭包类型(闭包类型&&) = default;

闭包类型非可默认构造 (DefaultConstructible) 。闭包类型没有默认构造函数。

(C++20 前)

如果没有指定 捕获,那么闭包类型拥有预置的默认构造函数。否则,它没有默认构造函数(这包含有 默认捕获符(capture-default) 的情况,即使它实际上没有捕获任何变量)。

(C++20 起)

复制构造函数与移动构造函数声明为预置,并可能按照复制构造函数移动构造函数的通常规则隐式定义。

闭包类型::operator=(const 闭包类型&)

闭包类型& operator=(const 闭包类型&) = delete;
(C++20 前)
闭包类型& operator=(const 闭包类型&) = default;
闭包类型& operator=(闭包类型&&) = default;
(C++20 起)
(仅当未指定任何捕获时)
闭包类型& operator=(const 闭包类型&) = delete;
(C++20 起)
(其他情况)

复制赋值运算符被定义为弃置的(且未声明移动赋值运算符)。闭包类型非可复制赋值 (CopyAssignable)

(C++20 前)

如果没有指定 捕获,那么闭包类型拥有预置的复制赋值运算符和预置的移动赋值运算符。否则,它拥有弃置的复制赋值运算符(这包含有 默认捕获符 的情况,即使它实际上没有捕获任何变量)。

(C++20 起)

闭包类型::~闭包类型()

~闭包类型() = default;

析构函数是隐式声明的。

闭包类型::捕获

T1 a;

T2 b;

...

如果 lambda 表达式以复制(隐式地以捕获子句 [=] 或显式地以不含字符 & 的捕获符,例如 [a, b, c])捕获了任何内容,那么闭包类型包含保有所有被如此捕获的实体的副本的无名非静态数据成员,它们以未指明的顺序声明。

如果数据成员对应的捕获符没有初始化器,那么它们在求值 lambda 表达式时被直接初始化。如果有初始化器,那么按它的初始化器的要求初始化(可为复制或直接初始化)。如果捕获了数组,那么各数组元素以下标递增顺序直接初始化。初始化各数据成员所用的顺序是它们的声明顺序(即未指明)。

每个数据成员的类型是它对应的被捕获实体的类型,除非实体拥有引用类型(此时到函数的引用被捕获为到被引用函数的左值引用,而到对象的引用被捕获为被引用对象的副本)。

对于以引用捕获(以默认捕获符 [&] 或使用了字符 &,例如 [&a, &b, &c])的实体,闭包类型中是否声明额外的数据成员是未指明的,但任何这种附加成员必须满足字面类型 (LiteralType) (C++17 起)


不允许在不求值表达式模板实参别名声明typedef 声明,以及函数(或函数模板)声明中除了函数体和函数的默认实参以外的任何位置中出现 lambda 表达式。

(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 起)
1) 简单的以复制捕获
2) 作为包展开的简单的以复制捕获
3)初始化器的以复制捕获
4) 简单的以引用捕获
5) 作为包展开的简单的以引用捕获
6) 带初始化器的以引用捕获
7) 当前对象的简单的以引用捕获
8) 当前对象的简单的以复制捕获
9) 初始化器为包展开的以复制捕获
10) 初始化器为包展开的以引用捕获

当默认捕获符是 & 时,后继的简单捕获符不能以 & 开始。

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&]{};          // OK:默认以引用捕获
    [&, i]{};       // OK:以引用捕获,但 i 以值捕获
    [&, &i] {};     // 错误:以引用捕获为默认时的以引用捕获
    [&, this] {};   // OK:等价于 [&]
    [&, this, i]{}; // OK:等价于 [&, i]
}

当默认捕获符是 = 时,后继的简单捕获符必须以 & 开始,或者为 *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) {};   // 错误:形参和捕获的名字相同
}

只有定义于块作用域或默认成员初始化器中的 lambda 表达式能拥有默认捕获符或无初始化器的捕获符。对于这种 lambda 表达式,它的可达作用域(reaching scope)定义为它最内层的外围函数(及它的形参)内(包含自身)的外围作用域的集合。这其中包含各个嵌套的块作用域,以及当此 lambda 为嵌套的 lambda 时也包含它的各个外围 lambda 的作用域。

(除了 this 捕获符之外的)任何无初始化器的捕获符中的 标识符 会使用通常的无限定名字查找在 lambda 的可达作用域中查找。查找结果必须是在可达作用域中声明的且具有自动存储期的变量,或对应变量满足这种要求的结构化绑定 (C++20 起)。该实体被显式捕获

带有初始化器的捕获符的行为如同声明并显式捕获以 auto 类型说明符声明并拥有相同初始化器的变量,该变量的作用域是 lambda 表达式体(即不在初始化器的作用域内),但:

  • 如果以复制捕获,则因而引入的闭包对象的非静态数据成员和该变量将被视为引用同一对象;
    • 换言之,源变量并不实际存在,而经由 auto 的类型推导和初始化均应用到该非静态数据成员;
  • 如果以引用捕获,那么引用变量的生存期在闭包对象的生存期结束时结束。

这可以用于以像 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;
}(); // 更新 ::x 到 6 并初始化 y 为 25。
(C++14 起)

如果捕获符列表具有默认捕获符,且未显式(以 this*this)捕获它的外围对象,或任何在 lambda 体内可 ODR 使用的自动变量,或对应变量拥有自动存储期的结构化绑定 (C++20 起),那么在以下情况下,它隐式捕获之:

  • 或者,该实体在取决于某个泛型 lambda 形参的 (C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的 this->)。就此目的而言,始终认为 typeid 的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。 (C++17 起)
void f(int, const int (&)[2] = {}) {}   // #1
void f(const int&, const int (&)[1]) {} // #2
 
void test()
{
    const int x = 17;
 
    auto g0 = [](auto a) { f(x); };  // OK:调用 #1,不捕获 x
 
    auto g1 = [=](auto a) { f(x); }; // C++14 中不捕获 x,C++17 中捕获 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 是否为不求值操作数
    };
}
(C++14 起)

如果 lambda 体 ODR 使用了以复制捕获的实体,那么它访问的是闭包类型的成员。如果它未 ODR 使用该实体,那么访问的是原对象:

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // 非 ODR 使用:指代 g 的 const int N
        f(&N); // ODR 使用:导致 N 被(以复制)捕获
               // &N 是闭包对象的成员 N 的地址,而非 g 中的 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); // f 中对 x 的使用直接绑定到 i
    i = 5;
    f(); // OK:打印 5
}

在带默认捕获符 = 的 lambda 体内,任何可捕获对实体的类型为如同它被捕获(从而在 lambda 非 mutable 时通常会加上 const 限定),即使该实体在不求值运算数中且未被捕获(例如在 decltype 中):

void f3()
{
    float x, &r = x;
    [=]
    { // x 与 r 不被捕获(在 decltype 的操作数中出现并不是 ODR 使用)
        decltype(x) y1;        // y1 拥有 float 类型
        decltype((x)) y2 = y1; // y2 拥有 float const& 类型,因为此 lambda
                               // 非 mutable 且 x 是左值
        decltype(r) r1 = y1;   // r1 拥有 float& 类型(不考虑变换)
        decltype((r)) r2 = y2; // r2 拥有 float const& 类型
    };
}

lambda (隐式或显式)捕获的任何实体均被该 lambda 表达式 ODR 使用(因此嵌套的 lambda 的隐式捕获将触发它的外围 lambda 的隐式捕获)。

所有隐式捕获的变量必须在 lambda 的可达作用域中声明。

如果 lambda(以 this*this)捕获了它的外围对象,那么要么它的最接近的外围函数必须是非静态成员函数,要么该 lambda 必须处于某个默认成员初始化器中:

struct s2
{
    double ohseven = .007;
 
    auto f() // 以下两个 lambda 的最接近外围函数
    {
        return [this] // 以引用捕获外围的 s2
        {
            return [*this] // 以复制捕获外围的 s2(C++17)
            {
                return ohseven;// OK
            }
        }();
    }
 
    auto g()
    {
        return [] // 无捕获
        {
            return [*this]{};// 错误:*this 未被外层 lambda 表达式所捕获
        }();
    }
};

如果 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 与 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 显式捕获
                };
            };
        }
    };
}

以不带有初始化器的捕获符不能捕获类成员(如上提及,捕获符列表中只能有变量):

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)
    }
};

当 lambda 用隐式的以复制捕获捕获某个成员时,它并不产生该成员变量的副本:对成员变量 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 均以复制捕获
    }
};

如果 lambda 表达式在默认实参中出现,那么它不能显式或隐式捕获任何内容,除非所有捕获都带有完整表达式可以在默认实参中出现的初始化器 (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 不能在默认实参中出现
}

不能捕获匿名联合体的成员。只能以复制捕获位域

如果嵌套的 lambda m2 捕获了也被它的直接外围 lambda m1 所捕获的实体,那么以如下方式将 m2 的捕获进行变换:

  • 如果外围 lambda m1 以复制捕获,那么 m2 捕获 m1 的闭包类型的非静态数据成员,而非原变量或 *this;如果 m1mutable,那么认为该非静态数据成员有 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();                             // 调用 m2() 并打印 123
    std::cout << a << b << c << '\n'; // 打印 234
}

示例

此示例演示如何传递 lambda 给泛型算法,以及 lambda 表达式所产生的对象能如何存储于 std::function 对象。

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
 
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 起,lambda 可以有默认实参
    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';
 
    std::cout << "模仿递归 lambda 调用:\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另一种 lambda 递归方案:\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 的 lambda 递归方案:\n";
    auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1)
    {
         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
模仿递归 lambda 调用:
斐波那契数:0, 1, 1, 2, 3, 5, 8, 13
另一种 lambda 递归方案:
斐波那契数:0, 1, 1, 2, 3, 5, 8, 13

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 974 C++11 lambda 表达式的形参列表中不能有默认实参 允许
CWG 975 C++11 只有在 lambda 体含有单条 return 语句时
才推导闭包的 operator() 的返回类型
如同对于 C++14 返回
auto 的函数一般推导
CWG 1249 C++11 不明确外围非 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 的闭包类型的成员是否可以被显式实例化或被显式特化 两者都不允许
CWG 1891 C++11 闭包带有弃置的默认构造函数和隐含的复制/移动构造函数 无默认及预置的复制/移动
CWG 1937 C++11 未指明调用转换函数的返回值的效果与调用哪个对象的 operator() 相同 与调用默认构造的闭包类型
实例的 operator() 相同
CWG 2011 C++11 对于以引用捕获的引用,未指明该捕获符的标识符表示的是哪个实体 表示的是原来引用的实体
CWG 2095 C++11 以复制捕获到函数的右值引用的行为不明确 使之明确
CWG 2211 C++11 未指明捕获与形参的名字相同时的行为 此时程序非良构
CWG 2358 C++14 在默认实参中的 lambda 表达式不能有任何捕获,
即使它们都被可以在默认实参内出现的表达式初始化
这种带捕获的 lambda 表达式
可以在默认实参中出现
CWG 2509 C++17 说明符序列里每个声明符可以多次出现 只能各出现最多一次

参阅

auto 说明符(C++11) 指定从表达式推导的类型
(C++11)
包装具有指定函数调用签名的任意可复制构造类型的可调用对象
(类模板)
包装具有指定函数调用签名的任意类型的可调用对象
(类模板)