模板类中模板函数的重载

Overloading of template function in template class

本文关键字:重载 函数      更新时间:2023-10-16

我在模板化类中有一个模板化操作符,我想为特定类型更改其行为。我的代码:

#include <iostream>
template <typename N>
struct A {
  int x;
  template<typename T>
  A& operator<<(const T& t) {
    x += static_cast<int>(t);
    return *this;
  }
};

enum class B {
  s,t
};
template <typename N>
A<N>& operator<<(A<N>& a, const B& b) {
  a.x -= static_cast<int>(b);
  return a;
}
int main() {
  A<int> a{3};
  std::cout << (a<<1).x << " " << (a << B::s).x;
}

g++-4.9编译得很好,但是clang-3.6抱怨它有歧义。

注意,如果类没有模板化,那么两个编译器都可以很好地编译它。

什么是正确的行为?

简短总结:我认为这是gcc在模板部分排序规则中的一个错误,clang是正确的。我提交了错误66914,尽管它可能是错误53499的副本,我直到后来才注意到。


在call

a << B::s;

我们有两个可行的候选人:

template <typename T> A<int>::operator<<(const T& );
template <typename N> operator<<(A<N>&, const B& );

可以重写成员函数,使其以对实例的引用作为第一个参数,并将两者都写为实例化。我们输入:

template <> operator<<(A<int>&, const B& ); // [T = B]
template <> operator<<(A<int>&, const B& ); // [N = int]

既然两者都是可行的候选者,让我们检查一下[over.match]中的规则。来确定哪一个是最可行的候选人:

给定这些定义,一个可行函数F1被定义为比另一个可行函数更好的函数F2如果对于所有参数i, ICSi(F1)不是比ICSi(F2)更差的转换序列,然后

-对于某些参数j, ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,

不,两者接受完全相同的实参,因此转换序列是相同的。

-上下文是用户自定义转换的初始化[…])

不,无关紧要。

-上下文是一个初始化转换函数[…])

不,无关紧要。

- F1不是函数模板特化,F2是函数模板特化,或者,如果不是,

不,都是函数模板特化。

- F1和F2是函数模板专门化,F1的函数模板更专门化

根据14.5.6.2中描述的部分排序规则,将F2的模板替换。

这是最复杂的规则。最终,两者都不比另一个更专业。为什么?成员函数有效地:

template <typename T> A<int>& operator<<(A<int>&, const T& );

如果我们为T(称为Unique1)合成一个类型,则对自由函数的演绎将失败(因为Unique1B不匹配)。另一方面,如果我们为N(称为Unique2)合成了一个类型,则对成员函数的演绎将失败(因为Unique2int不匹配)。

由于这两个函数都没有比另一个函数更专门化,我们已经没有要点了。函数调用应该是不明确的,这是一个gcc bug。

This:

template <typename N>
A<N>& operator<<(A<N>& a, const B& b) {
  a.x -= static_cast<int>(b);
  return a;
}

不是成员操作符的重载(我确信从标准的角度有更正确的说法),也不是特化,它是参与重载解析的全局模板操作符。

从这个角度来看,两者都是完美匹配的,所以我认为clang是正确的。