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&);
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.