C++20 concept搭配consteval时编译失败及编译器行为差异问题
C++20 consteval与concept编译行为问题解答
参考测试代码
template<bool op> class Person { const bool own_pet; public: Person() : own_pet(op) {} consteval bool OwnPet() const { return own_pet; } consteval bool OwnPetC() const { return true; } void PatPet() const {} }; template<typename T> concept MustOwnPet = requires(T obj) { requires obj.OwnPet(); }; void pat(const MustOwnPet auto& p) { p.PatPet(); } template<typename T> concept MustOwnPetC = requires(T obj) { requires obj.OwnPetC(); }; void pat_c(const MustOwnPetC auto& p) { p.PatPet(); } int main() { // GCC 12.1 -std=c++20 报错: // in 'constexpr' expansion of 'obj.Person<true>::OwnPet()' // <source>:16:24: error: 'obj' is not a constant expression // Clang 14.0.0 -std=c++20 同样编译失败 // pat(Person<true>()); // Clang 14.0.0 -std=c++20 编译报错 // GCC 12.1 -std=c++20 编译通过 pat_c(Person<true>()); return 0; }
问题1:pat(Person<true>())编译失败的原因
两个编译器拒绝该调用的行为完全符合C++20标准要求,核心原因是consteval函数的常量求值规则限制:
consteval修饰的函数必须在编译期完成求值,调用过程中所有实际被访问的实体、执行的操作都必须满足核心常量表达式的约束。MustOwnPet概念的requires表达式中声明的obj是仅用于编译期检查的占位形参,既不是constexpr对象,也不属于常量初始化的静态对象这类可在常量表达式中合法使用的实体。- 调用
obj.OwnPet()时,这个consteval函数需要读取obj的非静态成员own_pet,本质是通过隐式传入的this指针解引用访问obj的内存内容——这一步要求obj本身必须是常量表达式语境下的合法实体,但obj不满足要求,因此consteval调用非法,概念检查不通过,编译报错。 - 哪怕
own_pet的初始值是编译期已知的模板参数op,也绕不开这个规则:常量求值检查的是访问路径是否合法,不是看最终返回值能不能在编译期推出来。
问题2:pat_c()跨编译器表现差异的合规性判断
GCC 12.1的编译通过行为符合C++20标准要求,Clang 14.0.0的报错是早期版本的实现缺陷:
OwnPetC()虽然也是consteval成员函数,但函数体直接返回字面量true,整个求值过程完全没用到隐式this指针,也没访问调用对象obj的任何非静态成员或存储状态。- 核心常量表达式的判定规则是只检查求值过程中实际执行的操作有没有违反常量约束,如果传入的实参(这里是隐式传入的
&obj)在整个函数求值过程中根本没被用到,就不需要满足常量表达式的要求。 - 因此
obj.OwnPetC()的调用是合法的编译期常量表达式,MustOwnPetC概念检查可以通过,pat_c(Person<true>())本来就应该正常编译。Clang 14.0.0错误地要求所有传入consteval函数的实参(不管用不用)都必须是常量表达式,这个实现bug在后续Clang版本里已经修复了。
内容的提问来源于stack exchange,提问作者Donotalo
相关产品推荐
相关产品推荐

