#include <iostream>
#include <cstdint>
using namespace std;
static_assert(-1 == numeric_limits<uint64_t>::max()); // ok
static_assert(-1 == numeric_limits<uint32_t>::max()); // ok
static_assert(-1 == numeric_limits<uint16_t>::max()); // error
int main()
{
cout << numeric_limits<uint16_t>::max() << endl;
cout << uint16_t(-1) << endl;
}
output:
65535
65535
Why is numeric_limits<uint16_t>::max()
not equal to -1?
Update:
According to cppref:
Similarly USHRT_MAX may not be of an unsigned type: its type may be int.
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 typestd::nullptr_t
. The operators==
and!=
both yieldtrue
orfalse
, i.e., a result of typebool
. 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 istrue
andfalse
if it isfalse
.
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
wheren
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 oflong int
, which shall be greater than the rank ofint
, which shall be greater than the rank ofshort int
, which shall be greater than the rank ofsigned 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
, orwchar_t
whose integer conversion rank is less than the rank ofint
can be converted to a prvalue of typeint
ifint
can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of typeunsigned int
.
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. [...] Plainint
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.
[...]
maximum value for an object of type
short int
:SHRT_MAX +32767
maximum value for an object of type
int
:INT_MAX +32767
[...]
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.