第15章 泛型编程:概念、模板与类型擦除
1. 为什么要有这一章
第4章用
::fast_io::vector<int> 解释了“什么是 vector”,却从没说过那个
<int> 是从哪儿来的。第6.6章
演示了重载解析如何在 add(int, int) 与 add(double, double)
之间选择,也停在了重载失效的地方:函数体一模一样,只有类型不同。
那里正是泛型编程的起点。它的想法不是“把类型填进空里”,而是 把算法写一遍、把它的要求写出来、让编译器去检查这些要求。做这件事的工具有四个, 也正是本章的四条主线:
- 类型特征(traits) —— 关于类型的编译期事实 (第15.1章);
- 概念(concepts) —— 编译器在实例化之前就检查的具名要求 (第15.3章);
- 模板 —— 怎么定义一个模板,以及实例化的一切细节:值作为参数、推导、 参数包、展开语句(第15.2章、 第15.4章至 第15.8章);
- 类型擦除 —— 当选择真的必须发生在运行期时该怎么做 (第15.10章)。
本章只有一个敌人:代码膨胀。每一个不同的模板实参都会把模板碰到的一切 原样生成一份;把同一套逻辑实例化一千次的程序,编译很慢、二进制很大。所以本章在讲模板的 同时,也讲怎么把它压小:用概念把它收紧,用去模板化把它缩小,用类型擦除把它藏到一个类型后面。
2. 每个想法的一句话总结
| 想法 | 一句话 | 章节 |
|---|---|---|
| 类型特征 | 关于类型的编译期事实,形式是一个 bool 或另一个类型。 |
第15.1章 |
| 模板定义 | 带形参列表的声明;编译器为每个实参生成一份。 | 第15.2章 |
| 概念 | 编译器在实例化之前检查的一份具名要求清单;用来给类型分类,不用来搭分派。 | 第15.3章 |
| 非类型参数 | 一个“是值而不是类型”的模板实参。 | 第15.4章 |
| 推导 | 编译器替你写 <...>。 |
第15.5章 |
| 参数包 | 零个或多个模板实参,由折叠表达式或 template for 消费。 |
第15.6章 |
template for |
编译器在编译期展开的循环,每个元素一次迭代。 | 第15.7章 |
if constexpr |
一个把未选中分支丢弃掉、而不编译它的 if。 |
第15.8章 |
| 去模板化 | 把不碰模板实参的代码移出模板。 | 第15.9章 |
| 类型擦除 | 一个非模板类型装下许多类型的值,而且不用继承。 | 第15.10章 |
print/scan 支持 |
一个标签变量加一个概念:fast_io 的泛型打印如何找到你的类型。 | 第15.11章 |
3. 贯穿全章的示例
有一个小类型会跟着我们走完本章:三路 8 位通道、外加一个名字的颜色。
struct rgb
{
::std::uint_least8_t r{}, g{}, b{};
[[nodiscard]] ::std::string_view name() const noexcept
{
return "rgb";
}
};
它小到可以直接印在页面中间,有成员函数可以用来约束,有字段可以比较,还有一个显然的文本形式
(7,128,255) 可以扫回来。本章最后一页就教 fast_io 打印并扫描这个类型。
4. 本章约定
-
每个函数要么
noexcept,要么throws,绝不留空 (第6.16章)。 对模板来说,noexcept是对所有实例化的承诺,所以只有任何实例化都不会失败时才写。 当契约取决于实参时,写成条件形式throws(!noexcept(...))(第15.8章)。 -
失败用赫布异常:
throw throws ...与catch throws (::std::error e)(第3.16章)。 函数指针类型同样带错误契约,所以void (*)(void *) noexcept和void (*)(void *) throws是不同类型。 -
整数用
::std::int_least64_t、::std::uint_least8_t、::std::size_t;浮点用::std::float64_t。 -
本页所有完整程序都用
-std=c++26 -fherbceptions -lherbceptions编译并运行过;显示的输出就是它真正打印出来的内容。实例化个数用nm -C在链接后的可执行文件上读出。 -
比较器一律用具名函数对象(
::std::ranges::greater),不用 lambda; 除非那一节谈的正是 lambda(第15.9章)。
5. 你应该已经会的东西
6. 目录
- 第15.1章 类型特征:编译器知道哪些关于类型的事
- 第15.2章 如何定义模板:函数、类、成员
- 第15.3章 概念,以及为什么不要用它来分派
- 第15.4章 非类型模板参数:枚举、结构化类型、变量模板
- 第15.5章 模板实参推导与 CTAD
- 第15.6章 变参模板、折叠表达式、完美转发
- 第15.7章
template for:展开语句与模板下标 - 第15.8章
if constexpr与条件错误契约 - 第15.9章 模板膨胀、lambda,以及 fast_io 如何对
vector去模板化 - 第15.10章 类型擦除:不用继承的
shape - 第15.11章 fast_io 如何找到你的打印与扫描代码