简体   繁体   中英

Template binary operator overload resolution

I encountered an issue using Apple LLVM compiler (shipped with XCode 4.5.2), while running correctly with GCC. More importantly than a debate on compiler issues (I think GCC is right though), this raises the question about the resolution order of template specialization when overloaded operators are concerned [1].

Consider a simple matrix class template <class T> class matrix_t , with a traits defining the result type of multiplication (with scalar, matrices or vectors). This would be something like the following code:

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>();
  }

};

Consider also a specialization of the operator* for the 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>();
}

and consider a simple test program:

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

The "problematic line" runs the globally defined operator*(matrix_t<T> const&, vectorN<U, N> const&) on GCC and template <class U> matrix_t<T>::operator*(U const&) const on LLVM. So it is like the matrix_t<T>::operator*(U const&) is catching all template specialization lookups. A simple "fix" would be to move the global operator* into the matrix class.

I first thought it was a problem in the traits class, that was maybe too complex or erroneous (SFINAE). But even simplifying the traits or completely disabling it (as in the paste code) produces the error. I then thought it was an order problem (like in Herb Shutter's article), but moving the global operator* between matrix_t declaration and definition does not change things.

Here is the question

Of course, the real problem is that template <class U> matrix_t::operator*(U const&) const is too general, but:

  • is this kind of problem something covered by the standard?
  • does an operator overload defined inside a class have priority over an operator overload defined globally?
  • (more like a vocabulary issue) what is the qualification of operator*(matrix_t<T> const&, vectorN<U, N> const&) ? template overload operator specialization? Is this more a template specialization or an overloaded function? What is the base definition for that? Since it is intrinsically an overloaded operator, I am a bit lost.

[1] I have read Herb Shutter advices on template specialization order.

does an operator overload defined inside a class have priority over an operator overload defined globally?

No . Per Paragraph 13.3.1/2 of the C++11 Standard:

The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list . So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called. For the purposes of overload resolution, both static and non-static member functions have an implicit object parameter, but constructors do not.

Moreover, nowhere in the Standard member functions are mentioned to be preferred to non-member functions, or vice-versa.

is this kind of problem something covered by the standard?

Yes. The reason why the global operator is ( should be! ) selected is that it is a more specialized template than the function template defined inside your class: after template argument deduction, both vectorN<U, N> const& and matrix_t<T> const& can be made to match matrix_t<T> const& r , but the former is more specialized than the latter and, therefore, it is preferred.

Per Paragraph 13.3.3/1:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then:

[...]

F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

Therefore:

So it is like the matrix_t::operator*(U const&) is catching all template specialization lookups

This behavior qualifies as a bug . However, note that these are not specializations , but overloads .

Finally:

what is the qualification of operator * (matrix_t<T> const&, vectorN<U, N> const&) ?

I guess you could say it is a (global) operator overload template. It is not a specialization, because template function specializations have a different syntax. Therefore, there is no primary template it is specializing. It is just a function template that, once instantiated, generates an overload of the multiplicative operator.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM