1053ebf41c
- docs/SCHEDULE_2026_10DAY.md:2026-07-05 新版 10 天安排正式文本, 取代 TRAINING_PLAN_2026.md 旧执行计划作为课堂设计基准 - docs/teaching/OUTLINE_4DAY.md:4 天讲授 + Day5 综合案例的正式教案大纲, 含逐天设计、旧主题去向决策(QSS→Day3、JSON→Day4、QThread→Day5 提升)、 中初级学员误区盲点审计(10 张新坑卡:所有权/编码路径/连接语义三主线)、 项目就绪度与高阶兴趣钩子评估 - OUTLINE.md 顶部加基准变更横幅,5 天版保留作历史参照与回退预案 - 顺带收编两篇此前未跟踪的自学文档 MODERN_CPP.md / MODERN_CPP_20PLUS.md (大纲与幻灯片多处链接它们)
559 lines
25 KiB
Markdown
559 lines
25 KiB
Markdown
# C++20 与以后(C++20 / 23 / 26 概览)
|
||
|
||
> 本文是 [`MODERN_CPP.md`](MODERN_CPP.md)(C++11/14/17)的续篇。
|
||
>
|
||
> **范围与基线说明**:本仓库的编译基线仍是 **C++17**(见顶层 `CMakeLists.txt`),所以本文介绍的是 **C++20 起** 的「进阶/前瞻」特性——需要在 `-std=c++20` / `-std=c++23` 下编译,**不**是本仓库示例默认能用的版本。每节标注引入版本;附录给出本机 **GCC 13.3.0** 的实测可用性(不是猜的,每条都编过)。
|
||
>
|
||
> 为什么单列一份:C++20 是继 C++11 之后**最大**的一次语言更新,四大件(Concepts / Ranges / Coroutines / Modules)体量足够撑一份独立文档。
|
||
|
||
---
|
||
|
||
## 0. 版本脉络:C++20 是又一次「大爆炸」
|
||
|
||
| 版本 | 年份 | 定位 | 标志性内容 |
|
||
|---|---|---|---|
|
||
| C++17 | 2017 | 实用增量 | `optional`/`variant`/`string_view`/`filesystem`/结构化绑定 |
|
||
| **C++20** | 2020 | **四大件** | Concepts · Ranges · Coroutines · Modules + `<=>` · `std::format` |
|
||
| **C++23** | 2023 | 完善与补库 | `expected` · `std::print` · `mdspan` · `flat_map` · ranges 扩展 · 推导 `this` |
|
||
| C++26 | 2026(开发中) | 暂定 | `std::execution`(Senders/Receivers) · `=delete("...")` · `inplace_vector` · 契约/反射(未定) |
|
||
|
||
Stroustrup 评价 C++20:「自 C++11 以来对 C++ 最大的冲击。」
|
||
|
||
**四大件一句话**:
|
||
1. **Concepts(概念)**——给模板加约束,错误信息从「天书」变「人话」。
|
||
2. **Ranges(范围)**——可组合、惰性的区间管道,STL 算法的现代化。
|
||
3. **Coroutines(协程)**——语言级协程,生成器/异步的统一底座。
|
||
4. **Modules(模块)**——替代 `#include`,更快编译 + 更干净的封装。
|
||
|
||
> **关于工具链**:本文附录 A 给出了**本机 GCC 13.3.0 的实测矩阵**。结论先行:
|
||
> **C++20 几乎全可用**;**C++23 的语言特性多数可用,但不少库(`<print>`/`<mdspan>`/`<flat_map>` 等)要 GCC 14+**;**C++26 在 13.3 上基本用不了**(需 GCC 14+ 且开 `-std=c++2c`)。课堂演示时请按此选用。
|
||
|
||
---
|
||
|
||
## 1. Concepts:给模板加约束 `C++20`
|
||
|
||
### 1.1 问题:模板错误信息「天书」
|
||
|
||
一个没有约束的模板,对所有类型都「假装能用」,直到实例化时在**最深处**失败:
|
||
|
||
```cpp
|
||
template<typename T>
|
||
void print(const T& x) { std::cout << x; } // 要求 x 可 <<
|
||
|
||
struct Foo {};
|
||
print(Foo{}); // ❌ 报错:operator<< 找不到,外加一长串模板展开
|
||
```
|
||
|
||
编译器把 `operator<<` 失败的整条实例化链路倒给你看,真正有用的「`Foo` 根本不可流式输出」
|
||
被淹没在几十行噪声里。Concepts 的目标:**在实例化前就用一句人话告诉你类型不达标**。
|
||
|
||
### 1.2 三个能力合在一起:命名谓词 + `requires` 表达式 + 能力分派
|
||
|
||
下面是一个**能体现 Concepts 全部威力**的例子——一个「按类型能力分派」的通用 `print`:
|
||
`int`/`std::string` 直接打印,容器(如 `vector`)递归逐元素打印,二者靠**类型能力**自动选对。
|
||
|
||
```cpp
|
||
#include <concepts>
|
||
#include <iostream>
|
||
#include <vector>
|
||
#include <string>
|
||
|
||
// 概念 1:可流式输出——探查一个 operator<<,并要求返回类型是 ostream&
|
||
template<typename T>
|
||
concept Printable = requires(std::ostream& os, const T& t) {
|
||
{ os << t } -> std::same_as<std::ostream&>;
|
||
};
|
||
|
||
// 概念 2:可迭代——探查成员函数 begin()/end()
|
||
template<typename T>
|
||
concept Iterable = requires(const T& t) {
|
||
t.begin();
|
||
t.end();
|
||
};
|
||
|
||
// 重载 A:可流式的直接打印(int / std::string / const char* ……)
|
||
void print(const Printable auto& x) {
|
||
std::cout << x;
|
||
}
|
||
|
||
// 重载 B:可迭代、但本身不能直接 << 的容器 → 递归逐元素打印
|
||
template<typename T>
|
||
requires Iterable<T> && (!Printable<T>)
|
||
void print(const T& c) {
|
||
std::cout << "[ ";
|
||
for (const auto& e : c) print(e); // 递归:容器套容器也能打
|
||
std::cout << "] ";
|
||
}
|
||
|
||
int main() {
|
||
print(42); // → A
|
||
print(std::string("s")); // → A(string 能 <<,不进 B)
|
||
print(std::vector<int>{1, 2, 3}); // → B
|
||
print(std::vector<std::vector<int>>{{1,2},{3,4}}); // → B 递归到 B 再到 A
|
||
// print(Foo{}); // ❌ 编译错误:清晰,见下
|
||
}
|
||
```
|
||
|
||
运行输出:
|
||
```
|
||
42
|
||
s
|
||
[ 1 2 3 ]
|
||
[ [ 1 2 ] [ 3 4 ] ]
|
||
```
|
||
|
||
这个小例子同时展示了 Concepts 的**四个关键能力**(这正是 `add(Number)` 那种玩具例子看不到的):
|
||
|
||
| 能力 | 体现在 |
|
||
|---|---|
|
||
| **`requires` 表达式探查成员** | `Iterable` 用 `t.begin(); t.end();` 检查成员函数是否存在 |
|
||
| **探查运算符 + 返回类型要求** | `Printable` 用 `{ os << t } -> std::same_as<std::ostream&>;` |
|
||
| **能力分派(按类型能力重载)** | 两个 `print` 重载靠 `Printable` / `Iterable` 自动二选一 |
|
||
| **概念可参与逻辑运算** | `requires Iterable<T> && (!Printable<T>)` 用 `!` 排除已处理情况 |
|
||
|
||
> **那个 `!Printable<T>` 是关键细节**:`std::string` 同时满足 `Printable` 和 `Iterable`
|
||
> (它既能 `<<` 又有 `begin/end`)。若不加 `!Printable<T>`,两个重载都可行且互不约束
|
||
> → 调用歧义报错。这里要求重载 B 只处理「可迭代但不可流式」的类型,让 `std::string`
|
||
> 干净地落进重载 A。这是实际工程里反复用到的模式。
|
||
|
||
### 1.3 报错终于说人话了
|
||
|
||
把上面 `print(Foo{})` 的注释去掉,错误信息变成:
|
||
|
||
```
|
||
required for the satisfaction of 'Printable<auto:16>' [with auto:16 = Foo]
|
||
note: constraints not satisfied
|
||
```
|
||
|
||
一句话点出「`Foo` 不满足 `Printable`」,不再有模板展开噪声。
|
||
|
||
### 1.4 挂载概念的三种写法(用最小的例子演示语法)
|
||
|
||
上例用了**简写模板语法** `void print(const Printable auto& x)`,最清爽。完整三种写法:
|
||
|
||
```cpp
|
||
template<typename T>
|
||
concept Number = std::integral<T> || std::floating_point<T>; // 组合标准库概念
|
||
|
||
// ① requires 子句(前置)
|
||
template<typename T> requires Number<T>
|
||
T add1(T a, T b) { return a + b; }
|
||
|
||
// ② requires 子句(尾随,适合约束长时)
|
||
template<typename T>
|
||
T add2(T a, T b) requires Number<T> { return a + b; }
|
||
|
||
// ③ 简写模板语法(推荐,省掉 typename)
|
||
auto add3(Number auto a, Number auto b) { return a + b; }
|
||
```
|
||
|
||
`requires(...) { 表达式序列 }` 检查这些表达式**是否合法**,还能用 `-> 概念` 附加返回类型要求,
|
||
概念本身可由 `&&` `||` `!` 组合(如上例),也可由标准库概念(`<concepts>` 里的 `integral`、
|
||
`floating_point`、`same_as`、`convertible_to`、`derived_from`…)拼装。
|
||
|
||
**常见的坑**:
|
||
- **能力重叠 → 歧义**:一个类型同时满足多个概念、且无一个更「强」(subsumption)时,
|
||
多个重载都可行 → 编译歧义。上面用 `!Printable<T>` 显式排除就是解法之一。
|
||
- **约束太松仍会回到「天书」**:概念只写 `template<typename T>` 不加 `requires`,
|
||
失败照样从深处爆出来——概念的价值在于**把约束前置、命名**。
|
||
- **concept 是零开销**:纯编译期谓词,不参与运行期、不产生代码。
|
||
|
||
---
|
||
|
||
## 2. Ranges:可组合的区间管道 `C++20`
|
||
|
||
```cpp
|
||
#include <ranges>
|
||
#include <vector>
|
||
#include <algorithm>
|
||
#include <iostream>
|
||
|
||
int main() {
|
||
namespace rv = std::views;
|
||
|
||
// 管道:惰性求值,不产生中间容器
|
||
auto result = std::views::iota(1) // 1, 2, 3, ...
|
||
| rv::filter([](int x){ return x % 2 == 0; }) // 偶数
|
||
| rv::transform([](int x){ return x * x; }) // 平方
|
||
| rv::take(5); // 取前 5 个 → 4,16,36,64,100
|
||
|
||
for (int x : result) std::cout << x << " ";
|
||
}
|
||
```
|
||
|
||
**核心概念**:
|
||
- **视图 (view)**:轻量、**惰性**的区间适配器(如 `filter`/`transform`/`take`/`drop`)。不拷贝数据,遍历时才计算,可 O(1) 拷贝。
|
||
- **管道 `|`**:像 Unix 管道一样把多个适配器串起来,`a | f | g`。
|
||
- **ranges 算法**:`std::ranges::sort(v)` / `std::ranges::find(v, x)` 直接吃容器,不用 `v.begin()/v.end()`;还支持投影 `sort(people, {}, &Person::age)`。
|
||
- **C++23 扩展**:`views::enumerate`(带下标遍历)、`views::zip`(并行拉链)、`views::slide`/`chunk`/`stride`/`adjacent` 等(见附录 A 可用性)。
|
||
|
||
**常见的坑**:
|
||
- 视图是**惰性**的:`filter` 里的谓词在遍历时才执行,定义时不跑。
|
||
- 视图**不拥有**底层容器:容器销毁后视图悬垂(类似 `string_view`)。
|
||
- `iota(1)`(无上界)是无限区间,必须配合 `take` 之类才有穷,否则 `for` 死循环。
|
||
|
||
---
|
||
|
||
## 3. Coroutines:语言级协程 `C++20`
|
||
|
||
协程是能**暂停、再恢复**的函数,用三个关键字:`co_await`(等待异步操作)、`co_yield`(产出一个值)、`co_return`(结束)。
|
||
|
||
```cpp
|
||
#include <coroutine>
|
||
#include <iostream>
|
||
|
||
// 最小生成器(C++20 没有标准 std::generator,需自己写 promise_type;
|
||
// C++23 才有 <generator> 直接可用,见附录 A)
|
||
Generator<int> count_up(int from) {
|
||
while (true)
|
||
co_yield from++; // 每次调用 next() 唤醒,产出一个数
|
||
}
|
||
|
||
int main() {
|
||
auto g = count_up(10);
|
||
for (int i = 0; i < 3; ++i)
|
||
std::cout << g() << " "; // 10 11 12
|
||
}
|
||
```
|
||
|
||
**核心概念**:
|
||
- 函数体里出现 `co_await`/`co_yield`/`co_return` 之一,即为协程。
|
||
- 协程**返回一个「协程句柄对象」**(其类型必须含 `promise_type`),调用者通过它控制暂停/恢复。
|
||
- 框架只规定**机制**(如何挂起/恢复、如何把值传出去),**策略**(调度、I/O)由库或你自己的 `promise_type` 实现。所以**裸 C++20 协程偏底层**,实际多用库(CppCoro/ASIO/C++23 的 `std::generator`)。
|
||
|
||
**常见的坑**:
|
||
- 协程帧在堆上分配,生命周期与返回的句柄绑定——句柄销毁前协程不能跑完 → 泄漏/悬垂。
|
||
- 协程本身的「调度」要你自己接,标准库不提供线程池或事件循环(C++26 的 `std::execution` 在补这块,暂定)。
|
||
|
||
> Qt 侧:Qt 也有协程支持(Qt Coroutine / 协程化的信号槽),但课程内异步更常用 `QThread` + 信号槽,协程属进阶内容。
|
||
|
||
---
|
||
|
||
## 4. Modules:替代 `#include` `C++20`
|
||
|
||
```cpp
|
||
// ---- greet.cppm(模块接口单元)----
|
||
export module greet;
|
||
export int hello() { return 42; }
|
||
|
||
// ---- main.cpp----
|
||
import greet;
|
||
int main() { return hello(); }
|
||
```
|
||
|
||
**核心概念**:模块是**语义导入**——编译一次后缓存为二进制接口(`.gcm` 等),下游导入不重复解析,也不受宏污染。相比 `#include` 的「文本粘贴」,模块带来:
|
||
- **更快编译**(无重复解析、无宏展开风暴、头文件顺序无关)。
|
||
- **更好封装**(`export` 才对外可见,内部实现不泄漏符号/宏)。
|
||
|
||
**重要的现实情况**(务必在课堂上讲清):
|
||
- 模块的**语言机制**已在 C++20 定稿,但**构建系统集成(CMake/Make/Ninja)与各编译器(GCC/Clang/MSVC)的支持在 C++20 周期是「能用但繁琐」**:GCC 要 `-fmodules-ts` 并安排模块的预编译顺序、生成 `.gcm` 缓存;CMake 到 3.28+ 才较好地支持。
|
||
- `import std;`(直接导入整个标准库)要**更晚**的工具链(GCC 14+ / Clang 17+ / MSVC 19.36+)才好用。
|
||
- 本机 GCC 13.3 可以编模块,但需要手动编排编译顺序(详见附录 A 的实测说明)。
|
||
- **结论**:课堂以「讲机制 + 看语法」为主;要做成可运行示例,需投入构建脚本,超出本仓库「最小示例」范畴。
|
||
|
||
---
|
||
|
||
## 5. 三向比较 `<=>` `C++20`
|
||
|
||
```cpp
|
||
#include <compare>
|
||
struct Point {
|
||
int x, y;
|
||
auto operator<=>(const Point&) const = default; // 一行生成 <,<=,==,!=,>=,>
|
||
};
|
||
```
|
||
|
||
**核心概念**:`<=>`("宇宙飞船运算符")做**三向比较**,返回一个**比较类别**:
|
||
- `std::strong_ordering`(`less`/`equal`/`greater`,全序,如 `int`)
|
||
- `std::weak_ordering`(相等但不等价,如大小写不敏感的字符串)
|
||
- `std::partial_ordering`(可能无序,如浮点 `NaN`)
|
||
|
||
`= default` 让编译器按成员逐一比较,自动派生全部 6 个关系运算符。也可以**只 `=default` `==`**,编译器会反过来补 `!=`。
|
||
|
||
```cpp
|
||
struct V {
|
||
int x;
|
||
bool operator==(const V&) const = default; // 自动获得 !=
|
||
};
|
||
```
|
||
|
||
**常见的坑**:
|
||
- `double` 之间是 `partial_ordering`(因为有 `NaN`),`a <=> b` 的结果可能是 `unordered`。
|
||
- `operator<=>` 默认生成 `==`/`!=`;但若类含指针或不希望全序,需自己定义。
|
||
|
||
---
|
||
|
||
## 6. `consteval` 与 `constinit` `C++20`
|
||
|
||
```cpp
|
||
consteval int sq(int x) { return x * x; } // 立即函数:必须在编译期求值
|
||
constinit int g = sq(5); // 常量初始化(值可在运行期,但初始化在编译期)
|
||
|
||
int n;
|
||
std::cin >> n;
|
||
int a = sq(5); // OK,编译期
|
||
// int b = sq(n); // ❌ n 不是常量,consteval 强制编译期 → 报错
|
||
```
|
||
|
||
**区别**(三者别混):
|
||
|
||
| | `constexpr` | `consteval` | `constinit` |
|
||
|---|---|---|---|
|
||
| 修饰对象 | 变量 / 函数 | 仅函数 | 仅变量(静态/线程局部) |
|
||
| 编译期求值 | **可能**(能在编译期就在,不能就运行期) | **必须**(一定是编译期,否则编译错误) | 初始化**必须**是常量表达式 |
|
||
| 是否 `const` | 是 | (函数) | **不是**(值之后可改,只是初始化在编译期) |
|
||
|
||
**`constinit` 解决什么**:防止**静态初始化顺序灾难**(跨翻译单元的全局对象初始化顺序未定义)。要求初始化是常量表达式,就把对象放进「零开销」的常量初始化阶段,绕开顺序问题。
|
||
|
||
---
|
||
|
||
## 7. 更多 `constexpr`:编译期能做更多 `C++20`
|
||
|
||
C++20 把 `constexpr` 的边界大幅推开:
|
||
|
||
- **`constexpr` 动态内存分配**:`constexpr std::vector`、`constexpr std::string`(在常量表达式求值期间分配的内存需在求值结束前释放)。
|
||
- **`constexpr` 函数里可用 `try/catch`、`std::is_constant_evaluated()`**(C++20,C++23 升级为 `if consteval`)。
|
||
- 大量标准库函数(`<cmath>`、`<algorithm>` 的部分)标记为 `constexpr`。
|
||
|
||
```cpp
|
||
constexpr int build_sum() {
|
||
std::vector<int> v{1, 2, 3, 4}; // C++20:constexpr vector
|
||
int s = 0;
|
||
for (int x : v) s += x;
|
||
return s; // 析构时释放,编译期合规
|
||
}
|
||
static_assert(build_sum() == 10);
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Lambda 与函数对象增强
|
||
|
||
### 8.1 模板 lambda `C++20`
|
||
|
||
```cpp
|
||
auto f = []<typename T>(std::vector<T> const& v) {
|
||
return v.size();
|
||
};
|
||
```
|
||
|
||
让 lambda 像模板一样把类型当参数,**不需要再用 `auto` 模糊推导**——能在参数列表精确写出 `std::vector<T> const&`。
|
||
|
||
### 8.2 推导 `this`(显式对象参数)`C++23`
|
||
|
||
```cpp
|
||
struct Builder {
|
||
// self 是显式的「this 参数」,类型可推导 → lambda 能递归、能写 CRTP 式重载
|
||
void log(this auto& self, std::string_view msg) { /* ... */ }
|
||
};
|
||
|
||
// 递归 lambda(以前只能用 std::function 或 Y 组合子):
|
||
auto fib = [](this auto& self, int n) -> int {
|
||
return n < 2 ? n : self(n - 1) + self(n - 2);
|
||
};
|
||
```
|
||
|
||
**核心概念**:把 `this` 写成**显式参数**(`this auto& self`),类型可推导。一举多得:递归 lambda、const/非 const 重载统一、消除 CRTP 样板。
|
||
|
||
> 本机 GCC 13.3 **不支持**推导 `this`(附录 A),演示需 GCC 14+。
|
||
|
||
### 8.3 多维 `operator[]` 与静态 `operator()` `C++23`
|
||
|
||
```cpp
|
||
struct Matrix {
|
||
double operator[](int i, int j) { return data[i * cols + j]; } // 多维下标
|
||
int data[100], cols;
|
||
};
|
||
struct Less { static bool operator()(int a, int b) { return a < b; } }; // 静态调用
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 杂项但常用的语言特性 `C++20/23`
|
||
|
||
| 特性 | 版本 | 示例 |
|
||
|---|---|---|
|
||
| 指定初始化 | `C++20` | `Point p{.x = 1, .y = 2};`(来自 C,受限形式:须按声明顺序、不嵌套) |
|
||
| 范围 for 的初始化语句 | `C++20` | `for (auto& c = get(); auto& e : c) { ... }` |
|
||
| `char8_t` | `C++20` | `u8""` 的类型从 `char` 变成独立的 `char8_t`(**ABI 破坏**,老代码要改) |
|
||
| `using enum` | `C++20` | `using enum Color;` 把枚举值引入作用域 |
|
||
| 聚合体 CTAD | `C++17/20` | `std::array a{1,2,3};` 推导 `array<int,3>` |
|
||
| `if consteval` | `C++23` | 替代 `std::is_constant_evaluated()`,更清晰 |
|
||
| `=delete("原因")` | `C++26` | 删函数时带诊断信息(本机 `-std=c++2b` 已可编译) |
|
||
| 位域成员默认初始化 | `C++20` | `struct { unsigned a : 3 = 0; };` |
|
||
|
||
---
|
||
|
||
## 10. 标准库大件 `C++20`
|
||
|
||
### 10.1 `std::format`:Python 风格格式化 `C++20`
|
||
|
||
```cpp
|
||
#include <format>
|
||
std::string s = std::format("x = {}, pi ≈ {:.3f}", 42, 3.14159);
|
||
```
|
||
|
||
替代 `printf`(不类型安全)和 `iostream`(冗长、慢)的中间路线:`{}` 占位 + 安全的类型检查。
|
||
> **C++23** 的 `std::print`/`std::println` 让你直接 `std::println("x = {}", 42)`,连 `std::format`+`<<` 都省了(本机要 GCC 14+)。
|
||
|
||
### 10.2 `<chrono>` 日历与时区 `C++20`
|
||
|
||
```cpp
|
||
using namespace std::chrono;
|
||
auto year = 2025y;
|
||
auto date = 2025y/January/1d; // 年/月/日:日历字段(字面量 y / Month / d)
|
||
auto next_week = sys_days{date} + weeks{1}; // 日期 + 时长
|
||
auto dur = days{7} + 12h + 30min; // 时长:180 小时(注意 days 是复数 = duration)
|
||
```
|
||
|
||
把日历(年月日、星期)、时长、时区都纳入类型系统,编译器帮你抓「天数+小时数」这类单位错误。
|
||
|
||
**坑(极易踩)**:`7d`(单数字面量)是 `chrono::day` **日历字段**(一个月里的「几号」),
|
||
**不是**「7 天的时长」。7 天的时长要写 `days{7}`(复数类型 = duration)或 `7*24h`。
|
||
所以 `7d + 12h` **不能编译**——字段不能和时长相加;只有 `sys_days{date} + weeks{1}`
|
||
这种「日期点 + 时长」才行。
|
||
|
||
### 10.3 `std::span`:连续内存的非拥有视图 `C++20`
|
||
|
||
```cpp
|
||
void process(std::span<int> data); // 接收数组 / vector / 某段 —— 零拷贝、带长度
|
||
int arr[4]; std::vector<int> v;
|
||
process(arr); process(v); process(std::span{v}.subspan(2, 5));
|
||
```
|
||
|
||
C++ 版的「指针+长度」:栈数组、`vector`、`array` 都能无开销绑定,函数签名终于不必在 `T*` 与 `vector<T>&` 之间二选一。
|
||
|
||
### 10.4 `std::jthread`:可停止的线程 `C++20`
|
||
|
||
```cpp
|
||
#include <thread>
|
||
std::jthread worker([](std::stop_token st){
|
||
while (!st.stop_requested()) { /* ...干活... */ }
|
||
});
|
||
// worker 析构时自动 join,且可 worker.request_stop() 协作式取消
|
||
```
|
||
|
||
比 `std::thread` 多两点:**析构自动 `join`**(不会 `std::terminate`)、内置**协作式取消**(`stop_token`)。
|
||
|
||
### 10.5 其它 `C++20` 库
|
||
|
||
| 头文件 | 用途 |
|
||
|---|---|
|
||
| `<bit>` | `std::bit_cast`(类型双关,替代 `memcpy` 把戏)、`popcount`、`countl_zero`、`rotr`、`bit_ceil` |
|
||
| `<numbers>` | `std::numbers::pi`、`e`、`sqrt2`…(变量模板) |
|
||
| `<source_location>` | `std::source_location::current()` 取当前文件/行/函数(替代 `__FILE__`/`__LINE__` 宏) |
|
||
| `<version>` | 标准库特性检测宏 |
|
||
| `<concepts>` | 标准概念库:`integral`/`floating_point`/`same_as`/`convertible_to`… |
|
||
| `<ranges>` | 范围与视图 |
|
||
| `<coroutine>` | 协程底层设施 |
|
||
|
||
---
|
||
|
||
## 11. C++23 速览
|
||
|
||
C++23 主要是**补 C++20 的坑 + 加库**:
|
||
|
||
| 类别 | 特性 | 一句话 |
|
||
|---|---|---|
|
||
| 错误处理 | `std::expected<T,E>` | 类型化的「成功/失败」,替代异常或返回码 |
|
||
| 输出 | `std::print` / `std::println` | `std::format` 直接打印到流 |
|
||
| 多维 | `std::mdspan` | 多维数组的非拥有视图(GPU/HPC 常用) |
|
||
| 容器 | `std::flat_map` / `flat_set` | 有序但连续存储的「扁平」关联容器(缓存友好) |
|
||
| 调试 | `std::stacktrace` | 运行期取调用栈 |
|
||
| optional 单子 | `and_then` / `or_else` / `transform` | 链式处理「可能无值」 |
|
||
| 工具 | `std::to_underlying` / `std::unreachable` / `std::byteswap` | 枚举转整数 / 标记不可达 / 字节反转 |
|
||
| 范围 | `views::enumerate` / `zip` / `slide` / `chunk` / `adjacent` / `stride` | 区间管道再扩 |
|
||
| 语言 | 推导 `this` / `if consteval` / 多维 `[]` / 静态 `operator()` | 见 §8 |
|
||
|
||
```cpp
|
||
// expected:替代异常的「带类型的错误」
|
||
std::expected<int, std::string> parse(std::string_view s) {
|
||
if (s.empty()) return std::unexpected("empty");
|
||
return 42;
|
||
}
|
||
if (auto r = parse("x")) use(*r); else handle(r.error());
|
||
|
||
// views::enumerate:带下标遍历,告别手动 i++
|
||
for (auto [i, v] : std::views::enumerate(items)) { /* ... */ }
|
||
```
|
||
|
||
---
|
||
|
||
## 12. C++26 展望(暂定,未完全定稿)
|
||
|
||
> **状态声明**:C++26 标准还在制定中,以下为「候选/已合并」特性,**本机 GCC 13.3 基本都还用不了**(需 GCC 14+,多数需 `-std=c++2c`,本机连该标志都不识别)。列在这里是为了让学员建立「C++ 还在持续演进」的认知。
|
||
|
||
- **`std::execution`(Senders/Receivers,P2300)**:标准化的异步/并行执行框架,C++ 终于有统一的「调度 + 组合异步操作」底座。
|
||
- **`= delete("原因")`**:删函数带诊断信息(本机 `-std=c++2b` 已能编译,是少数现在能试的)。
|
||
- **`std::inplace_vector`**:栈上、容量固定的 `vector`(不动态分配,适合嵌入式/实时)。
|
||
- **`std::linalg`**:基于 `mdspan` 的线性代数(BLAS 风格)。
|
||
- **`#embed`**:把二进制文件直接嵌入源码,替代 `xxd`/工具链把戏。
|
||
- **打包下标 `a[1, 2, 3]`**:一次取多个元素。
|
||
- **契约(Contracts)/ 静态反射(Reflection)**:呼声很高,但能否进 C++26 仍有变数(多次反复)。
|
||
|
||
---
|
||
|
||
## 附 A:本机 GCC 13.3.0 实测可用性矩阵
|
||
|
||
> 全部为 `-std=c++20` / `-std=c++23` / `-std=c++2b` 下的**实测**结果(`-Wall -pedantic-errors`)。✅ 可用 / ❌ 缺失或需更高版本。
|
||
|
||
### C++20(基本全可用)
|
||
|
||
| 特性 | 结果 | 备注 |
|
||
|---|---|---|
|
||
| Concepts(`requires` / 简写 / `<concepts>`) | ✅ | |
|
||
| Ranges(`views::`、`ranges::sort`、管道) | ✅ | |
|
||
| `<=>` / `<compare>` | ✅ | |
|
||
| `consteval` / `constinit` | ✅ | |
|
||
| `std::format` / `<format>` | ✅ | |
|
||
| `std::span` / `<bit>` / `<numbers>` / `<source_location>` | ✅ | |
|
||
| `std::jthread` + `stop_token` | ✅ | |
|
||
| 指定初始化 / 范围 for 初始化语句 / `char8_t` / `using enum` | ✅ | |
|
||
| 模板 lambda `[]<typename T>(...)` | ✅ | |
|
||
| `<chrono>` 日历字面量 | ✅ | |
|
||
| **Modules** | ⚠️ | 语言机制可用,但需手动编排编译顺序与 `.gcm` 缓存,构建系统集成在 CMake 3.28+ 才较好 |
|
||
| Coroutines(`<coroutine>`) | ⚠️ | 机制可用,但 `std::generator` 要 C++23,C++20 需自写 `promise_type` |
|
||
|
||
### C++23(语言特性多数可用,库分两半)
|
||
|
||
| 特性 | 结果 | 备注 |
|
||
|---|---|---|
|
||
| `if consteval` | ✅ | |
|
||
| 多维 `operator[]`(`a[i,j]`) | ✅ | |
|
||
| 静态 `operator()` | ✅ | |
|
||
| `std::to_underlying` / `std::unreachable` / `std::byteswap` | ✅ | |
|
||
| `<expected>` | ✅ | |
|
||
| `std::optional` 单子(`and_then`/`transform`/`or_else`) | ✅ | |
|
||
| `views::enumerate` / `zip` / `slide` | ✅ | |
|
||
| **推导 `this`**(`this auto& self`) | ❌ | 需 GCC 14+ |
|
||
| **`<print>` / `std::println`** | ❌ | 需 GCC 14+ |
|
||
| **容器 `std::format`**(`format("{}", vector)`) | ❌ | 需 GCC 14+ |
|
||
| **`<mdspan>` / `<flat_map>` / `<inplace_vector>`** | ❌ | 需 GCC 14+ |
|
||
| **`<stacktrace>`** | ⚠️ | 头文件存在,但需链接 `-lstdc++_exp`,本机**无该库** → 实际不可用 |
|
||
| **`std::generator`** | ❌ | 需 GCC 14+ |
|
||
|
||
### C++26(本机基本不可用)
|
||
|
||
| 特性 | 结果 | 备注 |
|
||
|---|---|---|
|
||
| `-std=c++2c` / `-std=c++26` 标志 | ❌ | 本机 GCC 13.3 不识别该标志 |
|
||
| `= delete("原因")` | ✅ | `-std=c++2b` 下可编译(少数能试的) |
|
||
| `std::execution` / `inplace_vector` / `#embed` / 契约 / 反射 | ❌ | 全部需 GCC 14+,多数尚未实现 |
|
||
|
||
---
|
||
|
||
## 附 B:学习路径建议
|
||
|
||
1. **先吃透 C++17**(见 [`MODERN_CPP.md`](MODERN_CPP.md)):移动、智能指针、lambda、`optional`/`variant` 是 C++20 的前提。
|
||
2. **C++20 入门顺序**:Concepts → Ranges → `<=>` → `std::format`/`span`/`jthread`;Modules 和 Coroutines 放最后(构建/调度复杂度高)。
|
||
3. **C++23 实用件**(你的工具链够新时):`expected`、`std::print`、`views::enumerate`——日常工程立刻能受益。
|
||
4. **C++26** 持续关注,不必急着用——标准与实现都还在收敛。
|
||
5. **权威参考**:[cppreference.com](https://en.cppreference.com/w/)、[C++ 标准委员会邮件列表](https://www.open-std.org/jtc1/sc22/wg21/)、Sutter《Welcome to the Jungle / 历年 trip report》。
|
||
|
||
## 附 C:与本仓库的关联
|
||
|
||
- **本仓库基线 C++17**:本文件所有特性都**超出基线**,需显式开 `-std=c++20`+;仓库默认构建(`cmake -DCMAKE_BUILD_TYPE=Debug`)不会触及。
|
||
- **如需在课堂上跑 C++20 演示**:可在 `tools/demo_versions/` 加一个 `-std=c++20` 的目标(需在生成器/CMake 里单独加 `COMPILE_OPTIONS`),与现有 `std_probe.py` 跨版本对比机制衔接。**这属于新增工程改动,需要你明确指令我才执行。**
|
||
- **跨版本探针**:`tools/std_probe.py` 已支持把片段喂给任意 `-std=`,可用它复现本文附录 A 的矩阵。
|
||
- **Qt 侧**:Qt 6 对 C++17 是基线、对 C++20 部分采用;本仓库用的隔离 Qt 5.14.2 以 C++17 为准,C++20 特性暂不混入 `p03/` 生成产物。
|