The rules for arithmetic operators are actually slightly different than the rules for general function overload resolution.
From cppreference on arithmetic operators:
Conversions
If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes integral promotion. If an operand has array or function type, array-to-pointer and function-to-pointer conversions are applied.
For the binary operators (except shifts), if the promoted operands have different types, additional set of implicit conversions is applied, known as usual arithmetic conversions with the goal to produce the common type (also accessible via the std::common_type
type trait). If, prior to any integral promotion, one operand is of enumeration type and the other operand is of a floating-point type or a different enumeration type, this behavior is deprecated. (since C++20)
- If either operand has scoped enumeration type, no conversion is performed: the other operand and the return type must have the same type
- Otherwise, if either operand is
long double
, the other operand is converted to long double
- Otherwise, if either operand is
double
, the other operand is converted to double
- Otherwise, if either operand is
float
, the other operand is converted to float
[snip]
So, quite explicitly, when we're doing unsigned + float
, the unsigned
gets converted to a float
. It's the first rule that applies, so we follow it.
However, for general overload resolution, the conversion from unsigned
to float
is equivalent to the conversion from float
to unsigned
. So for instance in the following code snippet:
unsigned add(unsigned l, unsigned r) { return l + r; }
float add(float l, float r) { return l + r; }
int main()
{
unsigned ui = 1;
float fval = 2.5;
add(ui, fval);
}
It's unable to decide which version of add
to use and fails to compile.