【C++20】(四)Lambda 加强:模板Lambda、this捕获与嵌套

前言:Lambda 的进化之路

C++11 引入了 Lambda,让我们可以就地定义匿名函数。但用过的人都知道,老版 Lambda 有很多局限:

  • 不能模板化:同一个 Lambda 无法同时处理 intdouble
  • 捕获方式有限[=] 捕获值,[&] 捕获引用,但无法捕获 *this
  • 不能在 Lambda 内部声明模板参数

C++20 给 Lambda 带来了革命性的升级,本文逐一解析。


一、模板 Lambda:泛型的威力

1.1 旧版 Lambda 的困境

1
2
3
4
5
6
// C++11/14/17:Lambda 不能有模板参数
auto square = [](auto x) { return x * x; };

square(3); // ✅ int
square(3.14); // ✅ double
// 但如果我想限制 x 必须是整数?做不到!

1.2 C++20 模板 Lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
// 模板 Lambda:尖括号中声明类型参数
auto make_pair = []<typename T, typename U>(T a, U b) {
return std::pair{a, b};
};

auto p = make_pair(1, 3.14);
std::cout << p.first << ", " << p.second << "\n";

// initializer_list lambda
auto vec = []<typename T>(std::initializer_list<T> init) {
return std::vector<T>(init);
};

auto v = vec({1, 2, 3, 4, 5});
for (auto n : v) std::cout << n << " ";
std::cout << "\n";
}

输出

1
2
1, 3.14
1 2 3 4 5

1.3 模板 Lambda 的语法细节

1
2
3
4
5
6
7
8
9
// ✅ C++20 语法
auto f = []<typename T>(T x) { };

// ❌ C++14/17 语法(不支持)
auto f = []<typename T>(T x) { };

// ⚠️ 如果省略 template<>,则整个 lambda 是"泛型 lambda"
// 但泛型 lambda 不能显式约束类型
auto f2 = [](auto x) { }; // C++14 就支持

二、this 捕获的进化

2.1 旧版 Lambda 的 this 捕获问题

1
2
3
4
5
6
7
8
9
10
11
12
class Widget {
int data = 42;

void process() {
// C++14:[=] 捕获 this 指针
// 问题:如果 Widget 被析构,Lambda 还在执行,会出问题
auto f = [=]() {
std::cout << data << "\n"; // 实际上是通过 this->data 访问
};
f();
}
};

2.2 C++20 的 [*this] 捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

class Widget {
public:
int data = 42;

void process() {
// [=, *this]:值捕获整个对象
auto f = [=, *this]() {
std::cout << data << "\n"; // copy of data
};
f();
}

void process_ref() {
// [this]:捕获指针(危险)
// 如果 Widget 被析构,Lambda 还在执行 -> 未定义行为
auto f = [this]() {
// std::cout << data << "\n"; // 危险!
};
}
};

int main() {
Widget w;
w.process(); // 42
}

2.3 捕获方式对比表

捕获方式C++ 版本行为安全性
[=]C++11值捕获 this 指针⚠️ 指针语义
[&]C++11引用捕获 this 指针⚠️ 引用语义
[=, *this]C++20值捕获整个对象副本✅ 安全
[&, *this]C++20值捕获 *this,其他引用✅ 安全

三、constexpr Lambda 进化

3.1 C++17 的 constexpr Lambda

C++17 让 Lambda 可以是 constexpr,但能力有限:

1
2
3
// C++17
constexpr auto add = [](auto a, auto b) { return a + b; };
static_assert(add(1, 2) == 3); // ✅

3.2 C++20 的改进

C++20 进一步增强了 constexpr Lambda:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

int main() {
// C++20:Lambda 本身可以隐式 constexpr
constexpr auto add = [](auto a, auto b) { return a + b; };
static_assert(add(1, 2) == 3); // ✅

// C++20:Lambda 可以在 constexpr 上下文中使用成员函数
constexpr auto vec_size = [](auto&& v) {
return v.size(); // C++20 前 constexpr lambda 不能调用成员
};

// 注意:这需要在常量表达式中调用
(void)vec_size; // 抑制未使用警告
}

四、泛型 Lambda + Concepts

4.1 用 Concepts 约束模板 Lambda

C++20 允许在模板 Lambda 中使用 Concepts:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <concepts>

int main() {
// 约束 T 必须是整数类型
auto print_integral = []<std::integral T>(T n) {
std::cout << n << " (integral)\n";
};

print_integral(42); // ✅
// print_integral(3.14); // ❌ 编译错误:double 不满足 integral
}

4.2 组合 Concepts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <concepts>

int main() {
// 组合约束:既要能加,也要能比较
auto process_num = []<typename T>(
std::integral<T> || std::floating_point<T>
)(T a, T b) {
std::cout << "sum: " << a + b << "\n";
};

process_num(1, 2); // ✅
process_num(1.5, 2.5); // ✅
// process_num("a", "b"); // ❌
}

五、嵌套 Lambda

5.1 模板 Lambda 嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

