在模板类中重载模板函数

时间:2015-07-17 11:41:45

标签: c++ templates g++ language-lawyer clang++

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

#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抱怨说它很模糊。

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

什么是正确的行为?

2 个答案:

答案 0 :(得分:5)

简短摘要:我认为这是模板部分排序规则中的gcc错误,并且该clang是正确的。我提交了bug 66914,虽然它可能是bug 53499的副本,我直到事后才注意到它。

在通话中

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.best]中的规则来确定哪一个是最有效的候选人:

  

鉴于这些定义,可行函数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),则对自由函数进行推导会失败(因为Unique1不匹配B) 。另一方面,如果我们为N合成了一个类型(称之为Unique2),则对成员函数的推导将失败(因为Unique2不匹配{{1} })。

由于这两种功能都不比其他功能更专业,我们已经完成了一些项目。函数调用应该是不明确的,这是一个gcc bug。

答案 1 :(得分:-1)

此:

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

不是你的成员操作员的重载(我确信从标准的角度来看有更正确的方式来说),而不是专业化,它是一个参与过载的全局模板操作符分辨率。

从这个角度来看,两者都是同样完美的比赛所以我认为铿锵是正确的。