Namespaces
Variants

Lambda expressions (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

目录

语法

无显式模板参数列表的Lambda表达式(可能为非泛型)
[ 捕获列表  ] 前属性  (可选) ( 参数  ) 说明符  (可选) 异常说明  (可选)
后属性  (可选) 尾随返回类型  (可选) 要求  (可选) 契约规范  (可选) { 函数体 }
(1)
[ 捕获列表  ] { 函数体 } (2) (C++23 前)
[ 捕获列表  ] 前属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 } (2) (C++23 起)
[ 捕获列表  ] 前属性  (可选) 异常说明
后属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 }
(3) (C++23 起)
[ 捕获列表  ] 前属性  (可选) 说明符 异常说明  (可选)
后属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 }
(4) (C++23 起)
带显式模板参数列表的Lambda表达式(始终为泛型) (since C++20)
[ 捕获  ] < 模板参数  > 模板要求  (可选)
前属性  (可选) ( 参数  ) 说明符  (可选) 异常说明  (可选)
后属性  (可选) 尾随返回类型  (可选) 要求子句  (可选) 契约规范  (可选) { 函数体 }
(1)
[ 捕获  ] < 模板参数  > 模板要求  (可选) { 函数体 } (2) (C++23 前)
[ 捕获  ] < 模板参数  > 模板要求  (可选)
前属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 }
(2) (C++23 起)
[ 捕获  ] < 模板参数  > 模板要求  (可选) 前属性  (可选) 异常说明
后属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 }
(3) (C++23 起)
[ 捕获  ] < 模板参数  > 模板要求  (可选) 前属性  (可选) 说明符 异常说明  (可选)
后属性  (可选) 尾随返回类型  (可选) 契约规范  (可选) { 函数体 }
(4) (C++23 起)
1) 带参数列表的lambda表达式。
2-4) 无参数列表的 lambda 表达式。
2) 最简单的语法。 back-attr 无法应用。
3,4) back-attr 仅当存在 specs except 时才能应用。

说明

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

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

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

可包含 显式对象形参

(C++23 起)
specs - 下列说明符的列表,每个说明符在每个序列中最多出现一次。
说明符 作用
mutable 允许 body 修改按值捕获的对象,并调用其非const成员函数。
  • 存在显式对象形参时不可使用。
(C++23 起)
constexpr
(C++17 起)
显式指定 operator ( ) constexpr函数
  • operator ( ) 满足所有constexpr函数要求,即使未出现 constexpr operator ( ) 也将是constexpr。
consteval
(C++20 起)
指定 operator ( ) 立即函数
  • consteval constexpr 不能同时指定。
static
(C++23 起)
指定 operator ( ) 静态成员函数
  • static mutable 不能同时指定。
  • 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__ ,其语义说明详见 此处

闭包类型

lambda 表达式是一种独特的无名非 联合 聚合 类类型的纯右值表达式,称为 闭包类型 。该类型(出于 ADL 目的)声明于包含该 lambda 表达式的最小块作用域、类作用域或命名空间作用域内。

当且仅当 捕获列表 为空时,该闭包类型为 结构类型

(C++20 起)

闭包类型拥有以下成员,它们不能被 显式实例化 显式特化 ,或 (since C++14) 友元声明 中指名:

ClosureType:: operator()( params )

ret operator ( ) ( params ) { body }
(可能包含 static 和 const,详见下文)
template < template - params >
ret operator ( ) ( params ) { body }
(C++14 起)
(泛型 lambda,可能包含 static 和 const,详见下文)

当被调用时执行 lambda 表达式的主体。访问变量时,访问其被捕获的副本(对于按值捕获的实体),或访问原始对象(对于按引用捕获的实体)。

operator ( ) 的参数列表是 params (如果提供了该参数列表),否则参数列表为空。

operator ( ) 的返回类型是在 trailing 中指定的类型。

如果未提供 trailing ,则 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 ( ) 是一个 静态成员函数

如果 params 包含显式对象参数,则 operator ( ) 是一个 显式对象成员函数

(C++23 起)


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

