Integer conversions and promotions
The uint16_t
value goes through integer promotion whereas for (your particular platform; see below) the uint32_t
and uint64_t
cases the -1
value (not an integer literal, per se, but the unary minus operator applied to the integer literal 1
), goes through integer conversion with a resulting value that is equal to the maximum respective value of the uint32_t
and uint64_t
types, due to the integer congruence between the source and the destination values of this conversion.
static_assert(-1 == std::numeric_limits<std::uint64_t>::max());
// ^^
// | Integer conversion:
// | Destination type: uint64_t
// | Resulting value: std::numeric_limits<std::uint64_t>::max()
static_assert(-1 == std::numeric_limits<std::uint32_t>::max());
// ^^
// | Integer conversion:
// | Destination type: uint32_t
// | Resulting value: std::numeric_limits<std::uint32_t>::max()
static_assert(-1 == std::numeric_limits<std::uint16_t>::max());
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// | Integer promotion:
// | Destination type: int
// | Resulting value: std::numeric_limits<std::uint16_t>::max()
From [expr.eq]/1 and [expr.eq]/6 [emphasis mine]:
[expr.eq]/1
The ==
(equal to) and the !=
(not equal to) operators group
left-to-right. The operands shall have arithmetic, enumeration,
pointer, or pointer to member type, or type std::nullptr_t
. The
operators ==
and !=
both yield true
or false
, i.e., a result
of type bool
. In each case below, the operands shall have the same
type after the specified conversions have been applied.
[expr.eq]/6
If both operands are of arithmetic** or enumeration type, the usual arithmetic conversions are performed on both operands; each of
the operators shall yield true
if the specified relationship is
true
and false
if it is false
.
From [conv.integral]/1 and [conv.integral]/2:
[conv.integral]/1
A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type.
[conv.integral]/2
If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n
where n
is the number of bits used to represent the unsigned type). [ Note: In a two's complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). — end note ]
This alone should yield the same behaviour for all your three examples. However, what differs for the uint16_t
case is that [conv.integral]/5 applies:
[conv.integral]/5
The conversions allowed as integral promotions are excluded from the set of integral conversions.
From [conv.rank]/1
[conv.rank]/1
Every integer type has an integer conversion rank defined as follows:
[...]
(1.3) The rank of long long int
shall be greater than the rank of long
int
, which shall be greater than the rank of int
, which shall be
greater than the rank of short int
, which shall be greater than the
rank of signed char
.
(1.4) The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.
the integer conversion rank of uint16_t
(same rank or lower than short int
) is lower than that of int
, which means that [conv.prom]/1 applies for uint16_t
[emphasis mine]:
[conv.prom]/1
A prvalue of an integer type other than bool
, char16_t
, char32_t
, or wchar_t
whose integer conversion rank is less than the rank of int
can be converted to a prvalue of type int
if int
can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int
.
Platform dependent behaviour
However, whilst we were able to make an argument for uint16_t
above due to the lower bound requirement on the maximum value of an unsigned short int
—guaranteeing that uint16_t
always has a lower integer conversion rank than int
—we cannot make the converse argument for the fact that uint32_t
will never have lower integer conversion rank than int
, as the ISO C++ Standard places no upper bounds on the maximum value requirement on the fundamental integer types.
From [basic.fundamental]/2 and [basic.fundamental]/3 [extract, emphasis mine]:
[basic.fundamental]/2
There are five standard signed integer types : “signed char
”,
“short int
”, “int
”, “long int
”, and “long long int
”. In this
list, each type provides at least as much storage as those preceding
it in the list. [...] Plain int
s have the natural size suggested
by the architecture of the execution environment; the other signed
integer types are provided to meet special needs.
[basic.fundamental]/3
For each of the standard signed integer types, there exists a
corresponding (but different) standard unsigned integer type:
“unsigned char
”, “unsigned short int
”, “unsigned int
”,
“unsigned long int
”, and “unsigned long long int
”, each of which
occupies the same amount of storage and has the same alignment
requirements as the corresponding signed integer type; [...]
The signed and unsigned integer types shall satisfy the constraints
given in the C standard, section 5.2.4.2.1.
And, from the C11 Standard draft [extract, emphasis mine]:
5.2.4.2.1 Sizes of integer types <limits.h>
[...] Their implementation-defined values shall be equal or greater
in magnitude (absolute value) to those shown, with the same sign.
[...]
[...]
Note that these maximum values describes the lower bound on the maximum values that the respective fundamental integer type shall be able to store, whereas there is no requirement placed on the upper bound of these maximum values. Moreover, recall from the [basic.fundamental]/2 quote above that each subsequent fundamental (signed) integer type needs only provide at least as much storage as the one proceeding it (in the list).
This means that, in theory, a platform could implement short int
and int
as 32 bit wide and 64 bit wide integers, respectively, meaning that, on this platform, uint32_t
would have same integer conversion rank as (unsigned
) short int
, which would mean a lower conversion rank than int
, in which case [conv.prom]/1
would apply also for the uint32_t
example on this particular platform.