When you have x + i
, since x
is of class type, overload resolution kicks in:
The specific details from the standard ([over.match.oper]p2)
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.
The built-in candidates are defined in paragraph 3.3:
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 built-in candidate functions could consist of, according to [over.built]p13:
For every pair of types L and R, where each of L and R is a floating-point or promoted integral type, there exist candidate operator functions of the form
LR operator*(L, R);
...
LR operator+(L, R);
...
bool operator>=(L, R);
where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R.
So there is a built-in function int operator+(int, int)
.
As to what possible implicit conversion sequences there are:
[over.best.ics]p3:
A well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence,
- a user-defined conversion sequence, or
- an ellipsis conversion sequence.
And here a user-defined conversion sequence is used, defined by [over.ics.user]:
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion ([class.conv]) followed by a second standard conversion sequence.
(Here, both standard conversion sequences are empty, and your user defined conversion to an int
can be used)
So when checking if int operator+(int, int)
is a built-in candidate, it is since there exists a conversion between your class type and int
.
As for the actual overload resolution, from [over.match.oper]:
- The set of candidate functions for overload resolution for some operator @ is the union of the member candidates, the non-member candidates, the built-in candidates, and the rewritten candidates for that operator @.
- The argument list contains all of the operands of the operator.
The best function from the set of candidate functions is selected according to [over.match.viable] and [over.match.best].
And int operator+(int, int)
is obviously the best match, since it requires no conversion for the second argument and only a user defined conversion for the first, so it beats other candidates like long operator+(long, int)
and long operator+(int, long)
You can see the problem that the built-in candidate set is empty, since the GCC error reports that there are no viable candidates. If you instead had:
auto add(int a, int b) -> int
{
return a + b;
}
auto main() -> int
{
auto i = int{0};
auto x = Wrapper<int>(i);
return add(x, i);
}
it now compiles fine with GCC since ::add(int, int)
is considered a candidate, even though it should be no different from the built-in operator int operator+(int, int)
.
If you instead had:
template<class S = T>
operator S() { return _value; } // Can convert to any type
Clang now has the error:
<source>:16:14: error: use of overloaded operator '+' is ambiguous (with operand types 'Wrapper<int>' and 'int')
return x + i;
~ ^ ~
<source>:16:14: note: built-in candidate operator+(float, int)
<source>:16:14: note: built-in candidate operator+(double, int)
<source>:16:14: note: built-in candidate operator+(long double, int)
<source>:16:14: note: built-in candidate operator+(__float128, int)
<source>:16:14: note: built-in candidate operator+(int, int)
<source>:16:14: note: built-in candidate operator+(long, int)
<source>:16:14: note: built-in candidate operator+(long long, int)
<source>:16:14: note: built-in candidate operator+(__int128, int)
<source>:16:14: note: built-in candidate operator+(unsigned int, int)
<source>:16:14: note: built-in candidate operator+(unsigned long, int)
<source>:16:14: note: built-in candidate operator+(unsigned long long, int)
<source>:16:14: note: built-in candidate operator+(unsigned __int128, int)
(Note this error message excludes conversions of the second argument, but since these will never be chosen, they are probably not considered as an optimisation)
And GCC still says there are no candidates at all, even though all of these built-in candidates exist.
language-lawyer
tag. I didn't do it, because it slightly changes the scope of the question. – Waterborneoperator S()
, notoperator T()
. Since it would be not possible to deduceS
for a compiler, it might be the reason for the error. Please, provide the error text – Phalangealx+i
operation. – Undersecretarytemplate<class S = T> operator T()
, does it matter that the conversion is a template at all? Does it work without the template? In any case, when the template param isS
, it should really beoperator S()
, too. Also, consider making itconst
and not usingALL_UPPERCASE
identifiers that could be confused with macros. – Mignonmignonetteoperator T
. What do you mean byALL_UPPERCASE
here? – Undersecretarytypename S
is not possible to be deduced from the context - in your case it can only be deduced toT
. So what kind of SFINAE do you expect here, ifS
always defaults toT
and can never ever be deduced or substituted to something else? – PhalangealWrapper<int>::operator T<double>()
, by the way? – Rathe