命名空间
变体
操作

Lambda 表达式 (C++11 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 
 

构造一个 闭包(一个能够捕获作用域内变量的未命名函数对象)。

目录

[编辑] 语法

[编辑] 不带显式模板参数列表的 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 起)
1) 带参数列表的 lambda 表达式。
2-4) 不带参数列表的 lambda 表达式。
2) 最简单的语法。后置属性不能应用。
3,4) 只有当 说明符异常说明 中存在任何一个时,才能应用 后置属性

[编辑] 解释

捕获列表 - 指定要捕获的实体。
模板参数列表 - 非空的逗号分隔的模板参数列表,用于为泛型 lambda 的模板参数提供名称(见下方的 ClosureType::operator())。
模板约束 - 模板参数列表 添加约束

如果 模板约束 以属性说明符序列结尾,则序列中的属性被视为 前置属性 中的属性。

(C++23 起)
前置属性 - (C++23 起) 属性说明符序列应用于闭包类型的 operator()(因此可以使用 [[noreturn]] 属性)。
参数列表 - 闭包类型的 operator()参数列表

它可以有一个显式对象参数

(C++23 起)
说明符 - 以下说明符的列表,每个说明符在每个序列中最多允许出现一次。
说明符 效果
mutable 允许 函数体 修改按值捕获的对象,并调用它们的非 const 成员函数。
  • 如果存在显式对象参数,则不能使用。
(C++23 起)
constexpr     
(C++17 起)
显式指定 operator() 是一个 constexpr 函数
  • 如果 operator() 满足所有 constexpr 函数要求,则即使不存在 constexproperator() 也会是 constexpr。
consteval
(C++20 起)
指定 operator() 是一个立即函数
  • constevalconstexpr 不能同时指定。
static
(C++23 起)
指定 operator() 是一个静态成员函数
  • staticmutable 不能同时指定。
  • 如果 捕获列表 不为空,或者存在显式对象参数,则不能使用。
异常规范 - 提供 动态异常说明(C++20 前) 闭包类型的 operator()noexcept 说明符
后置属性 - 一个属性说明符序列应用于闭包类型的 operator() 的类型(因此不能使用 [[noreturn]] 属性)。
尾随返回类型 - -> 返回类型,其中 返回类型 指定返回类型。
requires 子句 - (C++20 起) 为闭包类型的 operator() 添加约束
契约说明符 - (C++26 起) 闭包类型的 operator()函数契约说明符列表。
函数体 - 函数体。


如果参数类型使用 auto,或者提供了显式模板参数列表(C++20 起),则 lambda 是一个 _泛型 lambda_。

(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 的每个参数,一个虚构的模板参数会按出现的顺序添加到 模板参数 中。如果 参数列表 对应的函数成员是函数参数包,则虚构的模板参数可能是一个参数包

// generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
 
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // nullary lambda (takes no parameters):
        return [=] { printer(ts...); };
    };
};
 
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
 
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14
(C++14 起)


如果 lambda 定义使用显式模板参数列表,则该模板参数列表会与 operator() 一起使用。对于 参数列表 中类型指定为 auto 的每个参数,一个额外的虚构模板参数会附加到该模板参数列表的末尾。

// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
 
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(C++20 起)

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 对象的生命周期也适用同样的情况。

  1. 尽管 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<模板参数>

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

template<模板参数>

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

用户定义转换函数仅在 lambda 表达式没有 捕获列表 且没有显式对象参数时才定义(C++23 起)。它是闭包对象的公共、constexpr、(C++17 起)非虚、非显式、const noexcept 成员函数。

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

(C++20 起)

无捕获泛型 lambda 具有一个用户定义转换函数模板,其模板参数列表与 operator() 相同。

void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {}  // #1
void h(char (*)(int)) {} // #2
 
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // error: not convertible
h(glambda);  // OK: calls #1 since #2 is not convertible
 
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(C++14 起)


转换函数返回的值是指向具有 C++ 语言链接的函数的指针,当调用时,其效果与在闭包类型的默认构造实例上调用闭包类型的函数调用运算符相同。

(C++14 前)

转换函数(模板)返回的值是指向具有 C++ 语言链接的函数的指针,当调用时,其效果与以下情况相同

  • 对于非泛型 lambda,在闭包类型的默认构造实例上调用闭包类型的 operator()
  • 对于泛型 lambda,在闭包类型的默认构造实例上调用泛型 lambda 的相应 operator() 特化。
(C++14 起)
(直至 C++23)

转换函数(模板)返回的值是

  • 如果 operator() 是静态的,则是指向具有 C++ 语言链接operator() 的指针,
  • 否则,是指向具有 C++ 语言链接的函数的指针,当调用时,其效果与以下情况相同:
    • 对于非泛型 lambda,在闭包类型的默认构造实例上调用闭包类型的 operator()
    • 对于泛型 lambda,在闭包类型的默认构造实例上调用泛型 lambda 的相应 operator() 特化。
(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); // error: no specialization can be
                                // constexpr because of static s

如果闭包对象的 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 起)
1) 简单的按值捕获
2) 作为包扩展的简单按值捕获
3)初始化器的按值捕获
4) 简单的按引用捕获
5) 简单的按引用捕获,它是一个包扩展
6) 带初始化器的按引用捕获
7) 当前对象的简单按引用捕获
8) 当前对象的简单按值捕获
9) 带初始化器的按值捕获,它是一个包扩展
10) 带初始化器的按引用捕获,它是一个包扩展

如果 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 起)。该实体是显式捕获的。

带有初始化器的捕获,称为初始化捕获,其行为就像它声明并显式捕获了一个用类型说明符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;
}(); // 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 捕获了任何内容,则函数调用运算符的显式对象参数(如果有)的类型只能是:

  • 闭包类型,
  • 公开且无歧义地派生自闭包类型的类类型,或者
  • 对可能是 cv 限定的此类类型的引用。
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 并且没有显式捕获封闭对象(作为 this*this),或者在 lambda 函数体中可 ODR 使用的自动变量,或者一个对应的变量具有自动存储期的结构化绑定(C++20 起),则如果该实体满足以下条件,它会隐式捕获该实体:

  • 在依赖于泛型 lambda 的模板参数的表达式内的潜在求值表达式中命名,或者
(C++14 起)

[编辑] 示例

此示例展示了如何将 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)
任何可复制构造的可调用对象的包装器
(类模板) [编辑]
任何支持给定调用签名中限定符的可调用对象的仅移动包装器
(类模板) [编辑]

[编辑] 外部链接

嵌套函数 - 在另一个(封闭)函数内部定义的函数。