C++ / a working model

98 / 103   ·   C++20   ·   约 14 分钟

量纲类型:编译期阻止长度与时间相加

先记住这句话

把长度和时间的指数放进类型,数值仍按普通算术处理。这个小型模型证明加法必须同量纲、乘法会组合量纲,并明确区分量纲、单位比例与数值安全。

本篇内容
  1. 哪些信息属于类型,哪些属于数值
  2. 加法匹配,乘法合成
  3. 量纲正确不代表计算完全正确
  4. 运行示例
  5. 动手练习
READING EVIDENCE / 已读部分正文

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。这同时验证类型合成和数值计算。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录