consteval函数只能在编译期调用并求值,否则直接报错;constinit变量必须在编译期或启动前完成初始化,但变量可修改。
consteval 修饰的是函数(包括构造函数),它强制该函数**只能在编译期被调用**,且所有调用点都必须能被常量表达式上下文接受。一旦出现无法在编译期计算的情况(比如参数来自运行时变量),编译器会立刻报错,不会退化为运行时调用。
常见错误现象:error: call to consteval function 'xxx' is not a constant expression
try、asm、goto、动态内存分配等运行时操作constexpr 变量初始化、模板实参、static_assert 条件),就编译失败consteval int square(int x) {
return x * x;
}
int main() {
constexpr int a = square(5); // ✅ OK:编译期求值
int b = 10;
// int c = square(b); // ❌ 编译错误:b 不是常量表达式
}
constinit 修饰的是**变量声明**,它只约束初始化过程必须发生在静态初始化阶段(即编译期或程序启动前),不关心变量是否可修改、也不要求初始化器是常量表达式——只要初始化器本身能被编译器在编译期“算出来”即可(例如调用 constexpr 或 consteval 函数,或使用字面量、常量引用等)。
关键区别:它不隐含 const,变量可以是非 const 的;但它禁止动态初始化(比如调用普通函数、依赖全局对象构造顺序的初始化)。
error: 'xxx' must be initialized by a constant expression
consteval int get_init_val() { return 42; }
constexpr int f() { return 123; }
constinit int x = get_init_val(); // ✅ 编译期初始化,x 可修改
constinit int y = f(); // ✅ 同样合法
// constinit int z = rand(); // ❌ rand() 不是常量表达式,编译失败
x = 99; // ✅ 允许,x 不是 const
三者定位完全不同:consteval 是函数限定符,constinit 是变量初始化限定符,const 是类型限定符。混用时语义叠加但互不替代。
constinit const int v = 42; → 变量在编译期初始化,且不可修改(双重保障)consteval int foo() { return 1; } → 函数只能用于常量表达式,但返回值未必绑定到 const 变量上constinit int arr[foo(2)]; → 合法:数组大小由 consteval 函数决定,且 arr 在编译期完成初始化容易踩的坑:constinit 不提供线程安全保证(它只是禁止动态初始化,并不等于 constexpr 初始化就自动线程安全);而 consteval 函数若内部访问静态局部变量,会导致编译失败(因为静态局部变量初始化属于动态初始化)。
如果你要确保某个计算逻辑**永远不跑到运行时**,且只用于常量上下文,就用 consteval;如果你要确保某个全局变量**一定在 main 之前初始化完毕**(尤其跨翻译单元),避免 SIOF,就用 constinit。
二者不是替代关系,而是互补:一个管“怎么算”,一个管“何时赋初值”。最

constinit 变量的初始化器可以调用 consteval 函数,但反过来,consteval 函数里不能引用未被 constinit(或 constexpr)约束的非常量全局变量——因为那可能还没初始化。