# 纲要
> 主干纲要、Hint/线索/路标
- lambda 表达式
- lambda 的语法形式
- lambda 的捕获列表
- 可捕获目标
- 捕获方式
- 初始化捕获:可在捕获列表中使用 "**初始化表达式**",甚至捕获移动对象。
- lambda 的返回类型
- lambda 使用示例
- lambda 实现递归
- lambda 作为函数返回类型
- 泛型 lambda(C++14)
- 全局 lambda(C++17)
%%
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
<br><br><br>
# lambda 表达式
lambda 表达式是定义一个 "**匿名函数对象**"(Functor) 的语法糖。
### 闭包
闭包(closure)是指由 lambda 表达式生成的**匿名函数对象**。
通过 `[](){...}` 语法声明一个 "**lambda 表达式**" 时,编译器完成以下工作:
1. 生成一个唯一的 **==匿名类类型==**,称之为 "**==闭包类==**"。
2. 实例化该匿名类的一个 **临时==函数对象==**,称之为 "**==闭包==**"。
因此,`auto f = [](){...};` 即是**为 lambda 表达式生成的闭包对象** 赋予一个变量名。
在闭包类中:
- lambda 函数体作为 **函数调用运算符的重载**:`operator()() const { ... }`
- 捕获列表捕获的变量会作为 "**匿名类**" 的数据成员(按值捕获是 "**拷贝副本**",按引用捕获则是 "**引用**")
- 编译器自动生成相应的构造函数,初始化其数据成员。
<br><br>
## lambda 表达式的语法形式
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-F9344B27771318514B954FDC20CBCE84.png|598]]
lambda 表达式的语法形式包括以下部分:
- **捕获列表**
- **参数列表**(无参数时可省略,支持指定 **默认参数**)
- **函数修饰符**(可选):`mutable` 与 `noexcept`
- **尾置返回类型**(可省略,由编译器自动推断)
- **函数体**
```cpp
// lambda表达式可省略参数列表和返回类型,但必须声明捕获列表和函数体
auto f = [] { return 42 }; // 无参数的lambda
cout << f() << endl;
```
<br><br><br>
# lambda 的捕获列表
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-D893AD586CA0A247E3B17209C3852F20.png|690]]
<br>
## 可捕获目标
捕获列表仅用以获取 "**lambda 创建时所在的外层作用域下的==非静态局部变量==**"。
**==全局变量==**、**==全局静态变量==**、**lambda 创建时所在作用域的==局部静态变量==**,均可在 lambda 函数体内直接访问,无需捕获。
> [!caution] lambda 只能捕获其被创建时 "**所在作用域里的==非静态局部变量==**"(包括形参)
>
> 在类成员函数中,默认按值捕获时`[=]`,捕获的是 "this" 指针,而非类的数据成员[^1]。
>
> ```cpp
> using FilterContainer = std::vector<std::function<bool(int)>>;
> FilterContainer filters; // 一个全局容器, 存放过滤函数
>
> class MyClass {
> public:
> ...
> void alddFilter() const;
> private:
> int divisor;
> };
>
> void MyClass::addFileter() const {
> filters.emplace_back(
> [=](int value) { return value % divisor == 0; }
> );
> // 按值捕获`=`真正捕获的是"this"指针, lambda 函数体内的 divisor 其实是 this->divisor.
> // 一旦一个类实例被销毁, 则存入 filters 中的闭包里的 this 指针就会失效!
> //
> // 如果尝试显示捕获 divisor, 捕获列表改为`[divisor]`, 就会编译失败.
> // 因为 divisor 并不是"局部变量", 而是类的成员变量,类的成员无法被直接捕获。
> }
> ```
>
> [!caution] lambda 的 "函数形参" 与 "捕获变量" 同名时,后者将被隐藏!
>
> 该情况下,函数体内只能访问到 "同名形参"!
>
> ```cpp
> int main() {
> int x = 100;
> auto lambda = [x](int x) {
> cout << "x inside lambda: " << x << endl; // 函数体内, 同名形参会隐藏"捕获的外层作用域下同名变量", 无法访问后者.
> };
> lambda(42); // 输出 42,而不是 100
> }
> ```
>
> [!caution] C++20 之前,不支持 lambda 捕获外层作用域中通过 "**==结构化绑定==**" 声明的变量!
>
> ```cpp
> auto [x, y] = pii;
>
> auto f = [x, y]() { // Warning: Captured structed bindings are a C++20 extension
> cout << x << " " << y << endl;
> };
> ```
>
>
> [!error] 静态数组名被 "默认捕获" 时,退化为 "指向数组元素类型的指针",不能应用结构化绑定!
>
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-7E95FE7BADB256FCAD16C6D6A138AE36.png|579]]
>
> 解决办法:用 `&dirs` 显式捕获数组名
>
> ```cpp
> auto dfs = [&dirs](auto& self) { // `&dirs` 显式指定捕获数组名, 不会退化
> for (auto [dx, dy] : dirs) {
> cout << dx << dy << endl;
> }
> };
> ```
>
<br><br>
## 捕获方式
lambad 支持两种捕获方式:
- **按值捕获**: 在 "lambda 创建时" **拷贝捕获变量的 "==副本=="**,作为闭包的数据成员。
- **按引用捕获** :在 "lambad 创建时" 建立**对捕获变量的 "==引用=="**,作为闭包的数据成员。
> [!caution] "按值捕获" 的变量在 lambda 函数体内默认不可修改
>
> 本质原因:**lambda 表达式生成的闭包类的函数调用运算符的重载默认为 `operator()() const`**,参见 [^2] [^3]
>
> 若要在函数体内修改 "**按值捕获**" 的副本,需 **==声明 `mutable` 函数修饰符==**。
>
> ```cpp
> int x = 10;
> auto lambda = [x]() mutable {
> x += 1; // lambda 必须声明为`mutable`, 否则不可修改"按值捕获" 的值.
> };
> ```
>
> [!NOTE] "按引用捕获" 的变量在 lambda 函数体内可被直接修改
>
> 本质原因:**类的 const 成员函数中本身就可修改 "==引用成员=="**,如下所示:
>
> ```cpp
> class FuncObj {
> public:
> FuncObj(int v, int& r) : var(v), ref(r) {}
>
> void operator()() const {
> // var = 42; // 错误: const 成员函数内, 不能修改"值类型的成员变量"
> ref = 42; // 正确: const 成员函数内, 可以修改"引用类型的成员变量",
> // 原因在于修改的不是"引用这一成员本身", 而是"成员所引用的外部对象".
> // 编译器在实现时, 对"引用成员"实际实现为"指针", 存储的是被引用对象的地址.
> // 因此, 在 const 成员函数内"修改引用", 等价于"修改指针所指对象", 而非"修改指针本身", 故可编译通过.
> }
>
> private:
> int var;
> int& ref;
> };
> ```
>
>
说明示例:
```cpp
int x = 10, y = 20;
// 捕获变量x, 通过值捕获, x的副本被存储在lambda函数对象中;
auto lambda1 = [x] { /* 使用x; 但不能修改值捕获的x */ };
// 通过"值捕获"得到的变量, 如果要在lambda内修改其值, 则必须在参数列表后加上关键字mutable;
// 值捕获下, 内部对x的修改不会影响外部作用域的x;
auto lambda11 = [x]() mutable { return ++x; } // ✔
// 捕获变量x, 引用捕获, lambda内部引用外部作用域的x,lambda内部对x的修改即是对外部x的修改;
auto lambda2 = [&x] { /* lambda内部引用外部作用域的x; */ };
// 通过值捕获x, 通过引用捕获y;
auto lambda3 = [x, &y] { /* 使用x和y, */ };
// 隐式捕获, 让编译器根据lambda函数体中的代码自动推导需要捕获的变量 ↓↓↓
// 通过值捕获使用外部任意变量
auto lambda4 = [=] { /* 通过值捕获的形式使用任意外部变量 */};
// 通过引用捕使用外部任意变量
auto lambda5 = [&] { /* 通过引用捕获的形式使用任意外部变量*/};
// 混合捕获模式(捕获列表中的首项必须是"="或"&", 表示默认捕获方式)
auto lambda6 = [=, &x] { /*对x引用捕获, 而对其他所有变量进行值捕获 */ };
auto lambda7 = [&, x] { /*对x值捕获, 而对其它所有变量进行引用捕获 */}
auto lambda8 = [this] { /*捕获当前类中的this指针,如果已经使用了&或者=就默认添加此选项*/ };
```
<br><br>
## 初始化捕获(C++14)
> 初始化捕获,也称 "**通用 lambda 捕获**"(generalized lambda capture)
C++14 起支持 "**初始化捕获**",可在捕获列表中 "**显式==定义并初始化==指定变量**",而不仅是捕获既有的外层局部非静态变量。
具体支持三种形式[^4]:
- **按值** 初始化捕获:`[变量名 = 初始化表达式]`
- **按引用** 初始化捕获: `[&变量名 = 初始化表达式]`
- **移动捕获**:`[变量名 = std::move(外部变量)]` 或 `[变量名 = 纯右值外部变量]`
说明示例:
```cpp
int x = 42;
auto lambda = [value = x]() { // 按值捕获 x: 拷贝给 lambda 内部变量 value.
reurn value + 1;
};
auto lambda = [&value = x]() { // 按引用捕获 x: 赋给 lambda 内部引用 value
return value + 1;
};
auto lambda = [vec = std::move(v)]() { // 将外部 v 移动赋值给 lambda 内部 vec.
...
};
auto lambda = [ptr = std::make_unique<Widget>()] { // 构造一个临时量, 传递给ptr (移动捕获)
...
};
```
初始化捕获的常用场景:向 lambda 函数体内传递 `unique_ptr`
```cpp
auto ptr = std::make_unique<int>(10);
auto f = [p = std::move(ptr)] { // 捕获列表以移动语义接收外层作用域的unique_ptr
ffunc(std::move(p));
};
```
<br>
#### 初始化捕获的实现方式
在不支持 **移动捕获** 的 C++11 中,有两种方式可手动实现该功能:
- (1)**使用 bind 绑定被 "移动" 对象**,作为参数传递;
- (2)自定义 "**仿函数**"
```cpp
// 使用 "移动捕获" (需要C++14)
auto lambda = [pw = std::make_unique<Widget>()] {
...
};
lambda();
// (1) 等效实现: 使用std::bind
auto fun_b = std::bind([](const std::unique_ptr<Widget> &pw){
... // 使用pw.
}, std::make_unique<Widget>()); // 绑定给bind的成员是左值, lambda的形参接收的是左值引用.
fun_b();
// (2) 等效实现: 使用"仿函数"
class MyFunc {
public:
explicit MyFunc(std::unique_ptr<Widget>&& ptr): pw(std::move(ptr)) {}
bool operator()() const {
... // 使用pw
}
private:
std::unique_ptr<Widget> pw;
};
auto func = MyFunc(std::make_unique<Widget>())(); // 创建一个函数对象.
func();
```
<br><br><br>
# lambda 的返回类型
lambda 表达式省略 "**返回类型**" 时,**将由==编译器自动推断==其返回类型**:
- 若 lambda 函数体中**只有==唯一的 return 语句==**,则编译器**根据 `return` 的返回值类型推断**;
- 若 lambda 函数体中**还有除 `return` 之外的其他语句**,则**编译器==默认推断其返回类型为 `void`==**。
#### 🚨 返回类型的注意事项
> [!caution] 若 `lambda` 函数体**有除 `return` 外的其他语句,且==具有返回值==**,则 **==必须显式声明返回类型==**,否则编译报错。
>
> 原因:**编译器推断的返回类型为 `void`**,而返回类型为 void 的函数不能具有返回值。
> [!caution] `lambda` 表达式用于递归时,**必须显式声明返回类型(即使返回 `void`)** or **必须在递归调用前存在至少一个 `return` 语句**,否则编译错误。
>
>
> 原因:递归调用时,**还未见到完整函数定义**,因此**无法自动推断返回类型为`void`**,因此报错如下:
>
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-2FEF4D2CB53D91AE1789E9BFA8728088.png|556]]
>
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-343E6999BF416B623796DA6FFA0AC6F9.png|564]]
>
> 解决方法:显式声明返回类型为 `void`,如下:
>
> ![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-lambda 表达式.assets/IMG-cpp-lambda 表达式-CAC015BB4A7F66E2273EC33EE172D3C0.png|566]]
>
>
<br><br><br>
# lambda 使用示例
lambda 用于便捷定义比较函数:
```cpp
// 示例一: 使用lambda表达式作为容器的比较函数
auto comp = [](int a, int b) { return a > b; };
priority_queue<int, vector<int>, decltype(comp)) min_heap(comp); // decltype推导函数类型
// 示例二: 使用lambda表达式实现对string类的自定义排序.
sort(words.begin(), words.end(),
[](const string &a, const string &b) { return a.size() < b.size(); });
// 示例三: 使用lambda表达式实现对idx数组的自定义排序: idx为下标数组, 根据下标索引得到的growTime容器中的值, 由此对下标idx进行排序.
sort(idx.begin(), idx.end(),
[&]{const int &i, const int& j} { return growTime[i] < growTime[j]; });
```
<br>
## lambda 表达式实现递归
可通过以下方法实现 lambda 递归:
1. 方式一:使用 `std::function` 为 lambda 提供一个包装,从而在 lambda 内部使用该可调用对象
2. 方式二:通过 **lambda 参数** 来传递 lambda 自身(**推荐**)。
> [!error] lambda 本身是匿名类,故不能通过 "捕获" 的方式在函数体中递归调用 "匿名函数对象"。
>
> ```cpp
> // Wrong Code
> // 下述代码会报错, lambda 内部无法引用自己.
> // Variable 'lmb' declared with deduced type 'auto' cannot appear in its own initializer
> auto func = [&](int u) -> int {
> if (n <= 1) return 1;
> return n * func(n - 1);
> }
> ```
>
#### 方式一
**使用 `function` 对象**包装 lambda 表达式,并对其进行**引用捕获**,从而**在 lambda 表达式内部调用 function 对象**实现递归
```cpp
int main() {
// 方式一:
function<int(int, int)> dfs = [&](int i, int j) -> int {
// ...
// lambda表达式声明"捕获外部引用"后, 内部直接调用 dfs 这一function对象
return dfs(i+1, j-1); // 内部递归调用
};
dfs(0, n-1); // 外部调用
}
```
#### 方式二(推荐)
通过一个**额外的函数形参**来传递 lambda 对象自身:
```cpp
int main() {
auto dfs = [](auto& self, int i, int j) -> int { // 写成`auto& self`, 或者`auto&& self`形式都行.
// ...
// lambda表达式使用一个额外的参数接受其自己, 同时
return self(self, i+1, j-1); // 内部递归调用
}
dfs(dfs, 0, n - 1); // 外部调用
}
```
%%
> 什么是 Y 组合子?暂时看不太懂,以后再说吧
>
> 
%%
<br><br>
## 将 lambda 对象作为函数返回类型
将 lambda 对象作为函数返回只时,该函数的返回类型可通过以下方式声明:
1. 使用 **`auto` 自动推导返回类型**(since C++14)
2. 使用 **`std::function<>` 指明返回类型**
方式一:
```cpp
#include <iostream>
auto returnLambda() { // since C++14
return [] (int x, int y) {
return x*y;
};
}
int main() {
auto lf = returnLambda();
std::cout << lf(6, 7) << std::endl;
}
```
方式二:
```cpp
// 使用std::function<> 类模版指明一个一般化类型, 从而可接收lambda函数对象.
#include <functional>
#include <iostream>
std::function<int(int, int)> returnLambda() {
return [] (int x, int y) {
return x*y;
};
}
int main() {
auto lf = returnLambda();
std::cout << lf(6, 7) << std::endl;
}
```
> [!quote]
> 
<br><br><br>
# 泛型 lambda(C++14)
C++14 引入了 "**==泛型 lambda==**"(generic lambdas),可使用 `auto` 作为类型声明符,由编译器对 **形参类型** 自动推导。
同时,也支持在 lambda 中实现 "**完美转发**"。
> [!info] 本质上,泛型 lambda 是对 "**成员函数模版**" 的语法糖 [^5]。
> [!NOTE] 在泛型 lambda 中,要实现完美转发,可将 `decltype(x)` 作为模版参数传递给 `std::forward<>`
>
> 参见 [^6]:
>
> ```cpp
> auto f = [](auto&& x) {
> return func(std::forward<decltype(x)>(x));
> };
>
> auto f = [](auto&&... params) {
> return func(std::forward<decltype(params)>(params)...);
> }
> ```
>
> ^u95vez
<br><br><br>
# 全局 lambda(C++17)
> [!info] C++17 起,允许在 "**全局命名空间作用域**" 下定义 "==全局 lambda==",但**其不能捕获任何外部全局变量**。
<br><br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
<br><br>
# ♾️参考资料
# Footnotes
[^1]: 《Effective Morder C++》Item31
[^2]: 《C++ Primer》(P508)
[^3]: 《C++程序设计语言》(P252)
[^4]: 《Effective Morder C++》Item32
[^5]: 《C++ Templates》(P65)
[^6]: 《Effective Morder C++》Item33