Overload resolution adds an additional parameter to a member function just for the purpose of overload resolution:
[over.match.funcs]/2
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.
/4
For non-static member functions, the type of the implicit object parameter is
— “lvalue reference to cv
X
” for functions declared without a ref-qualifier or with the&
ref-qualifier— “rvalue reference to cv
X
” for functions declared with the&&
ref-qualifierwhere
X
is the class of which the function is a member and cv is the cv-qualification on the member function declaration.
Some special rules follow, for example to allow binding an rvalue to this implicit object parameter (for calling member functions w/o ref-qualifier on rvalues, e.g. ostr{std::cout}<<"hello"
).
The function signatures including the implicit object parameter we need to compare for overload resolution are:
template<class T>
ostr& ostr::operator<<(ostr&, const T&); // F1
template<class Stream>
Stream& ::operator<<(Stream&, const xy&); // F2
After substitution for os << x
, we get the same signature:
ostr& ostr::operator<<(ostr&, const xy&);
ostr& :: operator<<(ostr&, const xy&);
So only one of the "tie-breakers" in [over.match.best]/1 could resolve the ambiguity. Indeed, one could apply, namely the "F1
is more specialized than F2
" (or vice versa): partial ordering of function templates.
N.B. The procedure of adding an implicit object parameter is specified again in the description of partial ordering [temp.func.order]/3.
For partial ordering of F1
and F2
(as defined above), we first create two unique types:
struct unique_T {};
struct unique_Stream {};
Then we transform F1
into F1'
by replacing the template parameter T
with the 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'
are now used to try to deduce the template parameters of the untransformed F2
:
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 deduction succeeds for a0
[with Stream
= ostr
], therefore the type ostr&
from F1
is considered to be at least as specialized as type of the corresponding first parameter of F2
(Stream&
, with Stream
being a template parameter). I'm not sure what happens to the second argument a1
, since no deduction takes place for the second parameter of ::operator<<
(it is of type const xy&
).
Now we repeat the process with arguments from F2'
and try to deduce the template parameters of F1
:
unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);
// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);
Here, no deduction occurs for the first argument, but it occurs and succeeds for the second argument [with T
= xy
].
I conclude no function template is more specialized. Therefore overload resolution should fail due to ambiguity.