C++ Implicit Conversion (Signed + Unsigned)

signed int does not fit into unsigned long long. So you will have this conversion: signed int -> unsigned long long.


Note that the C++11 standard doesn't talk about the larger or smaller types here, it talks about types with lower or higher rank.

Consider the case of long int and unsigned int where both are 32-bit. The long int has a larger rank than the unsigned int, but since long int and unsigned int are both 32-bit, long int can't represent all the values of unsigned int.

Therefore we fall into to the last case (C++11: 5.6p9):

  • Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

This means that both the long int and the unsigned int will be converted to unsigned long int.


Relevant quote from the Standard:

5 Expressions [expr]

10 Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

[2 clauses about equal types or types of equal sign omitted]

— Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

— Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.

— Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

Let's consider the following 3 example cases for each of the 3 above clauses on a system where sizeof(int) < sizeof(long) == sizeof(long long) (easily adaptable to other cases)

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;

signed long int s2 = -4;
unsigned int u2 = 2;

signed long long int s3 = -4;
unsigned long int u3 = 2;

int main()
{
    std::cout << (s1 + u1) << "\n"; // 4294967294
    std::cout << (s2 + u2) << "\n"; // -2 
    std::cout << (s3 + u3) << "\n"; // 18446744073709551614  
}

Live example with output.

First clause: types of equal rank, so the signed int operand is converted to unsigned int. This entails a value-transformation which (using two's complement) gives te printed value.

Second clause: signed type has higher rank, and (on this platform!) can represent all values of the unsigned type, so unsigned operand is converted to signed type, and you get -2

Third clause: signed type again has higher rank, but (on this platform!) cannot represent all values of the unsigned type, so both operands are converted to unsigned long long, and after the value-transformation on the signed operand, you get the printed value.

Note that when the unsigned operand would be large enough (e.g. 6 in these examples), then the end result would give 2 for all 3 examples because of unsigned integer overflow.

(Added) Note that you get even more unexpected results when you do comparisons on these types. Lets consider the above example 1 with <:

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;
int main()
{
    std::cout << (s1 < u1 ? "s1 < u1" : "s1 !< u1") << "\n";  // "s1 !< u1"
    std::cout << (-4 < 2u ? "-4 < 2u" : "-4 !< 2u") << "\n";  // "-4 !< 2u"
}

Since 2u is made unsigned explicitly by the u suffix the same rules apply. And the result is probably not what you expect when comparing -4 < 2 when writing in C++ -4 < 2u...