// 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 ( ) 一起使用。对于 params 中类型被指定为 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 表达式上的异常规范 except 适用于 operator ( )

出于 名称查找 、确定 this 指针 的类型和值以及访问非静态类成员的目的,闭包类型的 operator ( ) </span

  1. 尽管函数返回类型推导在 C++14 中引入,但其规则在 C++11 中已可用于 lambda 返回类型推导。

ClosureType:: operator ret (*)( params )()

无捕获的非泛型 lambda
using F = ret ( * ) ( params ) ;
operator F ( ) const noexcept ;
(C++17 前)
using F = ret ( * ) ( params ) ;
constexpr operator F ( ) const noexcept ;
(C++17 起)
无捕获的泛型 lambda
template < template - params > using fptr_t = /* 见下文 */ ;

template < template - params >

operator fptr_t < template - params > ( ) const noexcept ;
(C++14 起)
(C++17 前)
template < template - params > using fptr_t = /* 见下文 */ ;

template < template - params >

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

用户定义转换函数 仅在 lambda 表达式没有 captures  且没有显式对象参数 (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); // 错误:不可转换
h(glambda);  // OK:调用 #1,因为 #2 不可转换
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 ( ) 是静态的,则是指向该 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</

ClosureType:: ClosureType()

ClosureType ( ) = default ;
(since C++20)
(仅当未指定任何捕获时)
ClosureType ( const ClosureType & ) = default ;
ClosureType ( ClosureType && ) = default ;

闭包类型不满足 默认构造要求 。闭包类型没有默认构造函数。

(until C++20)

如果未指定 捕获 ,则闭包类型具有默认的默认构造函数。否则,它没有默认构造函数(这包括存在 默认捕获 的情况,即使它实际上未捕获任何内容)。

(since 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:: Captures

T1 a ;

T2 b ;

...

若lambda表达式通过复制捕获任何内容(隐式使用捕获子句 [=] 或显式使用不包含&字符的捕获,例如 [a, b, c] ),则闭包类型包含以未指定顺序声明的无名非静态数据成员,这些成员持有所有被如此捕获实体的副本。

对应无初始化器的捕获的数据成员,在lambda表达式求值时进行 直接初始化 。对应带初始化器的捕获的数据成员,按初始化器要求进行初始化(可能是复制初始化或直接初始化)。若捕获数组,则按索引递增顺序直接初始化数组元素。数据成员的初始化顺序与其声明顺序一致(该顺序未指定)。

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

对于通过引用捕获的实体(使用 capture-default [&] 或使用&字符时,例如 [&a, &b, &c] ),闭包类型中是否声明额外数据成员是未指定的 ,但任何此类额外成员必须满足 LiteralType 要求 (C++17起)


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

(C++20 前)

Lambda 捕获

捕获列表 定义了可从 lambda 函数体内部访问的外部变量。其语法定义如下:

捕获默认 (1)
捕获列表 (2)
捕获默认 , 捕获列表 (3)
capture-default - & = 其中之一
capture-list - 以逗号分隔的 capture 列表


捕获 的语法定义如下:

标识符 (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)
{
    [&] {};          // 正确:默认引用捕获
    [&, i] {};       // 正确:引用捕获,但 i 通过拷贝捕获
    [&, &i] {};      // 错误:当默认捕获为引用时不能显式声明引用捕获
    [&, this] {};    // 正确,等价于 [&]
    [&, this, i] {}; // 正确,等价于 [&, i]
}

如果 捕获默认符 = ,后续的简单捕获必须以 & 开头或是 *this (C++17起) this (C++20起)

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // 正确:默认按值捕获
    [=, &i] {};    // 正确:按值捕获,但 i 按引用捕获
    [=, *this] {}; // C++17 前:错误:无效语法
                   // C++17 起:正确:按值捕获外围的 S2 对象
    [=, this] {};  // C++20 前:错误:当 = 为默认捕获时不能显式捕获 this
                   // C++20 起:正确,与 [=] 相同
}

