Template overload resolution for an operator, a member function, and a non-member function

When trying to execute clang-3.4 (compiled from git), he was unable to compile one of my projects complaining of ambiguity when resolving overloaded operators. I found out that there are two boilerplate operators, one of which was declared as a member function, and the other as a non-member operator, which both look equally good.

After SSCCE the situation is demonstrated:

#include <iostream> struct ostr { std::ostream& s; template<class T> ostr& operator<<(const T& x) { s << x; return *this; } }; struct xy { double x, y; }; template<class Stream> Stream& operator<<(Stream& s, const xy& x) { s << "[" << xx << ", " << xy << "]"; return s; } int main() { ostr os{std::cout}; xy x{4, 5}; os << "Value is: " << x <<"\n"; } 

The project was compiled earlier, and I checked this SSCCE again with several compilers ( gcc 4.5 , 4.6 , 4.7 , 4.8 and clang 3.3 ), and they all compiled it without warning (with -Wall -Wextra -pedantic ). All compilers were installed on the C ++ 11 / C ++ 0x standard. After adding ctor to ostr it compiled even on MSVC 2012 and 2010 )

Creating as operator<< non-member demonstrates ambiguity in all compilers (as expected)

After looking at standard drafts ( N3242 and N3690 ), I was unable to find anything that would improve the matching of member functions / operators than a non-member.

So I could not prove that clang-3.4 is wrong, and I wonder who is right. So my question is:

  • Is this code valid? Should operator / function members match better than non-members, and is this a bug in clang-3.4?
  • Or are all other compilers wrong / too solvable?

I know that changing the second operator<< to a non-templated function (with std::ostream instead of the template parameter) will resolve the ambiguity and will work as expected, but it is not.

+6
source share
1 answer

Overload resolution adds an additional parameter to a member function only for the purpose of overload resolution:

[over.match.funcs] / 2

A set of candidate functions can contain both member functions and non-member functions that must be allowed for the same list of arguments. Thus, the list of arguments and parameters is comparable in this heterogeneous set, it is believed that the member function has an additional parameter called the implicit parameter of the object, which represents the object for which the member function was called.

/4

For non-static member functions, the type of the implicit parameter of an object

- "lvalue reference to cv X " for functions declared without ref-qualifier or with & ref-qualifier

- "rvalue reference to cv X " for functions declared with && ref-qualifier

where X is the class of which the member is a member, and cv is the cv qualification in the declaration of the member function.

Some special rules are followed, for example, to allow the binding of the value of r to this implicit object (to call member functions without ref-qualifier on rvalues, for example ostr{std::cout}<<"hello" ).


Function signatures, including the implicit object parameter, which we need to compare to resolve overload:

 template<class T> ostr& ostr::operator<<(ostr&, const T&); // F1 template<class Stream> Stream& ::operator<<(Stream&, const xy&); // F2 

After substituting for os << x we get the same signature:

 ostr& ostr::operator<<(ostr&, const xy&); ostr& :: operator<<(ostr&, const xy&); 

Thus, only one of the β€œtiebreakers” in [over.match.best] / 1 can eliminate the ambiguity. Indeed, one could apply, namely: β€œ F1 more specialized than F2 ” (or vice versa): partial order of function templates.

NB The procedure for adding an implicit object parameter is again specified in the partial ordering description [temp.func.order] / 3.


To partially order F1 and F2 (as defined above), we first create two unique types:

 struct unique_T {}; struct unique_Stream {}; 

Then we convert F1 to F1' , replacing the template parameter T unique type unique_T (and similarly for F2 ):

 ostr& ostr::operator<<(ostr&, const unique_T&); ostr& :: operator<<(unique_Stream&, const xy&); 

The parameters of the transformed function F1' now used to try to derive the parameters of the non-transformed F2 template:

 ostr a0; unique_T a1; // no reference, no cv-qualifier ::operator<<(a0, a1) // does template argument deduction succeed? // reminder: signature of ::operator<< template<class Stream> Stream& ::operator<<(Stream&, const xy&); 

The output is performed for a0 [with Stream = ostr ], therefore the type ostr& from F1 is considered at least as specialized as the type of the corresponding first parameter F2 ( Stream& , where Stream is a template parameter). I'm not sure what happens with the second argument a1 , since deduction is not performed for the second parameter ::operator<< (it is of type const xy& ).

Now we repeat the process with the arguments from F2' and try to infer the parameters of the F1 template:

 unique_Stream a0; xy a1; ostr::operator<<(a0, a1); // reminder: signature of ostr::operator<< template<class T> ostr& ostr::operator<<(ostr&, const T&); 

There is no conclusion for the first argument, but it occurs and is executed for the second argument [with T = xy ].

I conclude that no function template is more specialized. Therefore, overload resolution should fail due to ambiguity.

+2
source

Source: https://habr.com/ru/post/957996/


All Articles