std::is_constant_evaluated() 仅在明确需常量表达式的上下文中返回 true,如 static_assert 或数组大小;const 变量初始化、非常量参数或旧标准下均返回 false。

它不能让运行时代码变快,但能让你在同一个 constexpr 函数里,安全地为编译期常量和运行时变量提供两套互不干扰的实现——前提是调用点真被编译器当作常量表达式求值;否则分支永远进不去,还可能拖慢编译。
为什么 std::is_constant_evaluated() 总是返回 false
这不是 bug,是设计使然:它只在明确需要常量表达式的上下文中才可能为 true。常见误判场景包括:
-
int x = my_func();→ 普通变量初始化,不强制常量求值,必为false -
const int y = my_func();→const不等于constexpr,标准不要求走编译期 - 传入参数含
argv[1]、std::cin >> x或任何非常量值 → 整个调用退化为运行时 - 用 C++17 或更早标准 →
std::is_constant_evaluated根本不存在,链接失败
唯一可靠验证方式:static_assert(my_func() == 42); 或 int arr[my_func()] = {};。通不过,就说明没进编译期路径。
if (std::is_constant_evaluated()) 分支里写什么会直接编译失败
一旦进入该分支,所有语句都必须满足 C++20 常量求值规则。常见非法操作:
立即学习“C++免费学习笔记(深入)”;
- 调用
std::strlen、std::printf、std::sqrt(C++20 及之前)等非constexpr函数 - 使用
new、delete(C++23 部分支持但跨编译器不稳定) - 读取非常量全局变量或未初始化的
static变量 - 对局部变量取地址(除非该变量已初始化且生命周期可静态验证)
- 调用非
constexpr成员函数,或使用std::vector::push_back
安全替代方案:把编译期逻辑抽成独立 constexpr 辅助函数,比如 constexpr_size_t literal_strlen(const char*),主函数只做跳转。
几何归一化函数中如何正确分离编译期与运行时路径
向量归一化在编译期已知长度时可完全折叠为比例系数,在运行时则需浮点除法与平方根。关键不是“能不能”,而是“分得干净、不越界”:
- 函数签名必须带
constexpr,哪怕运行时分支用了std::sqrt - 必须用普通
if,不是if constexpr—— 后者要求条件本身是常量表达式,而std::is_constant_evaluated()不是 - 编译期分支中仅使用字面量运算、
constexpr std::sqrt(C++23)、或手工展开的牛顿迭代近似;禁止调用std::sqrt(C++20 及之前) - 运行时分支中可安全调用
std::sqrt、std::hypot,并支持异常处理与NaN输入检测 - 确保所有中间变量(如长度平方)在编译期分支中以
constexpr double声明,避免非常量初始化
示例片段(C++23):
constexpr double normalize_length(double x, double y) {
double len_sq = x * x + y * y;
if (std::is_constant_evaluated()) {
return len_sq > 0.0 ? std::sqrt(len_sq) : 0.0; // C++23 允许
} else {
return std::sqrt(len_sq); // 任意精度/异常处理逻辑放这里
}
}齐次坐标旋转矩阵的静态生成陷阱
构造固定角度旋转矩阵时,编译期应返回 constexpr std::array 字面量,运行时则调用 std::sin/std::cos。最容易被忽略的是约束边界:
- 编译期分支中不得出现
std::sin、std::cos或任何非constexpr数学函数调用,否则触发硬编译错误 - 只能对角度为字面量
M_PI_2、M_PI等特殊值做constexpr switch或if链判断 - 若角度是模板参数(如
template<auto angle></auto>),需额外static_assert验证其是否为字面量 - 运行时分支可自由使用
std::vector缓存常见角度表,但该能力不能反向污染编译期分支
真正难的不是写分支,而是让编译器相信你没越界——尤其当角度来自配置文件解析结果时,哪怕数值相同,也绝不会进编译期路径。



















