constexpr bool是否为原子约束?编译器行为差异及标准解析
以下代码可在MSVC中编译通过,但无法在GCC/Clang中编译(注:若将constexpr bool替换为concept,代码可正常编译):
template <typename TYPE> constexpr bool c1 = TYPE::c1; template <typename TYPE> constexpr bool c2 = TYPE::c2; template <typename TYPE> requires c1<TYPE> struct Foo {}; template <typename TYPE> requires c1<TYPE> && c2<TYPE> struct Foo<TYPE> {};
我有几个疑问:
- 示例中的偏特化是否拥有比主模板更特化的约束?
c1<TYPE>和c2<TYPE>是否属于原子约束?
根据C++标准中temp.constr.normal章节的内容,我原本认为E1 && E2形式的约束总会被拆分为两个独立部分,因此c1<TYPE>应属于原子约束,代码理应编译通过。此外,temp.constr.atomic/1的注释提到“原子约束由约束规范化形成,E绝不会是逻辑与或逻辑或表达式”,而c1<TYPE> && c2<TYPE>是逻辑与表达式,据此不应属于原子约束。
但另一方面,temp.constr.normal中存在针对concept的显式规则。如果E1 && E2总会被拆分,这条规则本不应是使concept成为原子约束的必要条件。
请问哪个编译器的行为是正确的?若GCC/Clang正确,我的误解点在哪里?
GCC/Clang的行为符合C++标准,MSVC的实现存在偏差,核心误解点在于对「原子约束」范畴的错误判断:
1. constexpr bool模板变量不是原子约束
原子约束的定义在标准中明确绑定到概念(concept)的实例化,或者是符合标准规定的特定原子形式(比如std::same_as<T, int>这类概念实例、类型检查表达式等)。而c1<TYPE>本质是对模板变量的引用,属于表达式约束,并非原子约束。
2. 约束拆分规则仅适用于原子约束的组合
标准中temp.constr.normal对E1 && E2的拆分规则,仅针对由原子约束通过逻辑操作符组合而成的约束。当约束是普通表达式(比如模板变量引用)时,编译器无法将其拆解为原子约束,自然也无法通过原子约束的包含关系来判定偏序。
对于示例中的偏特化,主模板的约束是requires c1<TYPE>,偏特化是requires c1<TYPE> && c2<TYPE>——由于这两个约束都是基于表达式而非原子约束,标准中没有规则判定后者比前者“更特化”,因此GCC/Clang会将其视为歧义的模板特化,拒绝编译。
3. 替换为concept为何能正常编译?
当把constexpr bool模板变量替换为concept后,c1<TYPE>就成为了概念的实例化,也就是原子约束。此时c1<TYPE> && c2<TYPE>会被拆分为两个原子约束的逻辑与,偏特化的约束明显包含了主模板的所有原子约束,同时多了一个c2<TYPE>,因此能被标准判定为更特化的约束,编译器可以正确选择偏特化版本。
关于MSVC的行为
MSVC在处理表达式约束的偏序时,可能采用了非标准的启发式判断,简单认为多了一个逻辑与条件的约束更强,但这不符合标准中关于约束偏序的严格定义——标准要求约束偏序必须基于原子约束的包含关系,普通表达式约束无法直接形成这种包含关系。
内容的提问来源于stack exchange,提问作者geza

