constexpr修饰的变量必须在编译期求值,即初始化表达式须为常量表达式;const仅保证运行时不可修改,不保证编译期可知;constexpr函数可编译期或运行时执行,consteval则强制编译期求值。

constexpr 修饰的变量必须在编译期求值
这是 constexpr 最核心的约束:它要求初始化表达式必须是「常量表达式」,即编译器能在翻译单元处理阶段(而非运行时)完全计算出结果。比如 constexpr int x = 2 + 3; 合法,但 constexpr int y = std::rand(); 或 constexpr int z = some_runtime_func(); 直接编译失败——哪怕函数本身有 constexpr 声明,也得看调用上下文是否满足常量求值条件。
常见错误现象:error: constexpr variable 'xxx' must be initialized by a constant expression。典型诱因包括:用了未加 constexpr 的函数、访问了非静态成员变量、调用了未完成定义的 constexpr 函数(尤其跨文件时)、或对指针取地址后做算术(如 &arr[0] + 1 在 C++17 前不被视为常量表达式)。
使用场景集中在需要编译期确定值的地方:std::array 的模板参数 N、case 标签、位域宽度、静态断言 static_assert 的条件等。
const 只保证不可修改,不承诺编译期可知
const 是运行时语义:它声明一个对象不可被后续代码修改,但该对象的值可能来自运行时输入。例如 const int x = read_config_value(); 合法,x 在运行时才获得值,只是之后不能改——但它不能用于模板非类型参数,也不能触发编译期优化路径(如数组长度推导)。
立即学习“C++免费学习笔记(深入)”;
容易混淆的点:
-
const int x = 5;看似和constexpr int x = 5;效果一样,但前者不参与常量折叠(constant folding)优化,某些编译器不会把它当字面量用; -
const成员函数里可以调用非constexpr函数,而constexpr成员函数体内所有路径都必须满足常量表达式要求; -
const指针或引用(如const int* p)只限定解引用后的值不可改,指针本身可变;constexpr修饰的是变量本身,且隐含const(但反过来不成立)。
constexpr 函数不是“编译期专用”,而是“可编译期执行”
constexpr 函数的关键在于「双重能力」:当参数全为常量表达式时,它可在编译期求值;否则退化为普通函数,在运行时执行。例如:
constexpr int square(int x) { return x * x; }
constexpr int a = square(5); // 编译期计算,a 是字面量
int b = 10;
int c = square(b); // 运行时调用,c 是普通变量注意:C++14 起放宽了限制,允许 constexpr 函数体内出现循环、局部变量、if 分支等;但所有控制流路径仍需能构成常量表达式(比如不能有未定义行为、不能调用非 constexpr 函数)。C++20 进一步支持动态内存操作(如 std::string 构造),但实际支持度依赖标准库实现。
性能影响:过度标记 constexpr 不会提升运行时性能,反而可能增加编译时间;只有真正需要编译期计算的场景才值得用。
consteval 强制编译期求值(C++20 新增)
如果你发现某个函数「必须只能在编译期调用」,constexpr 已不够用——因为它允许运行时回退。consteval 就是为此而生:consteval int force_compile_time() { return 42; }。一旦尝试在运行时上下文中调用(如传入变量而非字面量),编译器直接报错:error: call to consteval function 'xxx' is not a constant expression。
适用场景极少但关键:比如生成唯一编译期 token、强制元编程路径、或规避某些运行时副作用。不过要注意:consteval 函数不能递归调用自身(除非编译器支持 C++23 的扩展),也不能调用非 consteval 或非 constexpr 函数。
容易被忽略的一点:constexpr 和 consteval 都不能绕过 ODR(One Definition Rule)——跨 TU 使用时,定义必须一致且可见,头文件中定义仍是主流做法。











