Question about Bitwise Shift in Microsoft C++ [duplicate]

I am doing the following bitwise shift in Microsoft C++:

uint8_t arr[3] = {255, 255, 255};
uint8_t value = (arr[1] << 4) >> 4;

The result of these operations confused me quite a bit:

value = 255

However, if I do the bitwise shift separately:

value = (arr[i] << 4);
value = value >> 4;

the answer is different and makes much sense:

value = 15

Can someone explain to me why this happens? I am familiar with the concepts of bitwise shift, or so I believed...

Thanks in advance!

(P.S.: It seems g++ will have the same behavior. I am probably missing some important concepts with bitwise shift. Any help is greatly appreciated!)


Solution 1:

In this expression with shift operators

(arr[1] << 4) >> 4;

there is used the integral promotions. That is the operand arr[1] is promoted to an object of the type int and such an object can store the result of the expression arr[i] << 4.

From the C++ 14 Standard (5.8 Shift operators, p.#1)

...The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

Here is a demonstration program

#include <iostream>
#include <iomanip>
#include <type_traits>
#include <cstdint>

int main()
{
    uint8_t x = 255;

    std::cout << "std::is_same_v<decltype( x << 4 ), int> is "
              << std::boolalpha
              << std::is_same_v<decltype( x << 4 ), int> << '\n';

    std::cout << "x << 4 = " << ( x << 4 ) << '\n';
}

The program output is

std::is_same_v<decltype( x << 4 ), int> is true
x << 4 = 4080

As for this code snippet

value = (arr[i] << 4);
value = value >> 4;

then in the first assignment statement the result of the shift operation is truncated.

Solution 2:

Expression (arr[1] << 4) will implicitly promote the value of arr[1] to type unsigned int before applying the shift operation, such that the "intermediate" result will not "loose" any bits (cf, for example, the explanation in implicit conversions).

However, when you write value = (arr[i] << 4);, then this "intermediate" result will be converted back to uint_8, and in this step bits get cut off.

See the difference when you write uint8_t value = ((uint8_t)(arr[1] << 4)) >> 4;