【问题标题】:Template binary operator overload resolution模板二元运算符重载决议
【发布时间】:2013-02-22 16:31:13
【问题描述】:

我在使用 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* 特化为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;
}

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

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

这是问题

当然,真正的问题是template &lt;class U&gt; matrix_t::operator*(U const&amp;) const太笼统了,但是:

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

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

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

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

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

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

    此外,没有任何地方提到标准成员函数优于非成员函数,反之亦然。

    标准是否涵盖了此类问题?

    是的。之所以选择全局运算符 (should be!) 是因为它是一个比在你的类中定义的函数模板更专业的模板:在模板参数推导之后,两者vectorN&lt;U, N&gt; const&amp;matrix_t&lt;T&gt; const&amp; 可以匹配 matrix_t&lt;T&gt; const&amp; 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&lt;T&gt; const&amp;, vectorN&lt;U, N&gt; const&amp;)的资格是什么?

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

    【讨论】:

    • 感谢您的回答,并指出 operator * 不是专业化的。我想所有这些东西也适用于 C++03。我同意这是苹果 llvm 编译器方面的一个错误,但我想问一下,以便知道我没有遗漏一些明显的东西。
    猜你喜欢
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2018-06-28
    • 2021-07-05
    • 2011-04-28
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多