Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading of template function in template class

I have a templated operator in templated class and I want to change its behavior for specific type. My code:

#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 compiles it fine but clang-3.6 complains that it's ambiguous.

Note, that if class is not templated, than both of compilers compile it fine.

What is correct behavior?

like image 934
RiaD Avatar asked Sep 27 '22 07:09

RiaD


1 Answers

Short summary: I believe this is a gcc bug in the template partial ordering rules, and that clang is correct. I filed bug 66914, although it's probably a duplicate of bug 53499 which I didn't notice until afterwards.


In the call

a << B::s;

We have two viable candidates:

template <typename T> A<int>::operator<<(const T& );
template <typename N> operator<<(A<N>&, const B& );

You can rewrite the member function to take a reference to the instance as the first argument, and write out both as instantiatons. So we have:

template <> operator<<(A<int>&, const B& ); // [T = B]
template <> operator<<(A<int>&, const B& ); // [N = int]

Since both are viable candidates, let's go through the rules in [over.match.best] to determine which one is the best viable candidate:

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

— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

Nope, both take the exact same arguments, so the conversion sequences are identical.

— the context is an initialization by user-defined conversion [ ... ]

Nope, irrelevant.

— the context is an initialization by conversion function [ ... ]

Nope, irrelevant.

— F1 is not a function template specialization and F2 is a function template specialization, or, if not that,

Nope, both are function template specializations.

— 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.

This is the most complicated of the rules. Ultimately, neither is more specialized than the other. Why? The member function is effectively:

template <typename T> A<int>& operator<<(A<int>&, const T& );

If we synthesized a type for T (call it Unique1), deduction would fail against the free function (since Unique1 wouldn't match B). On the other side, if we synthesized a type for N (call it Unique2), deduction would fail against the member function (since Unique2 wouldn't match int).

Since neither function is more specialized than the other, we've run out of bullet points. The function call should be ambiguous, this is a gcc bug.

like image 178
Barry Avatar answered Sep 30 '22 23:09

Barry