int main() {
// 外层模板 Lambda
auto make_calculator = []<typename T>(T x) {
// 内层普通 Lambda
return [x](auto op, auto y) {
if constexpr (op == '+') return x + y;
else if constexpr (op == '-') return x - y;
else return T{};
};
};

auto calc = make_calculator(10);
std::cout << calc('+', 5) << "\n"; // 15
std::cout << calc('-', 3) << "\n"; // 7
}

5.2 高阶函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
// 返回函数的 Lambda
auto make_filter = []<typename T>(T threshold) {
return [threshold](T x) { return x > threshold; };
};

std::vector<int> nums{1, 5, 3, 8, 2, 9};
auto it = std::ranges::find_if(nums, make_filter(4));

if (it != nums.end()) {
std::cout << "Found: " << *it << "\n"; // 5
}
}

六、完整编译示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <concepts>

int main() {
// 模板 Lambda (C++20)
auto make_pair = []<typename T, typename U>(T a, U b) {
return std::pair{a, b};
};
auto p = make_pair(1, 3.14);
std::cout << p.first << ", " << p.second << "\n";

// initializer_list lambda
auto vec = []<typename T>(std::initializer_list<T> init) {
return std::vector<T>(init);
};
auto v = vec({1, 2, 3, 4, 5});

// [=, *this] 捕获
class Widget {
public:
int data = 42;
void process() {
auto f = [=, *this]() {
std::cout << data << "\n"; // copy of *this
};
f();
}
};

Widget w;
w.process();

// constexpr lambda
constexpr auto add = [](auto a, auto b) { return a + b; };
static_assert(add(1, 2) == 3);

// 泛型lambda with concept
auto print_integral = []<std::integral T>(T n) {
std::cout << n << " (integral)\n";
};
print_integral(42);
}

运行结果

1
2
3
1, 3.14
42
42 (integral)

七、Lambda 演进时间线

graph LR
    A["C++11<br/>🟢 引入 Lambda<br/>基本功能"] --> B["C++14<br/>🟢 泛型 Lambda<br/>auto 参数"]
    B --> C["C++17<br/>🟢 constexpr Lambda<br/>if constexpr"]
    C --> D["C++20<br/>🟣 模板 Lambda<br/>*this 捕获<br/>Concepts 约束"]
    
    style A fill:#B5EAD7,stroke:#80CBC4,color:#333
    style B fill:#B5EAD7,stroke:#80CBC4,color:#333
    style C fill:#B5EAD7,stroke:#80CBC4,color:#333
    style D fill:#E8D5F5,stroke:#CE93D8,color:#333

八、常见错误与避坑

8.1 模板 Lambda 不能有默认参数

1
2
3
4
5
// ❌ 错误
auto f = []<typename T = int>(T x = 0) { }; // C++20 不支持!

// ✅ 正确:默认值在调用时指定
auto f = []<typename T>(T x = T{}) { };

8.2 [*this] vs [=] 的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Connection {
std::string name = "conn";
public:
void onDisconnect() {
// ❌ 危险:[this] 捕获了指针
auto callback = [this]() {
// 如果 this 已经被析构,这里是未定义行为!
// std::cout << name << "\n";
};
}

void safeOnDisconnect() {
// ✅ 安全:[*this] 捕获了对象的副本
auto callback = [=, *this]() {
// name 是副本,永远有效
std::cout << name << "\n";
};
}
};

8.3 mutable Lambda 与模板

1
2
3
4
5
6
7
8
9
10
11
int main() {
int x = 0;

// mutable 不影响模板参数推断
auto increment = [x]<typename T>(T y) mutable {
x = 10; // 修改捕获的副本
return x + y;
};

std::cout << increment(5) << "\n"; // 15
}

九、性能 considerations

Lambda 的性能零成本抽象——在编译期会被内联,没有额外运行时开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <chrono>

int main() {
auto start = std::chrono::high_resolution_clock::now();

// 多次调用 Lambda
auto add = [](int a, int b) { return a + b; };
for (int i = 0; i < 1'000'000; ++i) {
volatile auto result = add(i, i + 1);
}

auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

std::cout << "Time: " << duration.count() << " us\n";
}

结论:Lambda 在性能上等价于手写的普通函数。


十、总结

特性C++ 版本说明
[]<typename T>(T x) {}C++20模板 Lambda
[=, *this]C++20值捕获整个对象
[&, *this]C++20混合捕获,*this 值捕获
constexpr LambdaC++17+常量表达式 Lambda
[]<Concept T>(T x) {}C++20带约束的模板 Lambda
auto f = [](auto... args) {}C++17+变参模板 Lambda

记住:C++20 的 Lambda 已经成为了一个真正的泛型函数对象,拥有了与普通函数几乎等价的能力,同时保持了匿名函数的简洁性。学会用模板 Lambda,你会写出更简洁、更安全的泛型代码。


📚 C++20 新特性 系列导航

本文是《C++20 新特性》系列第 4/7 篇。

方向章节
◀ 上一篇(三)Spaceship Operator
下一篇 ▶(五)consteval 与 constinit
📖 全部 7 篇目录(点击展开)
  1. (一)Concepts
  2. (二)requires 表达式
  3. (三)Spaceship Operator
  4. (四)Lambda 加强 ← 当前
  5. (五)consteval 与 constinit
  6. (六)Coroutine 协程
  7. (七)Ranges 库