任何捕获只能出现一次,且其名称必须与任何参数名称不同:

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // 错误:重复捕获 i
    [this, *this] {}; // 错误:重复捕获 "this" (C++17)
    [i] (int i) {};   // 错误:参数与捕获变量同名
}

如果变量满足以下条件,lambda表达式可以不捕获而直接使用该变量:

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

如果变量满足以下条件,lambda表达式可以在不捕获的情况下读取其值:

  • 具有常量非易失性整型或枚举类型,且已使用 常量表达式 进行初始化,或
  • 是 constexpr 类型且不包含可变成员。

当前对象( * this )在存在任一捕获默认符时可被隐式捕获。若被隐式捕获,它始终通过引用捕获,即使捕获默认符为 = 当捕获默认符为 = 时对 * this 的隐式捕获已被弃用。 (since C++20)

仅满足以下任一条件的lambda表达式可以具有不带初始化器的 capture-default capture

(since C++26)

对于此类 lambda 表达式, 可达作用域 被定义为从最内层封闭函数(及其参数)开始的所有外围作用域集合。这包括嵌套的块作用域,以及当该 lambda 嵌套时外围 lambda 的作用域。

在任意不带初始化器的捕获项(除 this 捕获外)中, 标识符 会通过 lambda 的 可达作用域 中使用常规 非限定名称查找 进行查找。查找结果必须是声明于可达作用域中且具有自动存储期的 变量 ,或是其对应变量满足上述要求的 结构化绑定 (C++20 起) 。该实体被 显式捕获

带有初始化器的捕获(称为 初始化捕获 )的行为类似于声明并显式捕获一个以类型说明符 auto 和相同初始化器声明的变量,其声明区域是 lambda 表达式的主体(即不在其初始化器的作用域内),但以下情况除外:

  • 若捕获为按值捕获,则闭包对象引入的非静态数据成员是该变量的另一种引用方式;
    • 换言之,源变量实际上并不存在,通过 auto 进行的类型推导和初始化都应用于该非静态数据成员;
  • 若捕获为按引用捕获,则引用变量的生命周期随闭包对象生命周期的结束而结束。

这用于通过诸如 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;
}(); // 将 ::x 更新为 6 并将 y 初始化为 25。
(C++14 起)

如果 captures 具有 capture-default 且未显式捕获封闭对象(作为 this * this ),或在 lambda 主体中 可 ODR 使用 的自动变量 ,或对应变量具有原子存储期的 结构化绑定 (C++20 起) ,则当该实体在表达式中的潜在求值表达式(包括在使用非静态类成员前隐式添加 this - > 时)内被命名时,会隐式捕获该实体。

为确定隐式捕获的目的, typeid 永远不会被视为使其操作数处于未求值状态。

即使实体仅在 lambda 体实例化后的 被丢弃语句 中被命名,也可能被隐式捕获。

(since 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); };           // 正确:调用 #1,不捕获 x
    auto g0 = [](auto a) { f(x); };  // 同上
    auto l1 = [=]{ f(x); };          // 正确:捕获 x(自 P0588R1 起)并调用 #1
                                     // 该捕获可被优化消除
    auto g1 = [=](auto a) { f(x); }; // 同上
    auto ltid = [=]{ typeid(x); };   // 正确:捕获 x(自 P0588R1 起)
                                     // 即使 x 是未求值操作数
                                     // 该捕获可被优化消除
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // 正确:是依赖表达式,因此捕获 x
    };
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // 无论 a + x 是否为未求值操作数
                        // 都会捕获 x
    };
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // 正确:w 未被 ODR 使用,无需捕获
    // auto l5 = [=]{ return w.n_; };  // 错误:w 需要通过拷贝捕获
}

如果 lambda 表达式的主体 ODR 使用 了通过复制捕获的实体,则访问的是闭包类型的成员。如果未 ODR 使用该实体,则访问的是原始对象:

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // 非odr使用:引用的是g函数中的常量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(); // 正确:输出 5
}

在具有捕获默认值 = 的 lambda 表达式主体内,任何可捕获实体的类型都如同被捕获一样处理(因此如果该 lambda 不是 mutable ,通常会添加 const 限定),即使该实体处于未求值操作数中且未被实际捕获(例如在 decltype 中):

