第20.6章:今天仍然缺什么
1. 头文件清单,以及每一项是哪一年进来的
独立实现必须至少提供标准清单上的头文件。下面是对普通代码要紧的那些,以及把它们加进来的标准,取自 cppreference 的表:
| 头文件 | 状态 | 自 |
|---|---|---|
<cstddef> | All | C++98 |
<cfloat>, <climits>, <limits> | All | C++98 / C++11 |
<cstdarg>, <cstdint> | All | C++98 / C++11 |
<new>, <typeinfo>, <exception> | All | C++98 |
<initializer_list>, <type_traits>, <atomic> | All | C++11 |
<concepts>, <coroutine>, <bit>, <source_location>, <version> | All | C++20 |
<utility>, <tuple>, <ratio> | All | C++23 |
<memory>, <iterator>, <ranges>, <functional> | Partial | C++23 |
<cstdlib> | Partial | C++98 |
<array>, <cstring>, <string>, <cmath>, <system_error>, <charconv>, <algorithm> | Partial | C++26 |
两件事很扎眼。第一,<utility> —— std::move 住的地方 —— 直到 C++23 才成为独立头文件,比这门语言发布晚了二十五年。第二,看最后一行,然后注意那正好是你最需要的一行。
2. C++26 的窟窿
那最后几项是真正的标准工作,已经通过并投票采纳 —— 为 C++26 采纳的。它们由 __cpp_lib_freestanding_array = 202311L 之类的特性测试宏守护,而它们背后是一部没有发布的标准。所以 std::array 今天的诚实状态是:
-
在每一部已发布的标准里:
<array>都不是独立头文件。独立实现没有义务提供它,它可以缺失、可以坏掉,也可以作为一个不可移植的额外赠送出现。 -
在你的工具链里:它很可能能用,因为你的厂商提供了比标准要求更多的东西。GCC 的 libstdc++ 为独立环境提供了
<array>、<optional>、<span>和<bitset>,而 cppreference 自己的注记仍然建议避免使用它们,因为它们"可能不可移植,或者不提供与宿主实现相同的能力"。 - 在下一个工具链上:未知。厂商扩展是善意,不是契约,它可以在任何一个版本里被收回 —— 或者对你关心的目标根本从来没提供过,而这在换新板子时是常态。
一个三元素的定长数组是语言里要求最低的容器:不需要堆、接口不要求异常、不分配、不涉及 locale、不涉及 I/O。它从 C++11 起就在标准里,而它在任何已发布的修订版里仍然不是独立头文件。一个类型就概括了 C++ 独立实现的全部故事。
3. 宿主工具链加上 -ffreestanding 究竟给你什么
在 GCC 17 的 libstdc++ 上实测,它属于比较好的那几个。每一行都是一个单行文件,用 -std=c++26 -ffreestanding -fsyntax-only 编译:
OK <cstddef>
OK <utility>
OK <array>
FAIL <vector> error: #error "This header is not available in freestanding mode."
FAIL <string> error: #error "This header is not available in freestanding mode."
OK <string_view>
OK <optional>
OK <expected>
OK <span>
FAIL <cmath> error: #error "This header is not available in freestanding mode."
FAIL <iostream> error: #error "This header is not available in freestanding mode."
FAIL <print> error: #error "This header is not available in freestanding mode."
读中间那一列:<string_view>、<optional>、<expected> 和 <span> 能用,而 <cmath> —— 一个 C 从 1989 年就要求在独立环境里提供的头文件的 C++ 包装 —— 不能用。那不是经过考虑的移植性策略;那是三十年来头文件为先有宿主程序而写、事后再加闸门累积出来的结果。
还要记住这份清单的来源:一套宿主安装,翻了一个选项(20.1)。在一套真正的裸机工具链上,每一行都会是 fatal error: ... file not found(20.5)。
4. 三个层次,以及没人争的那个层次
| 层次 | 含义 | 对你的构建意味着什么 |
|---|---|---|
| 必需 | 标准说独立实现拥有它 | 你可以依赖它,在任何符合标准的工具链上,永远 |
| 厂商扩展 | 你的 libstdc++/libc++ 反正提供了 | 今天能用,在下一个工具链或下一块板子上会悄悄坏掉 |
| 永不 |
vector、list、deque、map:如 cppreference 所说,"由于它们对异常和堆的依赖"
|
再怎么标准化也改变不了 —— 那个依赖就是设计本身 |
最后一行才是有意思的那一行,因为它不是排期问题。标准库的容器通过抛出异常来报告分配失败,并且通过从全局堆分配来扩容。这些不是偶然属性;它们就是接口。于是标准库里真正对实际程序有用的那部分,正是标准已经决定根本不能独立的那部分。
结论不是"等 C++29"。而是:独立程序必须自己拥有它的头文件 —— 决定信任哪些、在哪些工具链上、用什么退路 —— 或者用一个已经替你做完了这个决定、并且能为目标编译的库。这就是 20.13 的论证。
5. 工具链厂商自己怎么说
三大实现,三个答案。MSVC STL 支持独立实现,而且已经支持了很多年。libstdc++ 在 13 版之前有"实现与构建上的问题" —— cppreference 的原话 —— 从 GCC 13 起可用,但是部分可用。而 libc++ 完全不支持独立实现:Clang 这边没有一个可目标化的独立配置,这就是为什么裸机 C++ 构建在 Clang 家族里是最难搞定的一种。
所以现状是:一个厂商支持它,一个厂商从较新版本开始支持其中一部分,还有一个完全不支持。这不是一个你可以不检查就依赖的特性;这是一个你每发一个工具链就得测一遍的特性。另外注意 cppreference 自己的注记把 libc++ 的说法写成了像是在说那两个值得一提的 C++ 库一样 —— 这正是那种你不能拿来代替实测的二手中介总结。
与此同时 C 的独立清单是四个头文件,而且从来没有兼容性问题,因为它是一份编译器缺了就无法工作的东西的清单。