How well is
__int128 supported, and what are its
properties? A Godbolt-based survey of the Big Four compilers.
Spoiler alert: libc++ thinks it’s integral, libstdc++ thinks it’s integral
-std=gnu++XX mode(!), and MSVC thinks it doesn’t exist at all.
Many thanks to Rein Halbersma for alerting me to libstdc++’s different behavior
-std=gnu++XX mode versus
-std=c++XX mode. (UPDATE, 2021-07-16: And
to Jonathan Wakely for alerting me to libstdc++ 10.3’s new behavior!)
using I128 = __int128;
GCC, Clang, and Intel ICC all support a built-in
Microsoft MSVC does not support any 128-bit integral type as far as I’m aware.
On GCC, Clang, and ICC,
__int128 is a token similar to
int: you can modify
unsigned to produce
unsigned __int128. However, all three front-ends
also support the built-in synonyms
static_assert(std::is_same_v<__int128_t, __int128>); static_assert(std::is_same_v<__uint128_t, unsigned __int128>); // PASSES on all three compilers
__uint128_t are safe to use in non-type template parameters
and constant expressions, on all compilers that support them.
The story on libstdc++ is complicated. Libstdc++ doesn’t do anything special for
__int128, but it does provide macro hooks
so that for example
__GLIBCXX_TYPE_INT_N_0 will be treated as an integral type, whatever that macro
expands to. (If it doesn’t expand to anything, it’ll be ignored.)
If you compile with GCC in
-std=c++XX mode (for any
__GLIBCXX_TYPE_INT_N_0 won’t be defined and so libstdc++ will consider
__int128 a non-integral type.
But if you compile with GCC in
-std=gnu++XX mode (for any
XX), then the compiler
will pre-define these two macros:
#define __GLIBCXX_BITSIZE_INT_N_0 128 #define __GLIBCXX_TYPE_INT_N_0 __int128
As of July 2016-ish, Clang also
defines these two macros in
You might wonder if these macros have any relationship with
_GNU_SOURCE: They do not. Both Clang and GCC unconditionally define
_GNU_SOURCEin all modes, anyway. If you’re manually passing
CFLAGS, like a lot of projects I’ve seen, well, you can stop doing that.
By the way, you can use the options
-dM -E to view all your compiler’s pre-defined macros:
clang++ -std=c++17 -dM -E test.cpp
The upshot is, if you compile in
-std=gnu++XX mode, libstdc++ will treat
unsigned __int128 as integral types with the proper numeric limits; but if you
compile in the usual
-std=c++XX mode, libstdc++ will not do any of that.
On the other hand, libc++ treats
__int128 as an integer type, full stop.
Relationship to integer types and
static_assert(std::is_integral_v<__int128>); // TRUE on libc++, "it depends" on libstdc++
libstdc++ (in standard, non-
gnu++XX mode) leaves
This makes a certain amount of sense from the library implementor’s point of view,
__int128 is not one of the standard integral types, and furthermore,
if you call it integral, then you have to face the consequence that
(which is 64 bits on every ABI that matters) is kind of lying about being the
-std=gnu++XX mode, libstdc++ makes
is_integral_v<__int128> come out to
as described in the previous section on sneaky macros.
Meanwhile, libc++ simply
Yes, this means that on libc++ (or on libstdc++ with
-std=gnu++17), there exist
integral values that do not fit in
In case you’re wondering: libstdc++ in standard C++17 mode considers
__int128a “compound” type. The definition of “compound type” is simply “anything that’s not a fundamental type,” where “fundamental” encompasses the arithmetic types (both integral and floating-point),
nullptr_t. So for example
int*is also a compound type.
Prior to the GCC 10.3 release, libstdc++ specialized
numeric_limits<__int128> only in non-
This had the unfortunate effect of
constexpr __int128 int128_max = std::numeric_limits<__int128>::max(); static_assert(int128_max == 0); // TRUE on libstdc++ in std mode before 10.3
However, since GCC 10.3
(specifically this August 2020 commit),
libstdc++ has provided the appropriate specialization in all modes, just like libc++, so that
have the appropriate values instead of zero.
true whenever the specialization is
provided at all — i.e., unconditionally on libc++ and libstdc++ 10.3+, and on older libstdc++
-std=gnu++XX mode. So:
static_assert(std::numeric_limits<__int128>::is_integer); // TRUE everywhere since August 2020 static_assert(std::is_integral_v<__int128>); // still FALSE on libstdc++ in std mode
libc++ reports that
libstdc++ in standard mode of course denies both, because only arithmetic types can be
signed or unsigned, and libstdc++ denies that
__int128 is arithmetic.
make_signed, libc++ (and libstdc++ in
gnu++XX mode) reports what you’d expect —
Trying to instantiate
make_signed<__uint128_t> on libstdc++ in standard mode
results in a hard error which is not SFINAE-friendly.
(It’s undefined behavior to instantiate
make_signed of any type which is neither
integral nor an enumeration type.)
Incidentally, today I learned that
is_signed_v<float> == true but
is a hard error.
UPDATE, 2021-07-22: Jonathan Wakely alerted me to some new wrinkles in C++20. C++20 gives us
at least two relevant library concepts:
std::integral is true of integral types, i.e.,
std::integral<T> is true iff
std::is_integral_v<T> is true. On libc++ (or libstdc++ in
they’re both true; on libstdc++ in
-std=c++XX mode they’re both false.
std::weakly_incrementable, which is modeled by
and also by pointer-like, iterator-like types. It is significant because if a type does not
std::weakly_incrementable, then (by definition) it is not integer-like, and
that means that it can’t be used as any iterator’s
difference_type. Since we certainly
want to be able to use
__int128 as the
difference_type of, say,
it’s important that
__int128 be considered “integer-like.” Therefore,
-std=c++XX mode specializes
On libc++, and libstdc++ in
__int128 is automatically
by virtue of being
std::integral; it doesn’t need those additional specializations.
std::integral does not subsume
that’s another story.)
For more on
__uint128_t arithmetic, see
“The abstraction penalty for wide integer math on x86-64” (2020-02-13).