void f3()
{
    float x, &r = x;
    [=]
    { // x 和 r 未被捕获(在 decltype 操作数中的出现不是 odr-use)
        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;// 正确
            }
        }();
    }
    auto g()
    {
        return [] // 不捕获任何变量
        { 
            return [*this] {};// 错误:外部lambda表达式未捕获*this
        }();
    }
};

如果 lambda 表达式 (或泛型 lambda 函数调用运算符的特化) (since 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;    // 正确:m 被 m4 隐式捕获
                               // 并被 m3 显式捕获
                    x += i;    // 错误:i 超出可达作用域
                               // (作用域终止于 work())
                    x += f;    // 正确: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); };  // 正确,复制捕获
        i = 1; x = 1; l2(); // 调用 use(0,0)
        auto l3 = [i, &x = x] { use(i, x); }; // 正确,引用捕获
        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 表达式出现在 默认参数 中,它不能显式或隐式捕获任何内容 ,除非所有捕获都具有满足默认参数中表达式约束的初始化器 (since 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; }() );       // 正确:无捕获
    void g5( int = [] { return sizeof i; }() ); // 正确:无捕获
    // C++14
    void g6( int = [x = 1] { return x; }() ); // 正确:数字1可以出现在默认参数中
                                              //     
    void g7( int = [x = i] { return x; }() ); // 错误:变量i不能出现在默认参数中
                                              //        
}

匿名联合体 的成员不能被捕获。 位域 只能通过复制方式捕获。

如果嵌套的 lambda m2 捕获了某个也被直接外层 lambda m1 捕获的实体,那么 m2 的捕获将按以下方式转换:

  • 如果外层 lambda m1 通过复制捕获, m2 捕获的是 m1 闭包类型的非静态成员,而非原始变量或 * this ;若 m1 不可变,其非静态数据成员被视为 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 表达式捕获了任何内容,则其函数调用运算符的显式对象参数(若存在)的类型只能是:

  • 闭包类型,
  • 公开且明确继承自闭包类型的类类型,或
  • 指向可能带有 cv 限定符的此类类型的引用。
struct C 
{
    template<typename T>
    C(T);
};
void func(int i) 
{
    int x = [=](this auto&&) { return i; }();  // 正确
    int y = [=](this C) { return i; }();       // 错误
    int z = [](this C) { return 42; }();       // 正确
    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}(); // 错误
}
(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) 通过值捕获 * this 作为 [ = , * this ]
__cpp_constexpr 201603L (C++17) constexpr lambda
__cpp_static_call_operator 202207L (C++23) 无捕获Lambda的静态 operator ( )

隐式 lambda 捕获规则因缺陷报告 P0588R1 发生了细微变化。截至 2023年10月,部分主流实现尚未完全应用该缺陷报告,因此在某些情况下仍在使用基于检测 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';
    // 闭包的类型无法命名,但可以用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';
    constexpr int fib_max {8};
    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 << "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) -> int
    {
         return n ? self(n - 1, a + b, a) : b;
    };
    <

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 应用于 发布时的行为 正确行为
CWG 974 C++11 lambda 表达式的参数列表中不允许使用
默认参数
允许
CWG 1048
( N3638 )
C++11 返回类型只能对仅包含一个 return 语句的
lambda 函数体进行推导
改进了返回
类型推导
CWG 1249 C++11 未明确外层非可变 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 ( ) 具有相同效果
在闭包类型的
默认构造实例上
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 可能具有
到不期望的函数指针类型的转换函数
它没有这样的
转换函数
CWG 2881 C++23 当继承非公开或存在歧义时,具有显式参数的
operator ( ) 可能为派生类实例化
使其非良构
P0588R1 C++11 隐式 lambda 捕获规则检测 odr-use 检测被简化

参见

auto specifier (C++11) 指定从表达式推导出的类型
(C++11)
任何可复制构造的可调用对象的可复制包装器
(类模板)
支持给定调用签名中限定符的任何可调用对象的仅移动包装器
(类模板)

外部链接

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