Let's start with ยง13.3.1.2 [over.match.oper] / p2-3:
If any operand has a type that is a class or enumeration, a user-defined operator function may be declared that implements this operator or a user-defined conversion may be necessary to convert the operand to a type that is suitable for the built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator to call the operator.
[...]
for the binary operator @ with the left operand of type cv-unqualified version T1 and the right operand of type cv-unqualified version T2 , three sets of candidate functions, assigned candidate candidates, non-member candidates, and built-in candidates are constructed as follows:
- If T1 is the full type of class or the class that is currently being defined, the set of candidate candidates is the result of a qualified search
T1:: operator@ (13.3.1.1.1); otherwise, the set of candidate candidates is empty. - The set of non-member candidates is the result of an unqualified search
operator@ in the context of an expression in accordance with the usual rules for finding names in unqualified function calls (3.4.2) except that all member functions are ignored. [...] - For the operator,, the unary operator
& or the operator -> , the built-in set of candidates is empty. For all other operators, the built-in candidates include all the functions of the candidate operator defined in 13.6, which, compared with this operator,- have the same operator name and
- accept the same number of operands and
- accept the types of operands to which a given operand or operands can be converted in accordance with 13.3.3.1 and
- do not have the same parameter list as any non-member candidate that is not a specialized function.
So, given the expression 2.1 + a , we construct sets of candidates:
T1 double , not a class type, so the set of member candidates is empty.A non-member recruitment consists of:
Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs);
(plus many other overloads for different instances of Foo , which are obviously worse than this one.)
The built-in candidate set consists of a long list of functions that you can see in the clang output on your code specified in ยง13.6 [over.built] / p12:
For each pair of advanced arithmetic types L and R there is a candidate operator operator of the form [...] LR operator+(L , R ); [...] where LR is the result of ordinary arithmetic conversions between types L and R
Overload resolution can only be successful if a unique best match can be found among this bunch of candidates.
First, note that out of the many possible built-in statements below, none of them can be the only best match, because Foo<int> converted to all possible types of the correct operand:
operator+(double, unsigned long long) operator+(double, unsigned long) operator+(double, unsigned int) operator+(double, __int128) operator+(double, long long) operator+(double, long) operator+(double, float) operator+(double, double) operator+(double, long double) operator+(double, int)
(I only listed those whose first argument is of type double , since the type of the first argument and, therefore, other built-in modules cannot be better than any of them.)
Thus, overload resolution can be successful if and only if your operator + overload is better than each one. Without loss of generality, we consider the following two functions:
operator+(double, int);
list of arguments given (double, Foo<int>) . For the first candidate, the first argument is an exact match, the second requires a custom conversion. For the second candidate, the first argument requires a custom conversion, and the second requires an exact match.
Thus, we have a cross-cutting situation, which means that not one of the candidates is better than the other. (The first requirement of one function F1 is better than another F2, is that for each argument the conversion required for F1 is no worse than F2 - ยง13.3.3 [over.match.best] / p2.)
As a result, there is no single best overload, the overload error failed, and the program is poorly formed. Clang correctly rejects this code, and g ++ does not work without abandoning it.