4. constexpr函数实现编译期单位换算
为什么单位换算适合放进编译期
长度、时间这类换算的系数是固定的:1 km = 1000 m、1 h = 3600 s。如果换算函数是 constexpr,那么当输入是编译期常量时,结果也在编译期算出,运行时零开销;同时可以用 static_assert 把"1 km 必须等于 1000 m"这类事实写成编译期断言,一旦系数写错,编译直接失败,而不是等到运行期才发现。
关键约束有三点:函数体必须是 C++14 起允许的宽松 constexpr(可含局部变量、分支、循环);参数与返回类型必须是字面类型;static_assert 的表达式必须能在编译期求值,因此传入的实参本身也得是常量表达式。
实现:长度与时间两组换算
下面给出一个可直接编译的单文件示例。长度以米为基准,时间以秒为基准,每个换算函数都是 constexpr,并用 static_assert 覆盖正向与反向换算。
#include <cassert>
#include <cstdint>
// ---------- 长度:基准单位 = 米 ----------
constexpr double km_to_m(double km) { return km * 1000.0; }
constexpr double m_to_km(double m) { return m / 1000.0; }
constexpr double cm_to_m(double cm) { return cm / 100.0; }
constexpr double inch_to_m(double in) { return in * 0.0254; }
// ---------- 时间:基准单位 = 秒 ----------
constexpr double h_to_s(double h) { return h * 3600.0; }
constexpr double min_to_s(double m){ return m * 60.0; }
constexpr double s_to_ms(double s){ return s * 1000.0; }
// ---------- 编译期验证 ----------
static_assert(km_to_m(1.0) == 1000.0, "1 km 应为 1000 m");
static_assert(km_to_m(2.5) == 2500.0, "2.5 km 应为 2500 m");
static_assert(m_to_km(500.0) == 0.5, "500 m 应为 0.5 km");
static_assert(cm_to_m(250.0) == 2.5, "250 cm 应为 2.5 m");
static_assert(inch_to_m(1.0) == 0.0254, "1 inch 应为 0.0254 m");
static_assert(h_to_s(1.0) == 3600.0, "1 h 应为 3600 s");
static_assert(min_to_s(90.0)== 5400.0, "90 min 应为 5400 s");
static_assert(s_to_ms(1.5) == 1500.0, "1.5 s 应为 1500 ms");
// 组合换算:km/h -> m/s,同样可在编译期完成
constexpr double kmh_to_ms(double v) { return km_to_m(v) / h_to_s(1.0); }
static_assert(kmh_to_ms(36.0) == 10.0, "36 km/h 应为 10 m/s");
int main() {
// 运行期同样复用这些函数
double d_km = 3.2;
double d_m = km_to_m(d_km);
assert(d_m == 3200.0);
return 0;
}
编译期断言的三个易错点
- 实参不是常量表达式:
static_assert(km_to_m(x) == 1000.0)中若x是普通变量,编译报错"不是常量表达式"。断言里必须直接写字面量或constexpr变量。 - 浮点相等比较:本例系数都是二进制可精确表示的整数倍(1000、3600、0.5、2.5、0.0254 需注意),
0.0254无法精确表示,但两侧用同一字面量参与运算,结果位模式一致,==成立。若换成0.1这类系数并做多次乘除,应改用容差比较,例如constexpr bool near(double a, double b) { return (a > b ? a - b : b - a) < 1e-9; }。 - 整数除法截断:若把参数写成
int,cm_to_m(250)会得到2而非2.5。单位换算统一用double,或在函数内显式转换。
把
static_assert 当作"编译期的单元测试":每新增一个换算函数,就补一条正向和一条反向断言。这样系数抄错、量纲写反都会在编译阶段暴露,而不是等到运行期数值异常。C++14 起
constexpr 函数才允许局部变量、if 和循环;若项目仍停留在 C++11,函数体只能是一条 return 语句,组合换算需要拆成多个单表达式函数。验证与扩展
编译命令:g++ -std=c++17 -Wall -Wextra -pedantic main.cpp -o units。若所有 static_assert 通过,编译无输出;故意把 km_to_m 的系数改成 100,会立刻看到类似 error: static assertion failed: 1 km 应为 1000 m 的提示,这就是编译期验证的价值。
扩展方向:把基准单位换成强类型(如 struct Meters { double v; };),让 km_to_m 返回 Meters 而非裸 double,可在编译期阻止"米和秒相加"这类量纲错误,与 constexpr 结合后仍保持零运行期开销。