std::ratio 是编译期分数类型,仅在类型层面携带约分后的分子分母,所有运算通过 std::ratio_add 等模板别名在实例化时完成,零运行时代价,不可取地址、不可用于算术表达式,取值需用静态成员 num/den。

std::ratio 是编译期分数,不是运行时对象
它不存储值,只在类型层面携带分子分母——std::ratio 和 std::ratio 是两个不同类型,但编译器会自动约分为同一类型(后者等价于前者)。这意味着所有运算都在模板实例化阶段完成,零运行时代价。
常见错误是试图对 std::ratio 取地址、传参给非模板函数,或用 auto 推导其“值”:auto r = std::ratio{}; 这样写看似合理,实际 r 是个空类型对象,没意义,也推不出分数值。
- 真正要的是类型运算:比如
std::ratio_add<ratio1 ratio2></ratio1>返回一个新std::ratio类型 - 取值必须通过静态成员
num和den:比如std::ratio::num是3,类型为constexpr intmax_t - 不能用
std::ratio直接参与算术表达式,比如r1 + r2会编译失败
加减乘除都靠 std::ratio_xxx 模板别名
标准库没提供操作符重载,所有运算都靠 std::ratio_add、std::ratio_subtract、std::ratio_multiply、std::ratio_divide 这四个别名模板。它们接受两个 std::ratio 类型,返回约分后的结果类型。
例如 std::ratio_multiply<:ratio>, std::ratio></:ratio> 展开后是 std::ratio,不是 std::ratio——约分是强制的,由标准保证。
立即学习“C++免费学习笔记(深入)”;
- 分母为 0 会触发编译错误(
static_assert),比如std::ratio或std::ratio_divide<r std::ratio>></r> - 溢出发生在分子/分母绝对值超过
intmax_t范围时,行为未定义(多数编译器报错) - 所有结果类型都是
std::ratio<n d></n>形式,N和D符号规则:分母恒为正,分子带符号
和 std::chrono::duration 混用时,ratio 决定精度
std::chrono::duration 的第二个模板参数就是 std::ratio,它直接控制该 duration 的最小可表示单位。比如 std::chrono::duration<int std::ratio>></int> 表示毫秒,而 std::chrono::duration<long std::nano></long> 中 std::nano 就是 std::ratio。
容易踩的坑是手动拼 std::ratio 导致精度丢失:比如用 std::ratio 表示微秒没问题,但若写成 std::ratio,虽然数学等价,却可能因中间计算溢出失败(尤其在旧编译器上)。
- 优先使用标准别名:
std::atto、std::femto、std::pico…std::kilo、std::mega - 自定义 ratio 时,确保
num和den是编译期常量且互质,否则可能触发冗余实例化或警告 - 跨 duration 运算(如
d1 + d2)依赖其 ratio 的通分逻辑,底层调用的就是std::ratio_add等,所以不兼容的 ratio 组合(如无法通分)会导致编译失败
自定义编译期分数工具要小心模板递归深度
如果基于 std::ratio 写更复杂的元函数(比如连分数展开、找最简逼近),很容易触发编译器模板递归限制。GCC 默认 900 层,Clang 约 256,MSVC 更低。
典型表现是编译卡住、内存爆满,或报错类似 template instantiation depth exceeds maximum of xxx。这不是代码逻辑错,而是元计算太“深”。
- 避免在模板参数中层层嵌套
std::ratio_xxx,尽量提前用using别名收口 - 用
static_assert快速拦截非法输入(如分母为负、过大),别让错误拖到深层实例化才暴露 - 某些场景下,用
constexpr函数替代深度模板递归反而更稳——std::ratio是类型工具,不是万能计算引擎











