命名空间
变体
操作

Lambda 表达式 (自 C++11 起)

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

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

内容

[编辑] 语法

[编辑] 没有显式模板形参列表的 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(可选) except
back-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(可选) except
back-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 起)
1) 带有形参列表的 lambda 表达式。
2-4) 没有形参列表的 lambda 表达式。
2) 最简单的语法。back-attr 不可应用。
3,4) 仅当存在 specsexcept 中的任何一个时,back-attr 才可以应用。

[编辑] 解释

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

如果 t-requires 以属性说明符序列结尾,则序列中的属性被视为 front-attr 中的属性。

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

它可以具有显式对象形参

(自 C++23 起)
specs - 以下说明符的列表,每个说明符在每个序列中最多允许一次。
说明符 效果
mutable 允许 body 修改按复制捕获的对象,并调用它们的非常量成员函数。
  • 如果存在显式对象形参,则不能使用。
(自 C++23 起)
constexpr     
(自 C++17 起)
显式指定 operator() 是一个 constexpr 函数
  • 如果 operator() 满足所有 constexpr 函数的要求,即使 constexpr 不存在,operator() 也将是 constexpr。
consteval
(自 C++20 起)
指定 operator() 是一个 立即函数
  • constevalconstexpr 不能同时指定。
static
(自 C++23 起)
static
  • 指定 operator() 是一个 静态成员函数
  • staticmutable 不能同时指定。
如果 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() 是一个 显式对象成员函数

// 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
如果在形参类型中使用了 auto或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda


对于 params 中类型指定为 auto 的每个形参,都会按照出现顺序将一个发明的模板形参添加到 template-params。如果 params 的相应函数成员是一个函数形参包,则发明的模板形参可以是形参包

// 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() 一起使用。对于 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++ 闭包不会延长按引用捕获的对象的生命周期。

  1. 这同样适用于通过 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 可能存在,见下文)

template<template-params> using fptr_t = /* 见下文 */;
如果在形参类型中使用了 auto或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda
operator F() const noexcept;
无捕获泛型 lambda

(static 和 const 可能存在,见下文)

operator fptr_t<template-params>() const noexcept;
(自 C++17 起)

constexpr operator fptr_t<template-params>() const noexcept;

仅当 lambda 表达式没有 captures且没有显式对象形参(自 C++23 起)时,才定义此用户定义的转换函数。它是闭包对象的 public、constexpr、(自 C++17 起)非虚函数、非显式、const noexcept 成员函数。

(自 C++20 起)

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

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
如果在形参类型中使用了 auto或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda


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

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

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

  • 对于非泛型 lambda,调用闭包类型的 operator(),作用于闭包类型的默认构造实例。
  • 对于泛型 lambda,调用泛型 lambda 对应的 operator() 特化版本,作用于闭包类型的默认构造实例。
如果在形参类型中使用了 auto或者提供了显式模板形参列表(自 C++20 起),则 lambda 是一个泛型 lambda
(直到 C++23)

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

  • 如果 operator() 是静态的,则返回一个指向该 operator() 并具有 C++ 语言链接的指针,
  • 否则,返回一个指向具有 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;

闭包类型不是 默认可构造 (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 起)
1) 简单的按值复制捕获
2) 简单的按值复制捕获,它是 包展开 (pack expansion)
3) 带有 初始值设定项 (initializer) 的按值复制捕获
4) 简单的按引用捕获
5) 简单的按引用捕获,它是 包展开 (pack expansion)
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 表达式可以读取变量的值而无需捕获它:

如果存在任何捕获默认 (capture default),则可以隐式捕获当前对象 (*this)。如果隐式捕获,则始终按引用捕获,即使捕获默认 (capture default) 为 =当捕获默认 (capture default) 为 = 时,隐式捕获 *this 已被弃用。(自 C++20 起)

只有满足以下任何条件的 lambda 表达式才能具有 捕获默认 (capture-default) 或没有初始值设定项的 捕获 (capture)

(自 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),其行为就好像它声明并显式捕获了一个使用类型说明符 auto 和相同初始值设定项声明的变量,其声明区域是 lambda 表达式的主体(即,它不在其初始值设定项的作用域内),但以下情况除外:

  • 如果捕获是按值复制的,则闭包对象的引入的非静态数据成员是引用该变量的另一种方式;
    • 换句话说,源变量实际上不存在,并且通过 auto 进行的类型推导和初始化应用于非静态数据成员;
  • 如果捕获是按引用捕获的,则引用变量的生命周期在闭包对象的生命周期结束时结束。

这用于捕获仅移动类型 (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 捕获了任何内容,则函数调用运算符的显式对象参数(如果有)的类型只能是:

  • 闭包类型,
  • 公开且明确地从闭包类型派生的类类型,或
  • 对可能具有 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 起)

[编辑] 注释 (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),并且没有显式捕获外围对象(如 this*this),或者 lambda 主体中 ODR 可用 (odr-usable) 的自动变量,或者是 结构化绑定 (structured binding),其对应的变量具有原子存储期 (atomic storage duration)(自 C++20 起),则如果实体是:隐式捕获实体,如果实体是

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

[编辑] 示例 (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) [编辑]
任何可调用对象的仅移动包装器 (move-only wrapper),该对象在给定的调用签名中支持限定符 (qualifiers)
(类模板)(class template) [编辑]

[编辑] 外部链接 (External links)

嵌套函数 (Nested function) - 在另一个(外围)函数中定义的函数。