I was testing the behaviour of the big three (GCC, clang, MSVC). I'm not sure what is standard-compliant.
Consider this code:
// p43.cxx
#include <type_traits>
constinit auto n1 = 9223372036854775807; // 2^63 - 1
static_assert(std::is_same_v<decltype(n1), signed long long int>);
constinit auto n2 = 9223372036854775808; // 2^63
constinit auto n3 = 18446744073709551615; // 2^64 - 1
#if defined _MSC_VER || defined __clang__
static_assert(std::is_same_v<decltype(n2), unsigned long long int>);
static_assert(std::is_same_v<decltype(n3), unsigned long long int>);
#elif defined __GNUC__
static_assert(std::is_same_v<decltype(n2), __int128>);
static_assert(std::is_same_v<decltype(n3), __int128>);
#endif
#if defined __GNUC__ && !defined __clang__
constinit auto n4 = 18446744073709551616; // 2^64
static_assert(std::is_same_v<decltype(n4), int>);
#endif
Syntax checking passes with no errors:
g++ -std=c++2c p43.cxx -Wall -Wextra -Wpedantic -fsyntax-only
clang++ -std=c++2c p43.cxx -Wall -Wextra -Wpedantic -fsyntax-only
cl /nologo /std:c++latest /nologo /Wall /wd4577 p43.cxx /Zs
(for cl
, C4577 is disabled for clarity)
Seeing that I've only used decimal literals, if I've understood the wording in the current draft correctly, [lex.icon]/3 says that one of int
, long int
or long long int
should be picked. /4 allows using an extended integer type if those cannot be used, which appear to be distinct from the standard ones as per [basic.fundamental]/1,2,5.
All compilers agree that n1
is signed long long int
.
For n2
and n3
, MSVC emits no warnings, clang emits this warning:
warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned`
and GCC emits the same kind of warning as clang, but chooses the (signed) extended type.
Defining n4
only works with GCC, but the type isn't even the extended one, rather int
.
MSVC and clang would give an error about it being too big.
Shouldn't 9223372036854775808
and 18446744073709551615
be ill-formed if signed __int128
is not supported?
GCC evidently supports __int128
by default, but 18446744073709551616
is made into an int
, even though it would fit in signed __int128
. Why is that so?
Edit (based on comment): I didn't specify my platform. PC Windows x86-64 LLP64
Since these are decimal integer literals without a suffix, the type is the first of int, long, and long long which can represent the value. See [lex.icon]:
The type of an integer-literal is the first type in the list in Table 8 corresponding to its optional integer-suffix in which its value can be represented.
There is an additional allowance for extended integer types:
Except for integer-literals containing a size-suffix, if the value of an integer-literal cannot be represented by any type in its list and an extended integer type ([basic.fundamental]) can represent its value, it may have that extended integer type.
And finally, if none of the options are viable:
If an integer-literal cannot be represented by any of the allowed types, the program is ill-formed.
Neither GCC nor Clang support extended integer types. I believe the same is true of MSVC.
I'll repeat since it can be confusing: on GCC __int128
is not an extended integer type.
Therefore, on a platform where long
is 32-bit and long long
is 64-bit, we are guaranteed that:
constinit auto n1 = 9223372036854775807; // 2^63 - 1
n1
has type long long
.
In contrast:
constinit auto n2 = 9223372036854775808; // 2^63
constinit auto n3 = 18446744073709551615; // 2^64 - 1
These are necessarily ill-formed. And if you ask GCC to issue errors when it can for ill-formed programs, it does so here: https://godbolt.org/z/dWhTY1M8q. Otherwise, treating an ill-formed program as well-formed with a certain meaning is an extension.