C++ Implicit Conversion (Signed + Unsigned)

20,206

Solution 1

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...

Solution 2

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

Solution 3

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.

Share:
20,206
2013Asker
Author by

2013Asker

Updated on March 28, 2020

Comments

  • 2013Asker
    2013Asker about 4 years

    I understand that, regarding implicit conversions, if we have an unsigned type operand and a signed type operand, and the type of the unsigned operand is the same as (or larger) than the type of the signed operand, the signed operand will be converted to unsigned.

    So:

    unsigned int u = 10;  
    signed int s = -8;
    
    std::cout << s + u << std::endl;
    
    //prints 2 because it will convert `s` to `unsigned int`, now `s` has the value
    //4294967288, then it will add `u` to it, which is an out-of-range value, so,
    //in my machine, `4294967298 % 4294967296 = 2`
    

    What I don't understand - I read that if the signed operand has a larger type than the unsigned operand:

    • if all values in the unsigned type fit in the larger type then the unsigned operand is converted to the signed type

    • if the values in the unsigned type don't fit in the larger type, then the signed operand will be converted to the unsigned type

    so in the following code:

    signed long long s = -8;
    unsigned int u = 10;
    std::cout << s + u << std::endl;
    

    u will be converted to signed long long because int values can fit in signed long long??

    If that's the case, in what scenario the smaller type values won't fit in the larger one?

  • chux - Reinstate Monica
    chux - Reinstate Monica almost 11 years
    +1 Nice complete answer. BTW: May wish to note this is for 4-byte ints and 8-byte long long. Others may be different.
  • sasha.sochka
    sasha.sochka almost 11 years
    But in the same time unsigned long cannot represent all the values of long int (-1 for example). Why, then, long int converts to unsigned int and not in the opposite way?
  • Vaughn Cato
    Vaughn Cato almost 11 years
    @sasha.sochka: Actually they are both converted to unsigned long int (I made a mistake initially).
  • TemplateRex
    TemplateRex almost 11 years
    @chux I have reduced my example to the 3 clauses in the Standard, and also indicated where the platform dependencies are.
  • TemplateRex
    TemplateRex almost 11 years
    @chux the platform I used (Coliru online compiler) is 64-bits where sizeof(long) == sizeof(long long). You probably use 32-bits, where sizeof(long) == sizeof(int), so the 2nd and 3rd cases are interchanged. The Standard is phrased in ranges that can be represented, which is why I used the phrase "on this platform" in my answer.
  • chux - Reinstate Monica
    chux - Reinstate Monica almost 11 years
    Maybe use types with more explicit relative ranks like short, long, long long and avoid size int to reduce ambiguity in your fine answer?
  • TemplateRex
    TemplateRex almost 11 years
    @chux apart from the relative ranking in terms of <= the Standard does not provide many absolute size guarantees for integral types. furthermore, for char and short there are also integral promotions to int, prior to the arithmetic conversions. I think it would only complicate the answer. The Standard reasoning + the code example should be clear enough to resolve every practical case.
  • chux - Reinstate Monica
    chux - Reinstate Monica almost 11 years
    OK - agree about the short issue. Thanks.