The example would be unambiguous in C++17. C++20 brings change:
[over.match.oper]
For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:
- ...
- For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in [over.built] that, compared to the given operator,
- have the same operator name, and
- accept the same number of operands, and
- accept operand types to which the given operand or operands can be converted according to [over.best.ics], and
- do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.
The rewritten candidate set is determined as follows:
- ...
- For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
Thus, the rewritten candidate set includes these:
implicit object parameter
|||
(S&&, const S&); // 1
(const S&&, const S&); // 2
// candidates that match with reversed arguments
(const S&, S&&); // 1 reversed
(const S&, const S&&); // 2 reversed
The overload 1 is better match than 2, but the synthesised reversed overload of 1 is ambiguous with the original non-reversed overload because both have const conversion to one parameter. Note that this is actually ambiguous even if overload 2 doesn't exist.
Thus, Clang is correct.
This is also covered by the informative compatibility annex:
Affected subclause: [over.match.oper] Change: Equality and inequality expressions can now find reversed and rewritten candidates.
Rationale: Improve consistency of equality with three-way comparison and make it easier to write the full complement of equality
operations.
Effect on original feature: Equality and inequality expressions between two objects of different types, where one is convertible to
the other, could invoke a different operator. Equality and inequality
expressions between two objects of the same type could become
ambiguous.
struct A {
operator int() const;
};
bool operator==(A, int); // #1
// #2 is built-in candidate: bool operator==(int, int);
// #3 is built-in candidate: bool operator!=(int, int);
int check(A x, A y) {
return (x == y) + // ill-formed; previously well-formed
(10 == x) + // calls #1, previously selected #2
(10 != x); // calls #1, previously selected #3
}
(S&&, S&&)
are(S&&, const S&&)
,(const S&&, const S&&)
,(const S&&, S&&)/*rewritten*/
. eachS&&
is better match thanconst S&&
, so call should be ambiguous IMO. – OrganicS{}
is not const, but the expressionS{} == S{}
will need to choose between the best match, argument per argument, sayarg1
andarg2
, among four synthesized overloads, and forarg1
(S&&, const S&&)
will be ambigious to(S&&, S&&)
(and conversely forarg2
). If we compare to when using function call notation (which Clang accepts),S{}.operator(S{})
, the overload set will be(const S&&, const S&)
and(S&&, const S&)
, for which the latter is unambigiously a best match. – Sarnoff