When trying out clang-3.4 (compiled from git), it failed to compile one of my projects complaining about ambiguity while resolving overloaded operators. I turned out that there were two templated operators, one of which was declared as a member function, other as a non-member one, which both seems equally good match.
Following SSCCE demonstrates the situation:
#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 << "[" << x.x << ", " << x.y << "]";
return s;
}
int main() {
ostr os{std::cout};
xy x{4, 5};
os << "Value is: " << x <<"\n";
}
The project compiled fine before and I checked again this SSCCE with several compilers (gcc 4.5
, 4.6
, 4.7
, 4.8
and clang 3.3
) and all of them compiled it without any warning (with -Wall -Wextra -pedantic
). All compilers were set to C++11/C++0x standard.
After adding ctor to ostr
, it compiled fine even on MSVC 2012
and 2010
)
Making both operator<<
s non-member exhibits the ambiguity in all compilers (as expected)
After looking through the standard drafts (N3242
and N3690
) I failed to find anything making member functions/operators better match than non-member ones.
So I failed to prove clang-3.4
is wrong and I wonder who's right.
Thus my question is:
I am aware that changing the second operator<<
to non-templated function (with std::ostream
instead of template parameter) would resolve the the ambiguity and work as expected, but that's not the point here.
Not everything can be overloaded as a member function Because the overloaded operator must be added as a member of the left operand. In this case, the left operand is an object of type std::ostream. std::ostream is fixed as part of the standard library.
Member function declarations with the same parameters or the same name types cannot be overloaded if any one of them is declared as a static member function.
The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.
The term member template refers to both member function templates and nested class templates. Member function templates are function templates that are members of a class or class template.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With