c++unsignedunsigned-integerinteger-promotion

Why does "left shift count >= width of type" seemingly kick in late?


For context, I on a 64-bit (AMD64) little-endian system and am using g++ -std=gnu++23 -O3 -Wall to compile my code, if it makes any difference.

The following code snippet all shows some seemingly pathological behaviour: the warning "left shift count >= width of type" kicks in later than I would expect (partially explained here), but does not seem to do full integer promotion as I would expect either.

#include <cstdint>
#include <iostream>

int main()
{
  // any value will cause the confusing behaviour, of course
  uint8_t a = 5;
  a <<= 7;
  // 128, as expected
  std::cout << static_cast<int>(a) << '\n';

  uint8_t b = 5;
  b <<= 8;
  // 0, as expected, if slightly dubious (8 = width of type, right?)
  std::cout << static_cast<int>(b) << '\n';

  uint8_t c = 5;
  c <<= 16;
  // 0, as expected, assuming a promotion to a larger unsigned int type
  std::cout << static_cast<int>(c) << '\n';

  uint8_t d = 5;
  d <<= 31;
  // 0, as expected, assuming a promotion to a larger unsigned int type
  std::cout << static_cast<int>(d) << '\n';

  uint8_t e = 5;
  e <<= 64;
  /*
    raises a warning, rather understandably
    (I suppose it isn't aware of __uint128_t?)
  */
  // still outputs 0, although I assume this is undefined behaviour
  std::cout << static_cast<int>(e) << '\n';

  uint8_t f = 5;
  f <<= 32;
  /*
    raises a warning for some reason
    (shouldn't it just promote to uint64_t?)
  */
  // still outputs 0, although I assume this is undefined behaviour
  std::cout << static_cast<int>(f) << '\n';
}

What is going on?


Solution

  • Why does "left shift count >= width of type" seemingly kick in late?

    It does not "kick in late". It kicks in exactly when it intends to.

    I suppose it isn't aware of __uint128_t?

    It is aware of __uint128_t. Integer promotion does not promote to __int128_t.

    shouldn't it just promote to uint64_t?

    No.

    The rules of integral promotions are https://en.cppreference.com/w/cpp/language/implicit_conversion#Promotion_from_integral_types .

    [...] val can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type:

        int
        unsigned int
        long
        unsigned long 
        long long
        unsigned long long
        the underlying type of T
    

    Assuming normal system, uint8_t is unsigned char and int has 32 bits. uint8_t is promoted to int, as int can represent all values. And int on your system has width 32 bits, thus the warning on 32 and 64. It is not promoted to uint64_t, but to int.