模板类中模板函数的重载

5

我有一个模板类中的模板运算符,并且我想要改变它对于特定类型的行为。我的代码:

#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报告有歧义。

请注意,如果类没有进行模板化,则两个编译器都可以正常编译它。

正确的行为是什么?


1
我理解这个问题是:这是重载(模糊不清,clang 是正确的)还是特化(所以 gcc 是正确的)? - marom
@marom 一个专业化可能会有不同的语法,可能涉及到带有空参数集的 template<> - n. m.
@n.m. 你的意思是函数调用吗?那么为什么clang会抱怨呢? - marom
这不是过载与专业化的问题。我认为Clang的歧义警告是因为它无法确定具有右侧模板参数的成员模板函数与具有左侧模板参数的自由模板函数之间的部分排序。但我不确定哪种行为是正确的。 - TartanLlama
@marom 不好意思,我是指它的定义。 - n. m.
2个回答

5
简要概述:我相信这是一个gcc模板部分排序规则中的bug,而且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]中的规则来确定哪个是最佳的可行候选项:
根据这些定义,如果对于所有的参数 i,ICSi(F1) 不比 ICSi(F2)更差,则定义一个 viable function F1 比另一个 viable function F2 更好,然后
- 对于某些参数 j,ICSj(F1) 是优于 ICSj(F2) 的 conversion sequence,或者,如果不是这样,
没有,它们都采用相同的参数,因此转换序列是相同的。
- 上下文是由用户定义的转换初始化[...]
不是,与此无关。
- 上下文是通过 conversion function 进行 initialization [...]
不是,与此无关。
- F1 不是函数模板特化而 F2 是函数模板特化,或者,如果不是这样,
不是,它们都是函数模板特化。
- F1 和 F2 都是函数模板特化程序,且 F1 的函数模板比 14.5.6.2 中描述的 partial ordering rules 中的 F2 更加特化。
这是规则中最复杂的部分。最终,两者都不比另一个更加特化。为什么?成员函数实际上是:
template <typename T> A<int>& operator<<(A<int>&, const T& );

如果我们为T合成一种类型(称之为Unique1),则对于自由函数,推断将失败(因为Unique1不匹配B)。另一方面,如果我们为N合成一种类型(称之为Unique2),则对于成员函数,推断将失败(因为Unique2不匹配int)。
由于两个函数都没有更加特化,我们已经用完了所有的弹药点。函数调用应该是模棱两可的,这是一个gcc的bug。

1
为什么 N 不应参与模板解析的第一个案例? - RiaD
每次我在GCC中看到一个bug,我也会在MSVC++中找到它,并且两者都总是假定正确的调用。 - user5106417
@RiaD 因为只有模板参数被推导了 - 在第一种情况下,你正在调用一个类模板的成员模板 - 你不推导 N(它是给定的),只推导 T - Barry
以同样的方式,如果我们将operator<<(const B&)写为成员函数,那么该成员函数将不是成员函数模板。 - Barry

-1

这个:

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

不是成员运算符的重载(从标准角度来看,我确定有更正确的说法),也不是特化,它是一个全局模板运算符,参与重载决议。

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


1
你有“这不是重载”和“它参与重载决策”。它要么是重载,要么不是,选一个。 - Barry
我试图强调这个事实,即在回应帖子标题时,它并不是类的成员,这可能被解读为OP认为他正在重载模板的成员而不是创建全局运算符。很抱歉,正如我所说,我的标准语言不是很强。 - Alexander Balabin

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