C++20中requires子句位置不同为何导致编译结果差异?
C++中requires子句位置对模板运算符重载的影响问题
问题场景
我们定义了MyType模板类,以及SFINAE风格的类型检测工具IsNotMyType,用于判断某个类型是否不属于MyType。接着实现了相等运算符与两个<=>(太空船)运算符:
- 第一个用于
MyType<T>与MyType<U>的比较 - 第二个用于
MyType<T>与非MyType类型U的比较
当第二个<=>运算符的requires子句置于返回值之后时,GCC和MSVC可正常编译,但Clang会触发递归模板实例化超出最大深度错误;将该requires子句移至返回值之前后,Clang可正常编译。
哪种实现符合C++标准?
将requires子句放在返回值之前的实现完全符合C++标准,而放在返回值之后的写法会导致未定义的递归实例化风险,Clang的报错是符合标准规则的。
原因在于:
- 当
requires子句位于返回值之前时,它属于模板约束,会在模板实参推导的早期阶段就被检查。只有当IsNotMyType<U>为真时,编译器才会继续推导返回值、实例化模板,从根源上避免了不必要的递归调用。 - 若
requires子句放在返回值之后,编译器需要先推导返回值类型(<=>的返回值依赖于操作数的比较结果类型),这个过程中可能会隐式尝试比较操作数,进而再次实例化当前运算符模板,最终触发递归深度溢出。这种情况下,GCC和MSVC的编译成功属于编译器的非标准扩展行为,而非符合标准的表现。
是否存在requires子句位置导致功能差异的场景?
是的,C++标准中requires子句的位置确实会引发功能差异,核心差异体现在以下方面:
- 约束检查时机:
- 返回值前的
requires是模板约束,在模板实参推导阶段就会过滤不满足条件的候选模板,不会进入后续实例化流程。 - 返回值后的
requires是函数约束,需要完成返回值推导、模板参数替换后才会检查,此时可能已触发部分模板实例化,甚至导致递归错误。
- 返回值前的
- SFINAE生效逻辑:
对于依赖模板参数的表达式,返回值前的requires会更早参与SFINAE,排除无效重载候选;而返回值后的requires可能因提前实例化导致SFINAE无法生效,触发编译错误。 - 重载决议优先级:
带有模板约束的函数(requires在返回值前)重载优先级高于带有函数约束的函数,当存在多个重载候选时,位置差异会影响最终选中的重载函数。
内容的提问来源于stack exchange,提问作者Sedenion
相关产品推荐
相关产品推荐

