非常量替换约束表达式是否应触发硬错误?C++概念实现分歧
C++ Concept约束表达式的错误处理疑问
问题1:访问非静态成员的约束表达式应触发硬错误还是返回false?
当T = a时,表达式T::b无法解析,因为b是a的非静态成员。这种无效表达式的生成,应使concept c的约束表达式求值为false,还是直接触发硬错误?原因是什么?
template<typename T> concept c = (bool)T::b; struct a { bool b; }; static_assert(not c<a>); // #1 - clang通过,gcc报错,msvc通过
问题2:逗号表达式中访问非静态成员的约束求值问题
当将表达式改为(T::b, true)且T = a时,连GCC也不再将其视为硬错误。但无论表达式结果是否被丢弃,T::b都应被求值,GCC在T::b非常量时将约束表达式求值为true是否错误?
template<typename T> concept d = (T::b, true); static_assert(not d<a>); // #2 - clang通过,gcc报错,msvc通过
GCC错误信息(翻译后)
<source>:2:9: 由'template<class T> concept c'的约束所要求 <source>:2:22: 错误:'*(a*)(void)0' 不是常量表达式 2 | concept c = (bool)T::b; | ^ <source>:2:9: 由'template<class T> concept c'的约束所要求 <source>:2:22: 错误:'*(a*)(void)0' 不是常量表达式 <source>:6:15: 错误:静态断言的条件不是常量 6 | static_assert(not c<a>); | ^~~~~~~~ <source>:2:9: 由'template<class T> concept c'的约束所要求 <source>:2:22: 错误:'*(a*)(void)0' 不是常量表达式 2 | concept c = (bool)T::b; | ^
解答
核心规则:SFINAE与常量表达式求值的边界
根据C++20标准,约束表达式的求值遵循SFINAE(替换失败不是错误)规则,但存在关键例外:
- 仅当模板参数替换导致语法或类型匹配的直接错误时,才会触发SFINAE,此时约束表达式求值为
false。 - 若替换后的表达式语法合法,但语义上无法在常量表达式上下文中求值(比如访问非静态成员却无对象实例),则不属于SFINAE范畴,会触发硬错误。
问题1的分析
当T=a时,T::b尝试访问非静态成员,编译器会在常量表达式上下文中尝试用一个虚构的空指针(即错误信息中的*(a*)(void)0)来求值,这显然违反常量表达式的合法性要求:
- GCC的行为符合标准:这种情况属于常量表达式求值失败,不属于SFINAE的替换错误,因此直接触发硬错误。
- Clang和MSVC的处理属于对标准的宽松解读:它们将“无对象访问非静态成员”判定为替换阶段的语法错误,因此触发SFINAE,约束求值为
false。
问题2的分析
逗号表达式中,左操作数T::b仍需被求值,它的语义错误并不会因为右操作数是true而消失。GCC在这里的行为不一致且错误:
- 按照标准,整个逗号表达式作为约束的原子表达式,只要其中任何一部分无法在常量上下文中求值,整个表达式就不是合法的常量表达式,要么触发硬错误,要么通过SFINAE判定约束不满足。
- GCC返回
true的行为不符合标准,因为它忽略了左操作数的求值失败,错误地认为右操作数的常量性可以覆盖整个表达式的合法性。
内容的提问来源于stack exchange,提问作者303
相关产品推荐
相关产品推荐

