# 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 的语言特性多数可用,但不少库(``/``/`` 等)要 GCC 14+**;**C++26 在 13.3 上基本用不了**(需 GCC 14+ 且开 `-std=c++2c`)。课堂演示时请按此选用。 --- ## 1. Concepts:给模板加约束 `C++20` ### 1.1 问题:模板错误信息「天书」 一个没有约束的模板,对所有类型都「假装能用」,直到实例化时在**最深处**失败: ```cpp template 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 #include #include #include // 概念 1:可流式输出——探查一个 operator<<,并要求返回类型是 ostream& template concept Printable = requires(std::ostream& os, const T& t) { { os << t } -> std::same_as; }; // 概念 2:可迭代——探查成员函数 begin()/end() template 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 requires Iterable && (!Printable) 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{1, 2, 3}); // → B print(std::vector>{{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;` | | **能力分派(按类型能力重载)** | 两个 `print` 重载靠 `Printable` / `Iterable` 自动二选一 | | **概念可参与逻辑运算** | `requires Iterable && (!Printable)` 用 `!` 排除已处理情况 | > **那个 `!Printable` 是关键细节**:`std::string` 同时满足 `Printable` 和 `Iterable` > (它既能 `<<` 又有 `begin/end`)。若不加 `!Printable`,两个重载都可行且互不约束 > → 调用歧义报错。这里要求重载 B 只处理「可迭代但不可流式」的类型,让 `std::string` > 干净地落进重载 A。这是实际工程里反复用到的模式。 ### 1.3 报错终于说人话了 把上面 `print(Foo{})` 的注释去掉,错误信息变成: ``` required for the satisfaction of 'Printable' [with auto:16 = Foo] note: constraints not satisfied ``` 一句话点出「`Foo` 不满足 `Printable`」,不再有模板展开噪声。 ### 1.4 挂载概念的三种写法(用最小的例子演示语法) 上例用了**简写模板语法** `void print(const Printable auto& x)`,最清爽。完整三种写法: ```cpp template concept Number = std::integral || std::floating_point; // 组合标准库概念 // ① requires 子句(前置) template requires Number T add1(T a, T b) { return a + b; } // ② requires 子句(尾随,适合约束长时) template T add2(T a, T b) requires Number { return a + b; } // ③ 简写模板语法(推荐,省掉 typename) auto add3(Number auto a, Number auto b) { return a + b; } ``` `requires(...) { 表达式序列 }` 检查这些表达式**是否合法**,还能用 `-> 概念` 附加返回类型要求, 概念本身可由 `&&` `||` `!` 组合(如上例),也可由标准库概念(`` 里的 `integral`、 `floating_point`、`same_as`、`convertible_to`、`derived_from`…)拼装。 **常见的坑**: - **能力重叠 → 歧义**:一个类型同时满足多个概念、且无一个更「强」(subsumption)时, 多个重载都可行 → 编译歧义。上面用 `!Printable` 显式排除就是解法之一。 - **约束太松仍会回到「天书」**:概念只写 `template` 不加 `requires`, 失败照样从深处爆出来——概念的价值在于**把约束前置、命名**。 - **concept 是零开销**:纯编译期谓词,不参与运行期、不产生代码。 --- ## 2. Ranges:可组合的区间管道 `C++20` ```cpp #include #include #include #include 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 #include // 最小生成器(C++20 没有标准 std::generator,需自己写 promise_type; // C++23 才有 直接可用,见附录 A) Generator 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 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`)。 - 大量标准库函数(``、`` 的部分)标记为 `constexpr`。 ```cpp constexpr int build_sum() { std::vector 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 = [](std::vector const& v) { return v.size(); }; ``` 让 lambda 像模板一样把类型当参数,**不需要再用 `auto` 模糊推导**——能在参数列表精确写出 `std::vector 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` | | `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 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 `` 日历与时区 `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 data); // 接收数组 / vector / 某段 —— 零拷贝、带长度 int arr[4]; std::vector v; process(arr); process(v); process(std::span{v}.subspan(2, 5)); ``` C++ 版的「指针+长度」:栈数组、`vector`、`array` 都能无开销绑定,函数签名终于不必在 `T*` 与 `vector&` 之间二选一。 ### 10.4 `std::jthread`:可停止的线程 `C++20` ```cpp #include 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` 库 | 头文件 | 用途 | |---|---| | `` | `std::bit_cast`(类型双关,替代 `memcpy` 把戏)、`popcount`、`countl_zero`、`rotr`、`bit_ceil` | | `` | `std::numbers::pi`、`e`、`sqrt2`…(变量模板) | | `` | `std::source_location::current()` 取当前文件/行/函数(替代 `__FILE__`/`__LINE__` 宏) | | `` | 标准库特性检测宏 | | `` | 标准概念库:`integral`/`floating_point`/`same_as`/`convertible_to`… | | `` | 范围与视图 | | `` | 协程底层设施 | --- ## 11. C++23 速览 C++23 主要是**补 C++20 的坑 + 加库**: | 类别 | 特性 | 一句话 | |---|---|---| | 错误处理 | `std::expected` | 类型化的「成功/失败」,替代异常或返回码 | | 输出 | `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 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` / 简写 / ``) | ✅ | | | Ranges(`views::`、`ranges::sort`、管道) | ✅ | | | `<=>` / `` | ✅ | | | `consteval` / `constinit` | ✅ | | | `std::format` / `` | ✅ | | | `std::span` / `` / `` / `` | ✅ | | | `std::jthread` + `stop_token` | ✅ | | | 指定初始化 / 范围 for 初始化语句 / `char8_t` / `using enum` | ✅ | | | 模板 lambda `[](...)` | ✅ | | | `` 日历字面量 | ✅ | | | **Modules** | ⚠️ | 语言机制可用,但需手动编排编译顺序与 `.gcm` 缓存,构建系统集成在 CMake 3.28+ 才较好 | | Coroutines(``) | ⚠️ | 机制可用,但 `std::generator` 要 C++23,C++20 需自写 `promise_type` | ### C++23(语言特性多数可用,库分两半) | 特性 | 结果 | 备注 | |---|---|---| | `if consteval` | ✅ | | | 多维 `operator[]`(`a[i,j]`) | ✅ | | | 静态 `operator()` | ✅ | | | `std::to_underlying` / `std::unreachable` / `std::byteswap` | ✅ | | | `` | ✅ | | | `std::optional` 单子(`and_then`/`transform`/`or_else`) | ✅ | | | `views::enumerate` / `zip` / `slide` | ✅ | | | **推导 `this`**(`this auto& self`) | ❌ | 需 GCC 14+ | | **`` / `std::println`** | ❌ | 需 GCC 14+ | | **容器 `std::format`**(`format("{}", vector)`) | ❌ | 需 GCC 14+ | | **`` / `` / ``** | ❌ | 需 GCC 14+ | | **``** | ⚠️ | 头文件存在,但需链接 `-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/` 生成产物。