4

我在使用 Apple LLVM 编译器(XCode 4.5.2 附带)时遇到了一个问题,同时使用 GCC 正确运行。比关于编译器问题的辩论更重要(尽管我认为 GCC 是正确的),这引发了关于重载运算符时模板专业化的解析顺序的问题 [1]。

考虑一个简单的矩阵类template <class T> class matrix_t,其特征定义了乘法的结果类型(使用标量、矩阵或向量)。这将类似于以下代码:

template <class T, class U, class Enable = void>
struct matrix_multiplication_traits {
  //typedef typename boost::numeric::conversion_traits<T,U>::supertype type;
  typedef double type;
};

template <class T, int N>
struct vectorN {
  std::vector<T> vect;
  vectorN() : vect(N) { for(int i = 0; i < N; i++) vect[i] = i; }
};


template <class T>
class matrix_t {
  std::vector<T> vec;
public:
  matrix_t<T> operator*(matrix_t<T> const& r) const {
    std::cout << "this_type operator*(this_type const& r) const" << std::endl;
    return r;
  }

  template <class U>
  matrix_t<typename matrix_multiplication_traits<T, U>::type>
  operator*(U const &u) const {
    std::cout << "different_type operator*(U const &u) const" << std::endl;
    return matrix_t<typename matrix_multiplication_traits<T, U>::type>();
  }

};

operator*还可以考虑for 的专业化vectorN

template <class T, class U, int N>
//vectorN<typename matrix_multiplication_traits<T,U>::type, N>
vectorN<double, N>
operator*(matrix_t<T> const&m, vectorN<U, N> const&v)
{
  std::cout << "vectorN operator*(matrix, vectorN)" << std::endl;
  //return vectorN<typename matrix_multiplication_traits<T,U>::type, N>();
  return vectorN<double, N>();
}

并考虑一个简单的测试程序:

int main(int argc, const char * argv[])
{
  matrix_t<double> test;
  vectorN<double, 10> my_vector;
  test * my_vector; // problematic line
  return 0;
}

“问题线”运行operator*(matrix_t<T> const&, vectorN<U, N> const&)在 GCC 和template <class U> matrix_t<T>::operator*(U const&) constLLVM 上的全局定义。所以就像matrix_t<T>::operator*(U const&)捕获所有模板专业化查找一样。一个简单的“修复”是将全局移动operator*到矩阵类中。

我首先认为这是特征类中的问题,这可能太复杂或错误(SFINAE)。但即使简化特征或完全禁用它(如粘贴代码)也会产生错误。然后我认为这是一个顺序问题(就像在 Herb Shutter 的文章中一样),但是在声明和定义operator*之间移动全局matrix_t并不会改变事情。

这是问题

当然,真正的问题是这template <class U> matrix_t::operator*(U const&) const太笼统了,但是:

  • 这种问题是标准涵盖的吗?
  • 类中定义的运算符重载是否优先于全局定义的运算符重载?
  • (更像是一个词汇问题)什么是资格operator*(matrix_t<T> const&, vectorN<U, N> const&)?模板重载运算符特化?这更像是模板特化还是重载函数?它的基本定义是什么?由于它本质上是一个重载的运算符,我有点迷茫。

[1] 我已阅读有关模板专业化顺序的 Herb Shutter 建议。

4

1 回答 1

2

类中定义的运算符重载是否优先于全局定义的运算符重载?

没有。根据 C++11 标准的第 13.3.1/2 段:

候选函数集可以包含要针对同一个参数列表解析的成员函数和非成员函数。为了使实参和形参列表在这个异构集合中具有可比性,成员函数被认为有一个额外的形参,称为隐式对象形参,它表示已为其调用成员函数的对象。出于重载决议的目的,静态和非静态成员函数都具有隐式对象参数,但构造函数没有。

此外,在标准中没有提到成员函数优于非成员函数,反之亦然。

这种问题是标准涵盖的吗?

是的。之所以选择全局运算符(应该是!)是因为它是一个比你的类中定义的函数模板更专业vectorN<U, N> const&的模板:在模板参数推导之后,两者和matrix_t<T> const&都可以匹配matrix_t<T> const& r,但前者比后者,因此,它是首选。

根据第 13.3.3/1 段:

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

[...]

F1 和 F2 是函数模板特化,根据 14.5.6.2 中描述的偏序规则,F1 的函数模板比​​ F2 的模板更特化。

所以:

所以就像 matrix_t::operator*(U const&) 正在捕获所有模板专业化查找

这种行为被称为错误。但是,请注意,这些不是特化,而是重载

最后:

什么资格operator * (matrix_t<T> const&, vectorN<U, N> const&)

我想你可以说它是一个(全局)运算符重载模板。它不是特化,因为模板函数特化具有不同的语法。因此,它没有专门的主要模板。它只是一个函数模板,一旦实例化,就会生成乘法运算符的重载

于 2013-02-22T16:53:02.273 回答