uint8_t and int8_t conversion

Consider the following program:

using namespace std;

int main()
{
    uint8_t b = 150;
    int8_t a = -10;
    
    if (a>b){
        cout << "greater" << endl;
    }
    else{
        cout << "less" << endl; 
    }

    return 0;
}

In Online C++14 Compiler it prints less. The same result I get in Compiler Explorer with x86-64 gcc 12.2

According to the documentation

Otherwise, the operand has integer type (because bool, char, char8_t, (since C++20) char16_t, char32_t, (since C++11) wchar_t, and unscoped enumeration were promoted at this point) and integral conversions are applied to produce the common type, as follows:

  1. If both operands are signed or both are unsigned, the operand with lesser conversion rank is converted to the operand with the greater integer conversion rank.
  2. Otherwise, if the unsigned operand’s conversion rank is greater or equal to the conversion rank of the signed operand, the signed operand is converted to the unsigned operand’s type.
  3. Otherwise, if the signed operand’s type can represent all values of the unsigned operand, the unsigned operand is converted to the signed operand’s type.
  4. Otherwise, both operands are converted to the unsigned counterpart of the signed operand’s type.

In the Compiler Explorer with x86-64 gcc 12.2 I’ve compiled it and got the following result:

.LC0:
        .string "greater"
.LC1:
        .string "less"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     BYTE PTR [rbp-1], -106
        mov     BYTE PTR [rbp-2], -10
        movsx   edx, BYTE PTR [rbp-2]
        movzx   eax, BYTE PTR [rbp-1]
        cmp     edx, eax
        jle     .L2
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        jmp     .L3
...

So, accoring to

mov     BYTE PTR [rbp-1], -106
mov     BYTE PTR [rbp-2], -10

uint8_t b is converted to int8_t and is assigned a value of -106, which seems to be true, because it falls under the point 3. of the documentation:

Otherwise, if the signed operand’s type can represent all values of the unsigned operand, the unsigned operand is converted to the signed operand’s type.

If I understood the documentation and the assembly code right, the comparison

(a>b) //-10 > -106 

Should return true. But apparently, it returns false since I get the output of the else branch.

So my questions is: why is (a>b) returns false in this example?

>Solution :

You forgot about integral promotion. From https://en.cppreference.com/w/cpp/language/operator_arithmetic :

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.

From https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion :

prvalues of small integral types (such as char) may be converted to prvalues of larger integral types (such as int)

Both a and b are first promoted to int, and then they are compared. As an int.

Leave a Reply