ale*_*cov 14 c++ gcc templates clang language-lawyer
考虑以下:
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; }
Run Code Online (Sandbox Code Playgroud)
此代码在GCC-7和Clang-5上编译良好.选定的重载operator +是struct E.
现在,如果发生以下变化:
/* Put `operator +` inside the class. */
template<typename T>
struct C {
template<typename U>
void operator +(U);
};
Run Code Online (Sandbox Code Playgroud)
即,如果operator +被定义内部类模板,代替外,再产生锵歧义之间的两个operator +S出现在代码中.海湾合作委员会仍然编制好.
为什么会这样?这是GCC还是Clang的错误?
编辑:这个答案的原始版本说GCC是正确的.我现在认为Clang根据标准的措辞是正确的,但我可以看到GCC的解释也是正确的.
让我们看看你的第一个例子,其中两个声明是:
template<typename T, typename U>
void operator +(C<T>&, U);
template<typename T>
void operator +(C<T>&, E);
Run Code Online (Sandbox Code Playgroud)
两者都是可行的,但很明显,第二个模板比第一个模板更专业.所以GCC和Clang都解析了对第二个模板的调用.但是让我们一起来看看[temp.func.order],看看为什么在标准的措辞中,第二个模板更加专业化.
部分排序规则告诉我们用唯一的合成类型替换每个类型模板参数,然后对另一个模板执行演绎.在这种方案下,第一种过载类型变为
void(C<X1>&, X2)
Run Code Online (Sandbox Code Playgroud)
并且对第二个模板的扣除失败,因为后者仅接受E.第二种过载类型变为
void(C<X3>&, E)
Run Code Online (Sandbox Code Playgroud)
并且对第一个模板的推断成功(使用T= X3和U= E).由于推导仅在一个方向上成功,因此接受另一个转换类型(第一个)的模板被认为不太专业,因此,第二个重载被选择为更专业的一个.
当第二个重载移入类时C,仍然会发现两个重载,并且重载解析过程应以完全相同的方式应用.首先,为两个重载构造参数列表,并且由于第一个重载是非静态类成员,因此插入了隐含的对象参数.根据[over.match.funcs],该隐含对象参数的类型应为"左值引用C<T>",因为该函数没有ref-qualifier.所以两个参数列表都是(C<D>&, E).由于这无法在两个过载之间进行选择,因此部分排序测试再次启动.
[temp.func.order]中描述的部分排序测试也插入了一个隐含的对象参数:
如果只有一个函数模板
M是某个类的非静态成员A,M则认为在其函数参数列表中插入了新的第一个参数.鉴于CV 为一体的cv修饰符M(如果有的话),新的参数的类型是"右值参考CVA"如果可选的 REF-限定符的M是&&,或者如果M没有REF-限定符和其它模板的第一参数具有右值参考类型.否则,新参数的类型为"对cv的 左值引用A".[ 注意:这允许对非成员函数排序非静态成员,并且结果等同于两个等效非成员的排序.- 结束说明 ]
这可能是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作为自由参数,然后才开始偏序测试.
这是gcc中的一个错误; 具体来说,https://gcc.gnu.org/bugzilla/show_bug.cgi?id = 53499.
问题是gcc将类模板成员函数的隐式对象参数视为具有依赖类型; 也就是说,在功能模板部分排序gcc转换期间
C<D>::template<class U> void operator+(U); // #1
Run Code Online (Sandbox Code Playgroud)
成
template<class T, class U> void operator+(C<T>&, U); // #1a (gcc, wrong)
Run Code Online (Sandbox Code Playgroud)
什么时候应该转变成
template<class U> void operator+(C<D>&, U); // #1b (clang, correct)
Run Code Online (Sandbox Code Playgroud)
我们可以看到,与你的相比
template<class T> void operator+(C<T>&, E); // #2
Run Code Online (Sandbox Code Playgroud)
#2比错误好#1a,但是含糊不清#1b.
观察到gcc错误地接受,即使C<D>它根本不是模板 - 即,何时C<D>是类模板完全特化:
template<class> struct C;
struct D;
template<> struct C<D> {
// ...
Run Code Online (Sandbox Code Playgroud)
这由[temp.func.order]/3涵盖,在示例中有说明.请注意,再次,gcc错误编译该示例,错误地拒绝它,但出于同样的原因.