Clang的C++20 concepts实现是否存在触发无限模板递归的缺陷?
问题根本原因
这是Clang的约束检查逻辑严格遵循C++标准导致的递归,属于合规行为,GCC和MSVC属于对非可行候选的提前剪枝,恰好避开了递归,并不算标准违规。
递归触发的完整链路如下:
- 当Clang处理到需要检查
Deferred<double>是否满足Numeric约束时(触发源来自对泛型转换运算符operator TO()的约束检查),会按顺序验证Numeric的所有requires表达式。 - 验证第一个
{a + b} -> std::same_as<T>(此时T为Deferred<double>)时,需要对a + b做完整的重载决议:- 除了你定义的友元
operator+外,你的类还提供了不受限的隐式泛型转换运算符template <Numeric TO> operator TO(),只要TO满足Numeric就可以做隐式转换。 - 编译器需要穷举所有可能的TO类型来判断是否存在可行的转换+内置运算符组合,其中就包括
TO = Deferred<Deferred<double>>这种情况。
- 除了你定义的友元
- 要实例化
Deferred<Deferred<double>>,首先要检查模板参数Deferred<double>是否满足Numeric约束,这就回到了第一步,形成无限递归。
相关现象的解释
- 删除最后两个函数就正常:要么没了
operator+导致第一个约束直接失败短路,要么没了带Numeric约束的泛型转换运算符,不会触发对Deferred嵌套实例的约束检查。 - 调整约束顺序就正常:只要第一个检查的是你没有重载的运算符(比如
a - b),约束直接短路失败,不会走到a + b的检查步骤,自然不会触发递归。 - 把转换运算符约束改成
typename TO就正常:此时转换运算符没有Numeric约束,编译器不需要检查TO是否满足Numeric,也就不会触发Deferred嵌套实例的实例化。 - 加explicit就正常:explicit转换运算符不会在隐式转换场景下被纳入重载决议候选集,编译器不需要穷举可能的TO类型,也就不会触发递归。
为什么不需要隐式转换也会检查
C++标准要求在做重载决议时,必须考虑所有可行的隐式转换路径,哪怕最优匹配已经明显存在。Clang严格遵循了这个要求,哪怕你的友元operator+已经是完美匹配,它还是要完成所有候选的可行性检查,而这个检查过程刚好触发了递归。GCC和MSVC在实现上做了优化,提前剪枝了明显不如最优匹配的候选,所以避开了这个问题。
内容的提问来源于stack exchange,提问作者saxbophone
相关产品推荐
相关产品推荐

