GCC 上有歧义的构造函数重载,但 Clang 上没有

Ambiguous constructor overload on GCC, but not on Clang

假设我们有以下简单代码:

#include <iostream>
#include <type_traits>

struct S {
    template <typename T> explicit S(T) noexcept requires std::is_signed<T>::value {
        std::cout << "T\n";
    }
    template<typename T> explicit S(const T&) noexcept {
        std::cout << "const T&\n";
    }
};

int main() {
    S s(4);
}

此代码使用 Clang 编译并打印 'T',但使用 GCC 时出现以下错误:

error: call of overloaded 'S(int)' is ambiguous

我的问题是:哪个编译器有错误,GCC 还是 Clang?

海湾合作委员会是正确的。模棱两可。

首先,我们必须看一下隐式转换序列。在这两种情况下,都涉及身份转换序列:intint,以及intconst int&(由于[=21=,后者被认为是身份转换序列) ]).

其次,我们查看关于标准转换序列的 tie-breaker 规则,[over.ics.ref]/3.2。 None 这些 tie-breaker 适用于这种情况。这意味着两个隐式转换序列都不比另一个好。

我们接下来必须去全球 tie-breakers。这些可以允许一个重载被认为比另一个更好,即使一个重载的所有隐式转换序列既不比另一个的相应隐式转换序列好也不坏。全局 tie-breaker 在 [over.match.best.general]/2 中定义。根据第五个要点(其他 none 可能适用于这种情况),如果两者都是模板特化但一个模板比另一个更专门化,则其中一个重载可能比另一个更好。

要确定是否属于这种情况,我们参考[temp.func.order], which refers to [temp.deduct.partial]。我们处于函数调用的上下文中,因此根据 (3.1),“使用的类型是函数调用具有参数的那些函数参数类型”。然后,第 5 段删除引用,第 7 段删除 top-level cv-qualifiers。这样做的结果是演绎在两个方向上都成功了。 (也就是说,即使不是每个 T 都是 const U&,推导无论如何都会在这个方向上成功,因为 const U& 在实际推导发生之前被 U 替换。)

回到[temp.func.order],由于推导在两个方向上都成功了,因此第2段中最后提到的tie-breaker是一个模板是否比另一个模板更受约束。为此,我们向下滚动到第 6 段。适用的要点是 (6.2.2),根据它:

Otherwise, if the corresponding template-parameters of the template-parameter-lists are not equivalent ([temp.over.link]) or if the function parameters that positionally correspond between the two templates are not of the same type, neither template is more specialized than the other.

请注意,在这种情况下,引用的剥离和 cv-qualifiers 不适用,因为这只是推导的一部分,我们不再进行推导,所以函数参数类型位置对应的是 Tconst T&,它们是不一样的。因此,两个模板都不比另一个更专业,这意味着最终的 tie-breaker 未能优先选择一个重载。