GCC中构造函数的重载具有歧义性,但在Clang中不是。

19

假设我们有如下简单的代码:

#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编译器有bug?

1
我认为requires子句使得T版本更加特殊,应该优先于constT&版本。我认为gcc有个bug。 - Ted Lyngmo
@user17732522 - 谢谢,"constrained"正是我要找的词语 :-) - Ted Lyngmo
我听说GCC是正确的。(https://www.reddit.com/r/cpp_questions/comments/ser4nv/comment/humqwth/?utm_source=share&utm_medium=web2x&context=3, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96333, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98987) - Language Lawyer
1
以上代码也可以在MSVC上编译。 - The Coding Fox
1个回答

28
GCC是正确的。它是有歧义的。
首先,我们必须看隐式转换序列。在这两种情况下,都涉及到了恒等转换序列:intint,以及intconst int&(后者由于[over.ics.ref]/1被视为恒等转换序列)。
其次,我们看标准转换序列的决胜规则,[over.ics.ref]/3.2。在这种情况下,没有任何一个这些决胜者适用。这意味着两个隐式转换序列都不比另一个好。
接下来,我们必须去全局决胜者。即使对于一个重载的所有隐式转换序列既不比另一个更好也不比另一个更糟,这些决胜者也可以使一个重载比另一个更好。全局决胜者在[over.match.best.general]/2中定义。根据第五个子弹点(其他子弹点可能都无法适用于此情况),如果两个重载都是模板特化,但一个模板比另一个更专业,则其中一个重载可能比另一个更好。
要确定是否是这种情况,我们参考[temp.func.order],该参考[temp.deduct.partial]。我们处于函数调用的上下文中,因此根据(3.1),“使用的类型是函数参数类型,对于这些参数,函数调用有参数。”然后,第5段删除引用,并且第7段删除顶层cv限定符。这样做的结果是两个方向的推断都成功了。 (也就是说,即使不是每个T都是const U&,在实际推导发生之前,const U&仍然被U替换,因此推断在这个方向上成功。)
回到[temp.func.order],由于两个方向的推导都成功了,第2段提到的最终决胜者是一个模板是否比另一个更受约束。为此,我们滚动到第6段。适用的子弹点是(6.2.2),根据该子弹点:

否则,如果template-parameter-lists的相应template-parameter不等价([temp.over.link]),或者两个模板之间在位置上对应的函数参数不是相同类型,则两个模板都不比另一个更专业。

请注意,在这种情况下,引用和cv限定符的剥离不适用,因为那只是作为推导的一部分进行的,我们现在不再进行推导,所以在位置上对应的函数参数类型是Tconst T&,它们并不相同。因此,两个模板都不比另一个更专业化,这意味着最终的平局破解程序未能优先选择一个重载而不是另一个。


4
感谢您的解释和参考资料,我现在明白了为什么GCC具有正确的标准实现。 - apopa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接