第20.6章:今天仍然缺什么

English

1. 头文件清单,以及每一项是哪一年进来的

独立实现必须至少提供标准清单上的头文件。下面是对普通代码要紧的那些,以及把它们加进来的标准,取自 cppreference 的表:

头文件状态自
<cstddef>AllC++98
<cfloat>, <climits>, <limits>AllC++98 / C++11
<cstdarg>, <cstdint>AllC++98 / C++11
<new>, <typeinfo>, <exception>AllC++98
<initializer_list>, <type_traits>, <atomic>AllC++11
<concepts>, <coroutine>, <bit>, <source_location>, <version>AllC++20
<utility>, <tuple>, <ratio>AllC++23
<memory>, <iterator>, <ranges>, <functional>PartialC++23
<cstdlib>PartialC++98
<array>, <cstring>, <string>, <cmath>, <system_error>, <charconv>, <algorithm>PartialC++26

两件事很扎眼。第一,<utility> —— std::move 住的地方 —— 直到 C++23 才成为独立头文件,比这门语言发布晚了二十五年。第二,看最后一行,然后注意那正好是你最需要的一行。

2. C++26 的窟窿

那最后几项是真正的标准工作,已经通过并投票采纳 —— 为 C++26 采纳的。它们由 __cpp_lib_freestanding_array = 202311L 之类的特性测试宏守护,而它们背后是一部没有发布的标准。所以 std::array 今天的诚实状态是:

一个三元素的定长数组是语言里要求最低的容器:不需要堆、接口不要求异常、不分配、不涉及 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 的独立清单是四个头文件,而且从来没有兼容性问题,因为它是一份编译器缺了就无法工作的东西的清单。