第15章 泛型编程:概念、模板与类型擦除

English

1. 为什么要有这一章

第4章用 ::fast_io::vector<int> 解释了“什么是 vector”,却从没说过那个 <int> 是从哪儿来的。第6.6章 演示了重载解析如何在 add(int, int) 与 add(double, double) 之间选择,也停在了重载失效的地方:函数体一模一样,只有类型不同。

那里正是泛型编程的起点。它的想法不是“把类型填进空里”,而是 把算法写一遍、把它的要求写出来、让编译器去检查这些要求。做这件事的工具有四个, 也正是本章的四条主线:

本章只有一个敌人:代码膨胀。每一个不同的模板实参都会把模板碰到的一切 原样生成一份;把同一套逻辑实例化一千次的程序,编译很慢、二进制很大。所以本章在讲模板的 同时,也讲怎么把它压小:用概念把它收紧,用去模板化把它缩小,用类型擦除把它藏到一个类型后面。

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. 本章约定

5. 你应该已经会的东西

6. 目录