Search code examples
c++unsignedinteger-promotion

Do unsigned integers get promoted to signed? What is the type of expression (uint16_t)-1 * (uint16_t)-1


For uint32_t and uint64_t results are expected, but promotions for uint8_t and uint16_t are strange. Tests on c++14/c++17, gcc and clang, 64-bit linux, sizeof(int) == 4.

#include <cstdint>    
using namespace std;

static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // OK, but why not 1?
static_assert( uint8_t(-1)*uint8_t(-1) == 1);       // error: static assertion failed
static_assert( uint16_t(-1)*uint16_t(-1) == 1);     // error: static_assert expression is not an integral constant expression
static_assert( uint32_t(-1)*uint32_t(-1) == 1);     // OK
static_assert( uint64_t(-1)*uint64_t(-1) == 1);     // OK

Is in the following case std::uint16_t promoted to int?

static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

The compiler messages are:

error: static_assert expression is not an integral constant expression
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

note: value 4294836225 is outside the range of representable values of type 'int'
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

More interestingly the same assert for std::uint8_t is correct, but fails:

static_assert( uint8_t(-1)*uint8_t(-1) == uint8_t(uint8_t(-1)*uint8_t(-1)));  //error: static_assert failed
static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // does not fail

So it looks like uint8_t is promoted to uint16_t, but uint16_t is promoted to signed int. uint32_t is not promoted uint64_t.

Anyone can tell me why these promotions are done, is it specified in standard, or can differ on different implementations?


Solution

  • it looks like uint8_t is promoted to uint16_t, but uint16_t is promoted to signed int. uint32_t is not promoted uint64_t.

    No, that's not correct. Only types narrower than int are promoted to int, so if int has more than 16 bits then both uint8_t and uint16_t will be promoted to int.

    In the case of uint8_t(-1)*uint8_t(-1), it's equivalent to 0xFF*0xFF and the result will be 0xFE01

    OTOH uint16_t(-1)*uint16_t(-1) is the same as 0xFFFF*0xFFFF which results in 0xFFFE0001 = 4294836225 and is outside int's range. And signed overflow is undefined behavior