lambda表达式(1)
代码存储位置:shanchuann/Modern_CPP
lambda 表达式是 C++11 引入的匿名函数,是一个右值表达式,其类型为唯一的未命名非联合非聚合类类型,称为闭包类型(closure type),它被声明在包含 lambda 表达式的最小块作用域、类作用域或命名空间作用域中(用于 ADL)。它可以在需要函数的地方直接定义,无需单独声明,极大简化了代码编写。
1 | //lambda 表达式是 C++11 引入的**匿名函数**,属于*无名的非联合非聚类*类型 |
这是一个有
operator()
的类,在默认情况下operator()
函数是一个 const 函数,他的 this 指针被 const 修饰。
1 |
|
经过 C++ Insights 将源代码转化为编译器的推导结果后
1 |
|
可以很明确的发现默认情况下 operator()
函数是一个 const 函数。
总而言之,lambda 表达式的底层是一个编译器生成的匿名类(闭包类型),该类会重载 operator()
(函数调用运算符)。默认情况下,这个 operator()
是 const 成员函数,而按值捕获的外部变量会被存储为该类的成员变量。
在 C++ 中,const 成员函数内部不能修改非 mutable
的成员变量。因此:
- 按值捕获的变量在 lambda 内部默认是 “只读” 的(因为闭包的
operator()
是 const,且成员变量非 mutable)。 - 若要在 lambda 内部修改按值捕获的副本,必须用
mutable
关键字取消operator()
的 const 属性(C++23 前需要在[]
后添加()
使得编译器不报错)。
1 | //添加`()mutable`前 |
添加后:
1 | int main() { |
const 消失。
基本语法
lambda 表达式的完整语法格式如下:
1 | [捕获列表](参数列表) mutable noexcept/throw() -> 返回类型 { 函数体 } |
各部分的含义:
- 捕获列表:指定 lambda 表达式能访问的外部变量(局部作用域的变量),是 lambda 与外部环境交互的关键(详见下文)。
- 参数列表:与普通函数的参数列表一致,可省略(无参数时)。
- mutable:可选关键字,允许修改按值捕获的变量(默认按值捕获的变量是 const,不可修改)。
- noexcept/throw ():可选,指定函数是否抛出异常(C++11 起)。
- -> 返回类型:可选,指定函数返回值类型。若函数体只有一个
return
语句,编译器可自动推导,此时可省略。 - 函数体:lambda 的具体逻辑,与普通函数体一致。
捕获列表
捕获列表用于控制 lambda 对外部局部变量的访问权限,常见形式如下:
捕获方式 | 含义 |
---|---|
[] |
空捕获,不访问任何外部变量 |
[=] |
按值捕获所有外部变量(副本) |
[&] |
按引用捕获所有外部变量(原变量) |
[x] |
仅按值捕获变量 x(副本) |
[&y] |
仅按引用捕获变量 y(原变量) |
[=, &x] |
除 x 按引用捕获外,其余按值捕获 |
[&, x] |
除 x 按值捕获外,其余按引用捕获 |
[this] |
在类中,捕获当前对象(可访问成员) |
捕获示例:
1 |
|
用户定义转换函数仅在 lambda 表达式没有 捕获列表 且没有显式对象参数时才定义(C++23 起)。它是闭包对象的公共、constexpr、(C++17 起)非虚、非显式、const noexcept 成员函数。
1 | //普通lambda的转换函数 |
参数与返回类型
- 参数列表:与普通函数一致,支持默认参数(C++14 起),甚至泛型参数(
auto
,C++14 起,称为 “泛型 lambda”)。 - 返回类型:若函数体只有一个
return
,可省略-> 返回类型
,编译器自动推导。
1 |
|
泛型 lambda
泛型 lambda(Generic Lambda)是 C++14 引入的重要特性,它允许 lambda 表达式的参数使用 auto
关键字声明,从而让 lambda 能够接受任意类型的参数,实现类似模板函数的通用逻辑。
泛型 lambda 的定义与语法
泛型 lambda 的核心是 ** 参数列表中使用 auto
**,语法格式如下:
1 | [捕获列表](auto 参数1, auto 参数2, ...) -> 返回类型 { 函数体 } |
与普通 lambda 相比,仅参数类型用 auto
替代了具体类型(如 int
、double
等)。编译器会根据传入的实际参数自动推导类型,实现 “一次定义,多类型适用”。
1 | int main() { |
当然,写成模板形参同样可行
1 | auto p = []<typename T>(T n) { cout << n <<endl; }; |
输出:
1 | 10 |
常量表达式
如果 operator () 满足 constexpr 函数的要求,则它总是 constexpr。如果 lambda 说明符中使用了关键字 constexpr,它也是 constexpr。
1 | int main() { |
隐式 constexpr
:operator()
自动满足条件时
在 C++17 及以上标准中,若 lambda 的 operator()
满足 constexpr
函数的所有要求 ,则该 operator()
会被隐式声明为 constexpr
,无需显式添加 constexpr
说明符。
constexpr
函数的核心要求(适用于 lambda 的 operator()
):
- 函数体中不能包含汇编语句、
goto
、非constexpr
函数调用(除非在未求值上下文中)等。 - 不能修改非
mutable
的非constexpr
变量(对 lambda 而言,若按值捕获非constexpr
变量,修改会违反此条)。 - 参数和返回类型必须是字面类型(literal type,如基本类型、constexpr 构造的类等)。
- C++17 中,lambda 不能有捕获(或仅捕获
constexpr
变量,C++20 放宽);C++20 允许捕获更多类型(如constexpr
对象的引用、用constexpr
初始化的按值捕获等)。
示例
1 | // C++17及以上 |
这里的 add
lambda 未显式声明 constexpr
,但因其 operator()
满足所有 constexpr
要求(无捕获、操作可在编译期完成),故 operator()
被隐式视为 constexpr
,可用于编译期计算。
显式 constexpr
:强制 operator()
为 constexpr
若在 lambda 中显式添加 constexpr
说明符(语法:constexpr [捕获列表](参数列表) { ... }
),则编译器会强制 operator()
必须满足 constexpr
函数的要求。若不满足,编译会直接报错(即使隐式情况下可能不触发错误)。
示例
1 | // C++17及以上 |
显式 constexpr
与隐式 constexpr
的区别
场景 | 隐式 constexpr (无 constexpr 说明符) |
显式 constexpr (有 constexpr 说明符) |
---|---|---|
触发条件 | 自动判断:若 operator() 满足 constexpr 要求,则隐式为 constexpr |
强制要求:operator() 必须满足 constexpr 要求,否则编译报错 |
用途 | 可在编译期 / 运行时调用(根据上下文自动适配) | 明确告知编译器和开发者:此 lambda 设计为可在编译期执行,且必须满足条件 |
兼容性 | 若后续修改导致不满足 constexpr 要求,仅影响编译期调用的场景 |
若后续修改导致不满足 constexpr 要求,直接编译报错(无论是否用于编译期) |