请考虑以下事项:
template<typename T>
struct C {};
template<typename T, typename U>
void operator +(C<T>&, U);
struct D: C<D> {};
struct E {};
template<typename T>
void operator +(C<T>&, E);
void F() { D d; E e; d + e; }
此代码在GCC-7和Clang-5上编译良好。 operator +
的所选重叠是struct E
的重载。
现在,如果发生以下变化:
/* Put `operator +` inside the class. */
template<typename T>
struct C {
template<typename U>
void operator +(U);
};
也就是说,如果在里面定义了,而不是在之外,那么Clang会在operator +
之间产生歧义在代码中。海湾合作委员会仍然编制好。
为什么会这样?这是GCC还是Clang的错误?
答案 0 :(得分:7)
编辑:这个答案的原始版本说GCC是正确的。我现在认为Clang根据标准的措辞是正确的,但我可以看到GCC的解释也是正确的。
让我们看看你的第一个例子,其中两个声明是:
template<typename T, typename U>
void operator +(C<T>&, U);
template<typename T>
void operator +(C<T>&, E);
两者都是可行的,但很明显第二个模板比第一个模板更专业。所以GCC和Clang都解析了对第二个模板的调用。但是让我们一起浏览[temp.func.order]以查看为什么,在标准的措辞中,第二个模板更专业。
部分排序规则告诉我们用唯一的合成类型替换每个类型模板参数,然后对另一个模板执行演绎。在此方案下,第一个重载类型变为
void(C<X1>&, X2)
并且对第二个模板的扣除失败,因为后者仅接受E
。第二种重载类型变为
void(C<X3>&, E)
并扣除第一个模板成功(T
= X3
和U
= E
)。由于推导仅在一个方向上成功,因此接受另一个转换类型(第一个)的模板被认为不太专业,因此,第二个重载被选为更专业的。
当第二个重载移入类C
时,仍然会找到两个重载,并且重载解析过程应以完全相同的方式应用。首先,为两个重载构造参数列表,并且由于第一个重载是非静态类成员,因此插入了隐含的对象参数。根据[over.match.funcs],该隐含对象参数的类型应为“对C<T>
的左值引用”,因为该函数没有ref-qualifier。所以这两个参数列表都是(C<D>&, E)
。由于这无法在两个重载之间进行选择,因此部分排序测试再次启动。
[temp.func.order]中描述的偏序测试,也插入一个隐含的对象参数:
如果只有一个功能模板
M
是非静态成员 某些类A
,M
被认为在其函数参数列表中插入了新的第一个参数。鉴于 cv 作为M
的cv限定符(如果有的话),新参数的类型为“对 cvA
的rvalue引用”M
的参考资格为&&
或M
没有参考资格且其他模板的第一个参数包含右值 参考类型。否则,新参数的类型为“对 cvA
的左值引用”。 [注意:这允许一个 要针对非成员函数进行排序的非静态成员,并且结果是等效的 订购两个相当的非会员。 - 结束记录]
这可能是GCC和Clang对标准有不同解释的步骤。
我的看法:已在班级operator+
中找到成员C<D>
。不推导出类T
的模板参数C
;它是众所周知的,因为名称查找过程输入了C<D>
的具体基类D
。因此,提交给部分排序的实际operator+
不具有免费的T
参数;它不是void operator+(C<T>&, U)
,而是void operator+(C<D>&, U)
。
因此,对于成员重载,转换后的函数类型不应该是void(C<X1>&, X2)
,而应该是void(C<D>&, X2)
。对于非成员重载,转换后的函数类型仍然像以前一样void(C<X3>&, E)
。但现在我们发现void(C<D>&, X2)
不匹配非会员模板void(C<T>&, E)
, void(C<X3>&, E)
匹配成员模板void(C<D>&, U)
。因此,部分排序失败,重载解析返回模糊结果。
GCC决定继续选择非成员重载是有意义的,如果你假设它正在构造词汇成员的转换函数类型,使其仍然void(C<X1>&, X2)
,而Clang将D
替换为U
模板,只留下{{1}}作为免费参数,在开始部分订购测试之前。
答案 1 :(得分:6)
这是gcc中的一个错误;具体而言,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53499。
问题是gcc将类模板成员函数的隐式对象参数视为具有依赖类型;也就是说,在功能模板部分排序gcc变换期间
C<D>::template<class U> void operator+(U); // #1
到
template<class T, class U> void operator+(C<T>&, U); // #1a (gcc, wrong)
什么时候应该转换成
template<class U> void operator+(C<D>&, U); // #1b (clang, correct)
与您的
相比,我们可以看到template<class T> void operator+(C<T>&, E); // #2
#2
优于错误的#1a
,但与#1b
不一致。
观察即使C<D>
根本不是模板,gcc也会错误地接受 - 即C<D>
是一个完全专业化的类模板:
template<class> struct C;
struct D;
template<> struct C<D> {
// ...
这由[temp.func.order]/3涵盖,并在示例中进行了说明。请注意,gcc再次错误编译该示例,错误地拒绝它,但出于同样的原因。