CRTP中operator=与Concept的跨编译器兼容性问题咨询
C++编译器行为疑问:Clang报错而GCC/MSVC正常编译
Clang拒绝编译以下示例代码,而GCC和MSVC可正常编译。请问哪个编译器的行为符合C++标准?或者该代码属于无需诊断的格式错误?
#include <type_traits> #if 0 #define METHOD balabala // OK #else #define METHOD operator= // Clang error #endif struct base {}; template<typename T> concept derived = std::is_base_of_v<base, T>; template<derived Lhs, derived Rhs> struct assign; template<typename T> struct crtp: base { template<derived Rhs> constexpr auto METHOD(const Rhs& rhs) -> assign<T, Rhs> { return {}; } }; template<typename T> struct foo: crtp<foo<T>> { using crtp<foo<T>>::METHOD; }; template<derived Lhs, derived Rhs> struct assign: crtp<assign<Lhs, Rhs>> { }; static_assert(std::is_base_of_v<base, foo<int>>);
Clang错误信息
<source>:12:19: error: 代入约束表达式时产生非常量表达式 concept derived = std::is_base_of_v<base, T>; ^~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:14:10: note: 在此检查concept 'derived<foo<int>>'的满足性 template<derived Lhs, derived Rhs> struct assign; ^ <source>:14:10: note: 在此将模板实参代入约束表达式 template<derived Lhs, derived Rhs> struct assign; ^~~~~~~ <source>:19:44: note: 在此检查模板'assign<foo<int>, crtp<foo<int>>>'的约束满足性 constexpr auto METHOD(const Rhs& rhs) -> assign<T, Rhs> { ^~~~~~~~~~~~~~ <source>:17:8: note: 在此将推导的模板实参代入函数模板'operator=' [with Rhs = crtp<foo<int>>] struct crtp: base { ^ <source>:25:8: note: 在此为'foo<int>'声明隐式复制赋值运算符 struct foo: crtp<foo<T>> { ^ /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.1/../../../../include/c++/13.0.1/type_traits:3361:68: note: 在此实例化模板类'foo<int>' inline constexpr bool is_base_of_v = __is_base_of(_Base, _Derived); ^ <source>:32:20: note: 在此实例化变量模板特化'std::is_base_of_v<base, foo<int>>' static_assert(std::is_base_of_v<base, foo<int>>); ^ <source>:12:19: note: 'is_base_of_v<base, foo<int>>'的初始值未知 concept derived = std::is_base_of_v<base, T>; ^ /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.1/../../../../include/c++/13.0.1/type_traits:3361:25: note: 在此声明 inline constexpr bool is_base_of_v = __is_base_of(_Base, _Derived); ^ <source>:12:19: error: 代入约束表达式时产生非常量表达式 concept derived = std::is_base_of_v<base, T>; ^~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:14:10: note: 在此检查concept 'derived<foo<int>>'的满足性 template<derived Lhs, derived Rhs> struct assign; ^ <source>:14:10: note: 在此将模板实参代入约束表达式 template<derived Lhs, derived Rhs> struct assign; ^~~~~~~ <source>:19:44: note: 在此检查模板'assign<foo<int>, crtp<foo<int>>>'的约束满足性 constexpr auto METHOD(const Rhs& rhs) -> assign<T, Rhs> { ^~~~~~~~~~~~~~ <source>:17:8: note: 在此将推导的模板实参代入函数模板'operator=' [with Rhs = crtp<foo<int>>] struct crtp: base { ^ <source>:25:8: note: 在此为'foo<int>'声明隐式移动赋值运算符 struct foo: crtp<foo<T>> { ^ /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.1/../../../../include/c++/13.0.1/type_traits:3361:68: note: 在此实例化模板类'foo<int>' inline constexpr bool is_base_of_v = __is_base_of(_Base, _Derived); ^ <source>:32:20: note: 在此实例化变量模板特化'std::is_base_of_v<base, foo<int>>' static_assert(std::is_base_of_v<base, foo<int>>); ^ <source>:12:19: note: 'is_base_of_v<base, foo<int>>'的初始值未知 concept derived = std::is_base_of_v<base, T>; ^ /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.1/../../../../include/c++/13.0.1/type_traits:3361:25: note: 在此声明 inline constexpr bool is_base_of_v = __is_base_of(_Base, _Derived); ^ 生成2个错误。
问题分析与结论
核心原因
问题源于隐式赋值运算符生成触发的模板实例化循环依赖:
- 当
METHOD定义为operator=时,foo<int>继承自crtp<foo<int>>并引入了基类的赋值运算符模板。 - 编译器处理
foo<int>时,会尝试生成隐式复制/移动赋值运算符(因为类未显式声明),此过程需要检查基类的赋值运算符是否可用。 - 检查时会实例化基类的
operator=模板,而该函数的返回类型assign<T, Rhs>要求模板参数满足derived约束,即验证std::is_base_of_v<base, foo<int>>。 - 但此时
foo<int>的类型尚未完全定义(正处于隐式运算符生成过程中),导致is_base_of_v无法生成合法的常量表达式,触发Clang的报错。
编译器行为对比
- Clang符合标准:C++标准要求概念的约束表达式必须是常量表达式,当代入参数后无法得到有效常量表达式时,编译器必须诊断错误。这里
foo<int>未完全实例化,is_base_of_v的结果不确定,Clang的报错是正确的。 - GCC/MSVC属于宽松处理:这两个编译器可能推迟了约束检查的时机,或者在类型未完全定义时默认
is_base_of_v返回预期结果,从而绕过错误,但这不属于标准规定的行为。
最终结论
该代码存在需要诊断的格式错误,Clang的行为符合C++标准,GCC和MSVC的编译通过是编译器的非标准宽松处理。
内容的提问来源于stack exchange,提问作者VainMan
相关产品推荐
相关产品推荐

