Consider the following code (https://godbolt.org/z/s17aoczj6):
template<class T>
class Wrapper {
public:
explicit Wrapper(T t): _value(t) {}
template<class S = T>
operator T() { return _value; }
private:
T _value;
};
auto main() -> int
{
auto i = int{0};
auto x = Wrapper<int>(i);
return x + i;
}
It compiles with clang, but not with gcc (all versions).
It works in gcc when removing the template<class S = T>
.
Is this code ill-formed or is one compiler wrong?
The error is in gcc is error: no match for 'operator+' (operand types are 'Wrapper<int>' and 'int') return x + i;
.
I want a conversion to T
. The template is not necessary in this example, but in a non-minimal example I would like to use SFINAE and therefore need a template here.
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 unaryoperator &
, or theoperator ->
, 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.
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