模板是编译期计算引擎,需用requires约束、if constexpr分支、偏特化契约和显式实例化来控制实例化爆炸,而非仅泛化类型。

模板不是语法糖,是编译期计算引擎
Stroustrup 强调的“零开销抽象”本质是:抽象层不引入运行时成本,也不牺牲类型安全。这要求你把 template 当作编译期逻辑处理器来用,而不是仅用来泛化类型。
常见错误是写成“带 typename 的函数重载”——比如只用 typename T 替换 int,却不约束接口契约。结果是编译报错信息变成一长串 no matching function for call to ...,根本看不出哪条语义没满足。
- 用
std::is_integral_v或std::is_same_v做 SFINAE 前置守卫,比靠编译器推导失败更早拦截非法调用 - C++20 起优先用
requires约束而非enable_if,语义更直白,错误提示更准 - 避免在模板内部做运行时分支(如
if (sizeof(T) > 4)),这种判断应由特化或if constexpr在编译期完成
特化不是补丁,是接口契约的显式分发点
很多人把 template struct hash 当作“不得不加的胶水代码”,其实它是你主动声明“我承诺提供这个接口”的契约点。漏掉特化,不是功能缺失,而是契约断裂。
典型陷阱是误用全特化替代偏特化:比如为所有指针类型写一个 template class vector,这会覆盖所有 T*,但实际你只想处理 int* 和 double* —— 正确做法是偏特化 template。
立即学习“C++免费学习笔记(深入)”;
- 全特化(
template)适用于已知具体类型组合,且行为完全独立 - 偏特化(
template)用于一类类型共享逻辑,但需保留部分泛型参数参与推导 - 用户自定义类型若要适配标准库容器,必须提供
operator==、hash等,否则std::unordered_map编译失败不是 bug,是你没履约
constexpr if 是编译期 if-else,不是运行时优化开关
它不生成运行时分支指令,而是让编译器直接剔除不满足条件的分支代码。这意味着被剔除分支里的代码仍需语法正确(比如不能有未声明的函数调用),但不必能通过链接。
templateauto get_value(const T& x) { if constexpr (std::is_pointer_v ) { return *x; // 只有 T 是指针时,这一行才参与编译 } else if constexpr (std::is_arithmetic_v ) { return x + T{1}; // T 是算术类型时才编译此分支 } else { static_assert(sizeof(T) == 0, "get_value not supported for this type"); } }
-
static_assert放在else分支里,是为了给未覆盖类型提供清晰报错,而不是靠编译器默认失败 - 不要在
if constexpr外部写依赖分支内变量的代码,那些变量在别的分支里根本不存在 - 和宏相比,
if constexpr有作用域、类型检查和调试支持;和普通if相比,它消灭了无用代码路径的二进制体积和分支预测开销
别让模板实例化爆炸毁掉编译时间和二进制大小
一个 vector 实例化,可能触发 allocator、basic_string、char_traits 等十多个模板的连锁实例化。放任不管,会导致头文件包含爆炸、链接重复符号、LTO 时间飙升。
- 对稳定接口,用显式实例化(
template class vector)在 .cpp 文件中强制生成一份,头文件只留声明; - 避免在模板参数中传递非必要类型,比如用
std::size_t代替decltype(sizeof(0))—— 后者每次推导都可能产生新实例 - 使用
extern template抑制跨 TU 重复实例化,但需确保至少一处有定义,否则链接失败
最常被忽略的是:模板元函数(如 std::tuple_size)本身不占运行时空间,但它的每个特化都会增加编译器符号表压力。写库时,宁可多拆几个小模板,也不要堆一个“全能型”大模板。








