98 / 103 · C++20 · 约 14 分钟
量纲类型:编译期阻止长度与时间相加
把长度和时间的指数放进类型,数值仍按普通算术处理。这个小型模型证明加法必须同量纲、乘法会组合量纲,并明确区分量纲、单位比例与数值安全。
C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond
实际读完 InformIT 正式第 3 章 A Deeper Look at Metafunctions 的 3.1–3.7(含习题),并阅读 Boost MPL tutorial 的维度、量、加减、乘、除对应正文。第 1–2、4–11 章与附录未读;Boost 文档不是整本书,出版社购买版未取得。
查看版本、实际阅读范围与原文入口 →哪些信息属于类型,哪些属于数值
两段长度可以相加,一段长度与一段时间却不能相加。只用 double 保存二者,会把本应在接口处拒绝的操作留给人工记忆。Quantity<L,T> 把长度和时间的指数固定进类型,例如长度是 <1,0>,速度是 <1,-1>,持续时间是 <0,1>;成员 value 保存实际数值。
原书第 3 章使用 MPL 整数序列表达更完整的维度空间。本课刻意只保留两个维度,避免把一个原则演示包装成通用物理单位库。模板计算的是结果类型,不要求输入数值都是编译期常量。
加法匹配,乘法合成
加法模板的两个参数使用同一组 L、T。两个实参如果携带不同量纲,模板推导不能得到一致参数,因此没有匹配的加法。乘法则接收两组指数,并把指数分别相加:速度乘时间得到长度。运行时代码只需乘两个值,维度元数据不占每个对象的存储。
概念 Addable 的静态断言让“不允许什么”也成为示例的一部分:长度可与长度相加,却不能和时间相加。实际运行中只执行合法算式,没有故意运行未定义行为。这里不实现隐式转换回 double,因为轻易丢掉维度标签会让类型边界形同虚设。
量纲正确不代表计算完全正确
本例约定数值使用米与秒,但类型并没有编码米和千米的比例。若调用者把 3 千米误填为 value=3,编译器无法发现;完整单位库还需要比例换算和明确构造接口。摄氏温度等带偏移的单位也不能仅靠指数乘法解决。
double 的舍入、无穷大和除零风险同样独立存在。示例选择可精确表示的小整数结果,断言不会依赖脆弱的小数相等。类型系统阻止的是维度类别错误,不负责证明整个物理模型。本例使用 C++20 requires 展示拒绝边界;书中的 C++03 MPL 语法是历史实现背景,不是今天必须手工重建的框架。
容易答错的地方
- 相同量纲不表示相同单位比例;本例没有自动换算千米和米。
- 类型正确仍可能发生数值溢出或精度损失;编译期元数据不能替代数值分析。
运行一个例子
最低标准 C++20 · 完整程序 · 下载 .cpp
#include <cassert>
#include <concepts>
template<int Length, int Time>
struct Quantity {
double value;
explicit constexpr Quantity(double v) : value(v) {}
};
template<int L, int T>
constexpr Quantity<L, T> operator+(Quantity<L, T> a, Quantity<L, T> b) {
return Quantity<L, T>{a.value + b.value};
}
template<int L1, int T1, int L2, int T2>
constexpr Quantity<L1 + L2, T1 + T2> operator*(
Quantity<L1, T1> a, Quantity<L2, T2> b) {
return Quantity<L1 + L2, T1 + T2>{a.value * b.value};
}
template<class A, class B>
concept Addable = requires(A a, B b) { a + b; };
using Length = Quantity<1, 0>;
using Duration = Quantity<0, 1>;
using Speed = Quantity<1, -1>;
static_assert(Addable<Length, Length>);
static_assert(!Addable<Length, Duration>);
static_assert(std::same_as<decltype(Speed{3} * Duration{4}), Length>);
int main() {
const Speed speed{3};
const Duration elapsed{4};
const Length traveled = speed * elapsed;
const auto total = traveled + Length{5};
assert(traveled.value == 12);
assert(total.value == 17);
}
在本地编译
g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread books-cpp-template-metaprogramming.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
在不引入除法的前提下,表示加速度并验证“加速度乘时间得到速度”。
查看参考答案
定义 using Acceleration = Quantity<1,-2>;。表达式 Acceleration{2} * Duration{3} 的类型是 Quantity<1,-1>,也就是 Speed,值为 6。加入相应 std::same_as 静态断言,再将结果保存为 Speed 并断言 value == 6。这同时验证类型合成和数值计算。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。