Let's start from §13.3.1.2 [over.match.oper]/p2-3:
If either operand has a type that is a class or an enumeration, a
user-defined operator function might be declared that implements this
operator or a user-defined conversion can be necessary to convert the
operand to a type that is appropriate for a built-in operator. In this
case, overload resolution is used to determine which operator function
or built-in operator is to be invoked to implement the operator.
[...]
for a binary operator @
with a left operand of a type whose
cv-unqualified version is T1
and a right operand of a type whose
cv-unqualified version is T2
, three sets of candidate functions,
designated member candidates, nonmember candidates and built-in
candidates, are constructed as follows:
- If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup of
T1::operator@
(13.3.1.1.1); otherwise, the set of member candidates is
empty.
- The set of non-member candidates is the result of the unqualified lookup of
operator@
in the context of the expression according to the
usual rules for name lookup 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 candidates set is empty. For all other operators, the
built-in candidates include all of the candidate operator functions
defined in 13.6 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
13.3.3.1, and
- do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.
So, given the expression 2.1 + a
, let's construct the candidate sets:
T1
is double
, not a class type, so the set of member candidates is empty.
The non-member candidate set consists of:
Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs);
(plus lots of other overloads for different instantiations of Foo
that's obviously a worse match than this one.)
The built-in candidate set consists of a long list of functions you can see in clang's output on your code, specified in §13.6 [over.built]/p12:
For every pair of promoted arithmetic types L
and R
, there exist
candidate operator functions of the form [...] LR operator+(L , R );
[...] where LR
is the result of the usual arithmetic conversions
between types L
and R
.
Overload resolution can succeed only if a unique best match can be found among this pile of candidates.
First, note that of the numerous possible built-in operators below, none can possibly be the unique best match, because Foo<int>
is convertible to every possible type of the right 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 ones whose first argument is of type double
, since that's the type of the first argument, and hence the other built-ins can't possibly be better than any of those.)
Thus, overload resolution can succeed if and only if your overload of operator +
is a better match than every one of them. Without loss of generality, we consider the following two functions:
operator+(double, int); // built-in
Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs); // overload
given an argument list of (double, Foo<int>)
. For the first candidate, the first argument is an exact match, the second one requires a user-defined conversion. For the second candidate, the first argument requires a user-defined conversion, and the second one is an exact match.
We thus have a criss-cross situation, which means that neither candidate function is better than the other. (The first requirement of one function F1 being better than the other F2 is that for each argument the conversion required for F1 is not worse than F2 - §13.3.3 [over.match.best]/p2.)
As a result, there's no unique best overload, overload resolution fails, and the program is ill-formed. Clang is correct in rejecting this code, and g++ is broken in failing to reject it.