1. Revision history
This is the first revision.
2. Introduction
128bit integers have numerous practical uses, and all major implementations
(MSVC, GCC, LLVM) provide 128bit integers already.
Among C++ users, there has been great interest in standardizing integers beyond 64 bits for a long time.
With the new wording in the C23 standard for
(see § 4.1 C Compatibility),
one of the last obstacles has been removed.
The goal of this paper is to obtain a mandatory ≥ 128bit integer type with no core language changes
and strong support from the C++ standard library.
To accomplish this, the mandatory aliases
and
are proposed.
Note that any nonmalicious implementation would be required to define
if possible,
so standardizing the minimumwidth types is standardizing exactwidth types by proxy.
While the definition of these aliases is trivial, mandating them also implies
library support from
,
,
,
, and other facilities.
After extensive investigation, it was determined that the § 4 Impact on the standard and § 5 Impact on implementations is relatively low.
2.1. Lifting library restrictions
The standard library contains a large amount of artificial hurdles
which make it impossible to provide library support for extended integers.
The current standard already permits the implementation to provide additional
extended (fundamental) integer types in addition to the standard integers (
,
, etc.).
However, even if there exists an extended 128bit integer, among other issues:

cannot exist,std :: to_string ( std :: int128_t ) 
cannot be constructed from it (without truncating tostd :: bitset
), andunsigned long long 
cannot exist.std :: abs ( std :: int128_t )
It would not be legal for an implementation to provide such additional overloads because it would change the meaning of wellformed programs.
std :: int128_t
type.
#include <string>#include <concepts>struct S { template < typename T > requires std :: same_as < T , long long >  std :: same_as < T , std :: int128_t > operator T () const { return 0 ; } }; int main () { std :: to_string ( S {}); }
This code must always call
.
If
was not the same type as
and the implementation added an overload
in spite of the standard, the call to
would be ambiguous.
The implementation has some ability to add overloads, stated in [global.functions] paragraph 2:
A call to a nonmember function signature described in [support] through [thread] and [depr] shall behave as if the implementation declared no additional nonmember function signatures.
This condition is not satisfied.
If a
overload existed, the behavior would not be as if the implementation declared no additional signature.
Note:
is not a compiler extension; it’s an optional feature.
C23 [N3047] subclause 7.22.1.1 [Exactwidth integer types] paragraph 3 requires
implementations to "define the corresponding typedef names" if there exists a
paddingfree integer type with 128 bits.
Even if you don’t find this example convincing, at best,
and other library support
would be optional.
There are also functions which undeniably cannot exist, like
(there are only
and to
).
It would be highly undesirable to have a 128bit type but no standard library support which is
documented in the standard, and which is optional on a perfunction basis, with no featuretesting macros.
Wording changes should be made to clean up this environment.
3. Motivation and scope
There are compelling reasons for standardizing a 128bit integer type:

Utility: 128bit integers are extremely useful in a variety of domains.

Uniformity: Standardization would unify the many uses under a common name and ideally, common ABI.

Existing practice: 128bit integers are already implemented in multiple compilers (see § 8.1 Existing 128bit integer types).

Performance: It is difficult, if not impossible to optimize 128bit operations in software as well as the compiler could do for a builtin type (see § 3.2 Utilizing hardware support).

Low impact: The § 4 Impact on the standard and § 5 Impact on implementations is reasonable.
3.1. Use cases
A GitHub code search for
yields 150K files,
and a languageagnostic search for
yields more than a million.
While it is impossible to discuss every one of these, I will introduce a few use cases of 128bit integers.
3.1.1. Cryptography
128bit integers are commonly used in many cryptographic algorithms:

Most notably, AES128 uses a 128bit key size. AES variants with wider key sizes still use a block size of 128 bits.

Various other block ciphers such as Twofish and Serpent also have key and/or block sizes of 128 bits.

MD5 hashes produce 128bit output.

SHA2 and SHA3 produce outputs beyond 128bit, but outputs can be truncated to 128bit, or represented as a pair/array of 128bit integers.
For example, the
step in AES is simply a 128bit bitwise XOR.
To be fair, the utility of 128bit integers in cryptographic applications is often limited to providing storage for blocks and keys.
3.1.2. Random number generation
Some random number generators produce 128bit numbers.
For example, the CSRPNG (cryptographically secure pseudorandom number generator) Fortuna uses a block cipher to produce random numbers.
When a 128bit block cipher is used, the output is naturally 128bit as well.
Fortuna is used in the implementation of
in FreeBSD 11, and in AppleOSes since 2020.
Some PRNGs use a 128bit state, such as xorshift128.
std :: uint32_t xor128 ( std :: uint32_t x [ 4 ]) { std :: uint32_t t = x [ 3 ]; t ^= t << 11 ; t ^= t >> 8 ; x [ 3 ] = x [ 2 ]; x [ 2 ] = x [ 1 ]; x [ 1 ] = x [ 0 ]; x [ 0 ] ^= t ^ ( x [ 0 ] >> 19 ); return x [ 0 ]; }
This can be expressed more elegantly using 128bit integers:
std :: uint32_t xor128 ( std :: uint128_t & x ) { std :: uint32_t t = x >> 96 ; t ^= t << 11 ; t ^= t >> 8 ; x = ( x << 32 )  ( t ^ ( std :: uint32_t ( x ) ^ ( std :: uint32_t ( x ) >> 19 ))); return x ; }
Generally speaking, there is a large amount of code that effectively performs
128bit operations, but operates on sequences of 32bit or 64bit integers.
In the above example, it is not immediately obvious that the
line
is effectively performing a 32bit shift, whereas
is selfdocumenting.
[P2075R3] proposes counterbased Philox engines for the C++ standard library, and has been received positively. The [DEShawResearch] reference implementation makes use of 128bit integers.
3.1.3. Widening operations
128bit arithmetic can produce optimal code for mixed 64/128bit operations, for which there is already widespread hardware support. Among other instructions, this hardware support includes:
Operation  x86_64  ARM  RISCV 

64to128bit unsigned multiply  : output to register pair
 for high bits, for low bits
 for high bits, for low bits

64to128bit signed multiply  : output to register pair
 for high bits, for low bits
 for high bits, for low bits

128to64bit unsigned divide  : = quotient, = remainder
 ❌  (RV128I)

128to64bit signed divide  : = quotient, = remainder
 ❌  (RV128I)

64to128bit carryless multiply  : output 128 bits to register
 for low bits, for high bits
 for low bits, for high bits

Some operating systems also provide 64/128bit operations.
For example, the Windows API provides a
function.
A more general solution was proposed by [P3018R0], which supports widening multiplication through a
function, which yields the low and high part of the multiplication as a pair
of integers.
Such utilities would be useful in generic code where integers of any width can be used.
For 64to128bit, it’s obviously more ergonomic to cast operands to
prior to an
operation.
mul_hi64
function which yields the high part
of a 64bit multiplication:
inline uint64_t mul_hi64 ( uint64_t a , uint64_t b ) { #if defined(__GNUC__) && defined(IS_64BIT) __extension__ using uint128 = unsigned __int128 ; return ( uint128 ( a ) * uint128 ( b )) >> 64 ; #else // ... }
3.1.3.1. 64bit modular arithmetic
To perform modular arithmetic with a 64bit modulo, 128bit integers are needed.
For example, when computing
between 64bit unsigned integers
,
, and
,
the multiplication between
and
is already mod 2^{64},
and the result would be incorrect unless
was a power of two.
128bit operations are used in implementations of
(see § 8.2.10 <random> for implementation experience).
Linear congruential engines use modular arithmetic, and since the user can choose
a modulo arbitrarily, the issue is unavoidable.
Note: A popular workaround for linear congruential generators is to choose the modulo to be 2^{64} or 2^{32}. This means that division is not required at all.
3.1.3.2. Multiprecision operations
For various applications (cryptography, numerics, etc.) arithmetic with large widths is required.
For example, the RSA (Rivest–Shamir–Adleman) cryptosystem typically uses key sizes of 2048 or 4096.
"Scripting languages" also commonly use an infiniteprecision integer type.
For example, the
type in Python has no size limit.
Multiprecision operations are implemented through multiple widening operations. For example, to implement Nbit multiplication, the number can be split into a sequence of 64bit "limbs", and long multiplication is performed. Since this involves a carry between digits, a 64to128bit widening operation is required.
[BoostMultiPrecision] uses a 128bit integer as a
.
This type is used extensively in the implementation of multiprecision arithmetic.
Note: The introduction of bitprecise integers (§ 7.5 Why no bitprecise integers?) does not
obsolete multiprecision libraries because infiniteprecision numbers like
Python’s
cannot be implement using a constant size.
3.1.4. Fixedpoint operations
While 64bit integers are sufficient for many calculations, the amount of available bits is reduced when the 64 bits are divided into an integral and fractional part. This may cause issues in § 3.1.8 Financial systems.
Furthermore, fixedpoint arithmetic with a doublewide operand can emulate integer division, which is a relatively expensive operation, even with hardware support.
std :: uint64_t div3 ( std :: uint64_t x ) { // 1 / 3 as a Q63.65 fixedpoint number: constexpr std :: uint_fast128_t reciprocal_3 = 0xAAAA'AAAA'AAAA'AAAB ; return ( x * reciprocal_3 ) >> 65 ; // equivalent to return x / 3; }
While modern compilers perform this strength reduction optimization for constant divisors already, they don’t perform it for frequently reused nonconstant divisors.
For such divisors, it can make sense to precompute the reciprocal and shift constants and use them many times for faster division. Among other libraries, [libdivide] uses this technique (using a pair of 64bit integers, which effectively forms a 128bit integer).
Note: 3 is a "lucky" case because all nonzero bits fit into a 64bit integer. The amount of digits required differs between divisors.
3.1.5. Highprecision time calculations
64bit integers are somewhat insufficient for highprecision clocks, if large time spans should also be covered. When counting nanoseconds, a maximum value of 2^{63}1 can only represent approximately 9 billion seconds, or 7020 years. This is enough to keep time for the forseeable future, but is insufficient for representing historical data long in the past.
This makes 64bit integers insufficient for some time calculations, where 128bit integers would suffice. Alternatively, 64bit floatingpoint numbers can provide a reasonable tradeoff between resolution and range.
is effectively a 128bit type in POSIX (
in C++), since both the
seconds and nanoseconds part of the class are 64bit integers
(assuming that
is 64bit).
[Bloomberg] uses 128bit integers to safeguard against potential overflow in time
calculations (see
)
3.1.6. Floatingpoint operations
The implementation of IEEE 754/IEC559 floatingpoint operations often involves examining the bitrepresentation of the floatingpoint number through an unsigned integer.
The C++ standard provides
, but no matching 128bit integer type,
which makes this more difficult.
std :: signbit ( std :: float128_t )
can be implemented as follows:
constexpr bool signbit ( float128_t x ) { return bit_cast < uint128_t > ( x ) >> 127 ; }
std :: isinf ( std :: float128_t )
can be implemented as follows:
constexpr float128_t abs ( float128_t x ) { return bit_cast < float128_t > ( bit_cast < uint128_t > ( x ) & ( uint128_t ( 1 ) >> 1 )); } constexpr bool isinf ( float128_t x ) { return bit_cast < uint128_t > ( abs ( x )) == 0x7fff'0000'0000'0000'0000'0000'0000'0000 ; }
Note: Infinity for binary128 numbers is represented as any sign bit, 15 exponent bits all set to
,
and 112 mantissa bits all set to
.
[Bloomberg] uses 128bit integers as part of a 128bit decimal floating point
implementation, among other uses.
Decimal floatingpoint numbers are commonly used in financial applications
and are standard C23 types (e.g.
) since [N2341].
In C++, a software implementation is necessary.
3.1.7. Floattostring/Stringtofloat conversion
The [Dragonbox] binarytodecimal conversion algorithm requires an integer type that is twice the width of the converted floatingpoint number. To convert a floatingpoint number in binary64 format, a 128bit integer type is used.
Similarly, [fast_float] uses 128bit numbers as part of decimaltobinary conversions.
This library provides an efficient
implementation.
3.1.8. Financial systems
128bit integers can be used to represent huge monetary values with high accuracy. When representing cents of a dollar as a 64bit integer, a monetary value of up to 184.5 quadrillion dollars can be represented. However, this value shrinks dramatically when using smaller fractions.
Since 2005, stock markets are legally required to accept price increments of $0.0001 when the price of a stock is ≤ $1 (see [SEC]). At this precision, 1.84 quadrillion dollars can be represented. Using a uniform precision of ten thousandths would prove problematic when applied to other currencies such as Yen, which forces the complexity of variable precision on the developer.
Even more extremely, the smallest fraction of a Bitcoin is a Satoshi, which is a hundred millionth of a Bitcoin. 2^{63} Satoshis equal approximately 92 billion BTC. In 2009, a Bitcoin was worth less than a penny, so a monetary value of only 920 million USD could be represented in Satoshis.
In conclusion, simply storing the smallest relevant fraction as a 64bit integer is often insufficient, especially when this fraction is very small and exponential price changes are involved. Rounding is not always an acceptable solution in financial applications.
[NVIDIA] mentions fixedpoint accounting calculations as a possible use case of the
type, which is a preview feature of NVIDIA CUDA 11.5.
[TigerBeetle] discusses why 64bit integers have been retired in favor of 128bit integers to store financial amounts and balances in the TigerBeetle financial accounting database. The aforementioned subpenny requirement is part of the motivation.
3.1.9. Universally unique identifiers
A 128bit integer can be used to represent a UUID (Universally Unique Identifier). While 64bit integers are often sufficient as a unique identifier, it is quite likely that two identical identifiers are chosen by a random number generator over a long period of time, especially considering the Birthday Problem. Therefore, at least 128 bits are typically used for such applications.
std :: uint128_t random_uuid_v4 () { return std :: experimental :: randint < std :: uint128_t > ( 0 , 1 ) & 0x ffff ' ffff ' ffff ' 003f ' ff0f ' ffff ' ffff ' ffff // clear version and variant  0x 0000 ' 0000 ' 0000 ' 0080 ' 0040 ' 0000 ' 0000 ' 0000 ; // set to version 4 and IETF variant }
The [ClickHouse] database management system defines their
type through
.
3.1.10. Networking
IPv6 addresses can be represented as a 128bit integer. This may be a convenient representation because bitwise operations for masking and accessing individual bits or bit groups may be used. Implementing these is much easier using a 128bit integers compared to multiprecision operations using two 64bit integers.
std :: uint128_t ipv6 = /* ... */ ; constexpr auto mask10 = 0x3ff ; if (( ipv6 & mask10 ) != 0b1111111010 ) /* wrong prefix */ ; constexpr auto mask54 = ( std :: uint64_t ( 1 ) << 54 )  1 ; if (( ipv6 >> 10 & mask54 ) != 0 ) /* expected 54 zeros */ ; constexpr auto mask64 = std :: uint64_t ( 1 ); interface_identifier = ( data >> 64 ) & mask64 ;
The [ClickHouse] database management system defines their
type through
.
3.1.11. Bitsets and lookup tables
A popular technique for optimizing small lookup tables in highperformance applications is to turn them into a bitset. 128 bits offer additional space over 64 bits.
( 1 , 0 , 0 )
, ( 1 , 0 , 0 )
, ..., or ( 0 , 0 , 1 )
)
can be represented as integers in range [0, 6).
This requires three bits of storage, and a lookup table for the cross product of two vectors
requires 3^{3} = 81 bits.
The cross product can be computed as follows:
unsigned cross ( unsigned a , unsigned b ) { [[ assume ( a < 6 && b < 6 )]]; constexpr std :: uint128_t lookup = 0x201'6812'1320'8941'06c4'ec21'a941 ; return ( lookup >> ( a * 6 + b * 3 )) & 0b111 ; }
This is significantly faster than computing a cross product between triples of
or
using multiplication and subtraction.
Using unsigned integers as lookup tables is a very popular technique in chess engines, and commonly referred to as Bitboard. The [px0] chess engine uses a 90bit board, stored in a 128bit integer.
Note: Compilers can only perform an "arraytobitset optimization" to a limited extent at this time.
Clang is the only compiler which performs it, and only for arrays of
.
Note:
does not offer a way to extract ranges of bits, only individual bits.
Therefore, it would not have been of much help in the example.
Furthermore,
has runtime checks and potentially throws exceptions,
which makes it unattractive to some language users.
3.2. Utilizing hardware support
3.2.1. Futureproofing for direct 128bit support
Hardware support for 64/128bit mixed operations is already common in x86_64 and ARM. It is also conceivable that hardware support for 128bit integer arithmetic will be expanded in the forseeable future. The RISCV instruction set architecture has a 128bit variant named RV128I, described in [RISCV], although no implementation of it exists yet.
When hardware support for 128bit operations is available, but the source code emulates these in software, the burden of fusing multiple 64bit operations into a single 128bit operation is put on the optimizer.
For example, multiple 64bit multiplications may be fused into a single 64to128bit multiplication. x86_64 already provides hardware support in this case (see § 3.1.3 Widening operations), however, the language provides no way of expressing such an operation through integer types.
3.2.2. Support through 128bit floatingpoint
On hardware which provides native support for
(see [Wikipedia] for a list), integer division up to 113
bits can be implemented in terms of floatingpoint division, and this is possibly the fastest routine.
For such instruction selection, the 113bit division must be recognized by the compiler.
It is very unlikely that the hundreds of operations
comprising a software integer division could be recognized as such.
128 bits is obviously more than 113 bits, so not every operation can be performed this way. However, modern optimizing compilers keep track of range constraints of values.

If the divisor is zero, mark the operation undefined behavior for the purpose of compiler optimization, or emit
.ud2 
Otherwise, if the divisor and dividend are constant, compute the result.

Otherwise, if the divisor is constant and greater than the dividend, yield zero.

Otherwise, if the divisor is constant, perform strength reduction (§ 3.1.4 Fixedpoint operations), making the division a shift and multiplication.

Otherwise, if the divisor is a power of two, count the trailing zeros and perform a rightshift.

Otherwise, if both operands are 2^{64}1 or less, perform 64bit integer division.

Otherwise, if one of the operands is 2^{64}1 or less, perform 128to64bit (§ 3.1.3 Widening operations) division.

Otherwise, if both operands are 2^{113}1 or less, and if there is hardware support for 128bit floating point numbers, perform floatingpoint division.

Otherwise, use a software implementation of 128bit division.
ISO C++ does not offer a mechanism through which implementations can be chosen based on optimizer knowledge. What is easy for the implementation is difficult for the user, which makes it very compelling to provide a builtin type.
Note: The precomputation in bullet 4 must not be done in C++ because the cost of computing the reciprocal is as high as the division itself. The user must be guaranteed that the entire precomputation of shift and factor is constantfolded, and this is generally impossible because optimization passes are finite.
Note: Historically, floatingpoint division in hardware was used to implement integer division. The x87 circuitry for dividing 80bit floatingpoint numbers could be repurposed for 64bit integer division. This strategy is still many times faster than software division. Intel desktop processors have received dedicated integer dividers starting with Cannon Lake.
4. Impact on the standard
First and foremost, this proposal mandates the following integer types in
:
using int_least128_t = /* signed integer type */ ; using uint_least128_t = /* unsigned integer type */ ; using int_fast128_t = /* signed integer type */ ; using uint_fast128_t = /* unsigned integer type */ ; using int128_t = /* signed integer type */ ; // optional using uint128_t = /* unsigned integer type */ ; // optional // TODO: define corresponding macros/specializations in <cinttypes>, <climits>, <limits>, ...
This change in itself is almost no change at all.
The implementation can already provide
while complying with the C++11 standard.
Challenges only arise when considering the impact of these new types on the rest of the
standard library, and possibly C compatibility.
Note: A compliant libstdc++ implementation could define all aliases as
.
4.1. C Compatibility
This proposal makes the assumption that C++26 will be based on C23. Any attempt of standardizing 128bit integers must also keep possible compatibility with the C standard in mind.
4.1.1. intmax_t
and uintmax_t
In particular,
has historically prevented implementations from providing
integer types wider than
without breaking ABI compatibility.
A wider integer type would change the width of
.
C23 has relaxed the definition of
. [N3047], 7.22.1.5 [Greatestwidth integer types] currently defines
as follows:
The following type designates a signed integer type, other than a bitprecise integer type, capable of representing any value of any signed integer type with the possible exceptions of signed bitprecise integer types and of signed extended integer types that are wider thanand that are referred by the type definition for an exact width integer type:
long long intmax_t
For
to not be
,
there must exist an
alias for the same type.
GCC already provides an
type which satisfies the paddingfree requirement and could
be exposed as
.
In conclusion, it is possible to provide a
alias with equivalent
semantics in C and C++, and with no ABI break.
4.1.2. int_least128_t
in C
does not force
to exist in C.
In principle, C++ compilers can disable support for the type in C mode, so that there is
effectively no impact.
However, this would be somewhat undesirable because there would be no mandatory interoperable type.
C users would use
or
and C++ users would use
,
which would only be
by coincidence.
For the sake of QoI, implementations should expose the corresponding alias in C as well
(which they are allowed to).
To make the type mandatory in both languages, cooperation from WG14 is needed.
4.1.3. C library impact
A C++ implementation is required to provide compatibility headers as per [support.c.headers] which have equivalent semantics to the headers in C, with some details altered. The affected candidates are those listed in Table 40: C headers [tab:c.headers].
Header  Impact of extended integers 

 Define macro constants 
 Define macro constants 
 Define type aliases 
 Define type aliases 
 Support 128bit / optionally

There is no impact on other C headers.
Most of the issues are trivial and have no runtime C library impact.
The only thing worth noting is that 128bit support from
/
would be
required (§ 8.2.17 <cstdio> for implementation experience).
Note: This support is made optional, so that C++ implementations are able to keep using
the system’s C library.
Otherwise, the C++ implementation could only guarantee 128bit
support
if it was part of the C++ runtime library.
Note: If additionally, a C implementation wanted to support
,
it would need to add extended integer support in a few other places.
For example,
requires typegeneric functions to support all extended integers.
4.2. Impact on the core language
The proposal makes no changes to the core language because existing semantics of extended integers are sufficient (see § 7.7 Should extended integer semantics be changed? for discussion). See also § 7.8 Do we need new userdefined literals?.
4.2.1. Note on surprising semantics
It is worth noting that the existence of
leads to some oddities:

The result of the
operator can be a 128bit integer.sizeof 
The underlying type of enumerations can be a 128bit integer.

Previously illformed integer literals could now be of 128bit integer type.

The conditions in
preprocessing directives are evaluated as if operands had the same representation as#if
orintmax_t
, which means that 128bit integers cannot be used in this context, or the values would be truncated.uintmax_t
However, none of this is a new issue introduced by this proposal. Any compliant implementation could already have produced this behavior, assuming it supported 128bit integers as an optional extended integer type.
Note: The fact that the preprocessor doesn’t operate on the widest integer type,
but on
will need to be addressed.
However, this is a general problem with rebasing on C23 and not within the scope
of this proposal.
4.3. Impact on the library
Find below a summary of issues that arise from the introduction of 128bit integers in the
C++ standard library.
One common issue is that aliases such as
and
within
containers, iterators, and other types can be a 128bit integer.
The same applies to
,
,
.
The proposal does not force library maintainers to redefine any of these aliases; it’s just a possibility. Whether to define them as such is a QoI issue in general, and won’t be discussed further.
4.3.1. Language support library
Issue:
may need an overload for 128bit integers.
Action: ✔️ None because we don’t support it (see § 7.9 Why no std::div?).
Issue:
may need 128bit support.
Action: ✔️ None
(see § 8.2.1 std::to_integer for implementation experience).
Issue:
needs 128bit integer featuretesting macro.
Action: ⚠️ Add macros (see § 9.1 Header <version> for wording).
Issue:
needs a trait for 128bit integers.
Action: ✔️ None.
Issue:
needs additional constants for 128bit integers.
Action: ✔️ None.
Issue:
needs to explicitly require support for 128bit integers
in its synopsys.
Action: ⚠️ Define aliases (see § 9.2 Header <cstdint> for wording).
Issue:
needs to support 128bit only optionally.
Action: ⚠️ Make support optional (see § 9.3 Header <inttypes.h> for wording).
4.3.2. Metaprogramming library
Issue:
needs to support 128bit integers.
Action: ✔️ None
(see § 8.2.2 <type_traits> for implementation experience).
Issue:
and
require 128bit support.
Action: ✔️ None
(see § 8.2.2 <type_traits> for implementation experience).
Issue:
currently accepts nontype template arguments of
.
is no longer the widest integer type and changing the type of NTTP to
would be an ABI break because the type of template argument participates
in name mangling.
Action: ✔️ None
(see § 7.10 What about the std::ratio dilemma? for discussion).
4.3.3. General utilities library
Issue: Integer comparison functions (
et al.) require 128bit support.
Action: ✔️ None
(see § 8.2.3 std::cmp_xxx for implementation experience).
Issue:
needs to support 128bit integers.
Action: ✔️ None.
Issue:
could receive an additional constructor taking
.
Action: ⚠️ Add such a constructor
(see § 9.4 Class template bitset for wording § 6.4 std::bitset constructor semantic changes for discussion).
Issue:
could receive an additional
function, similar to
.
Action: ⚠️ Add such a function
(see § 9.4 Class template bitset for wording and § 8.2.4 <bitset> for implementation experience).
Issue:
and
need to support 128bit integers.
Action: ✔️ None
(see § 8.2.5 <charconv> for implementation experience).
Issue:
needs to support 128bit integers.
Action: ✔️ None
(see § 8.2.6 <format> for implementation experience).
Issue:
might need 128bit integers support.
Action: ✔️ None, it doesn’t
(see § 8.2.6 <format> for implementation experience).
Issue:
might need support for 128bit integers.
Action: ✔️ None, it doesn’t
(see § 8.2.6 <format> for implementation experience).
Issue:
needs to support 128bit integers.
Action: ✔️ None
(see § 8.2.7 <bit> for implementation experience).
Issue:
could support 128bit types.
Action: ⚠️ Add overloads
(see § 9.5 Numeric conversions for wording and § 8.2.8 std::to_string for implementation experience).
4.3.4. Containers library
Issue: The extents and index types of
could be 128bit integers.
This is also the case for type aliases of
.
The expositiononly helper
now also includes
128bit integers.
Action: ✔️ None.
All these issues are either QoI or don’t impact existing implementations substantially.
4.3.5. Iterators library
Issue: The expositiononly helper
now also includes
128bit integers.
Generally, 128bit integers would be a valid
and an implementation needs to
consider this when defining concepts that use integers in any way.
Action: ✔️ None.
Note: As long as
(and by proxy,
) is correct, the existing wording
should be unaffected.
4.3.6. Ranges library
Issue:
is required to be a 128bit integer if
is not 128bit, and such a type exists.
This is not the the case in libc++ and MSVC STL at this time,
where
is
and
respectively.
Action: ⚠️ Relax wording to prevent breaking ABI
(see § 9.6 Iota view for wording and § 5.2 std::ranges::iota_view ABI issue for discussion).
Issue:
may now return a 128bit integer.
The standard recommends to use a type which is sufficiently wide to store the product
of sizes of underlying ranges.
A similar issue arises for
.
Action: ✔️ None.
Note: The choice of integer type used to be (and still is) implementationdefined.
4.3.7. Algorithms library
Issue:
,
, and
need to support 128bit integers.
Action: ✔️ None
(see § 8.2.9 std::gcd, std::lcm for implementation experience).
Issue: Saturating arithmetic functions and
need to support 128bit integers.
Action: ✔️ None
(see § 8.2.12 std::xxx_sat for implementation experience).
4.3.8. Numerics library
Issue: Various random number generators and
need to support
128bit types.
Action: ✔️ None
(see § 8.2.10 <random> for implementation experience).
Issue:
needs to support
.
Action: ✔️ None
(see § 8.2.10 <random> for implementation experience).
Issue:
needs to support 128bit integers.
Action: ✔️ None (see § 8.2.15 std::valarray for implementation experience)
Issue: For most
functions, an additional overload taking 128bit integers would
need to be defined.
Action: ✔️ None
(see § 8.2.14 <cmath> for implementation experience.)
Issue:
could receive an additional 128bit overload.
Action: ⚠️ Add an overload
(see § 9.7 Absolute values for wording and § 8.2.13 std::abs for implementation experience).
Issue: The
library needs 128bit support.
Action: ✔️ None
(see § 8.2.16 <linalg> for implementation experience).
4.3.9. Time library
Issue: Significant portions of
use
, which has
template
parameters.
Action: ✔️ None
(see § 7.10 What about the std::ratio dilemma? for discussion).
4.3.10. Localization library
Issue:
and
could use
overloads for
.
Action: ✔️ None.
Note: This would be a an ABI break if changes were made.
relies on virtual member functions, and modifying the vtable breaks ABI.
and
cha can be used for localedependent formatting without breaking ABI.
and
provide localeindependent alternatives.
4.3.11. Input/output library
Issue:
and
don’t support 128bit integers.
By proxy, extraction with
and insertion with
would not work.
Action: ✔️ None.
Note: The standard doesn’t require these to work for all integer types, only for standard integer types.
Any change would be an ABI break, so these facilities could be left untouched.
Unfortunately, the user won’t be able to
, however,
the language provides sufficient alternatives
(
,
,
,
).
Issue:
and
need to support 128bit integers.
Action: ✔️ None
(see § 8.2.17 <cstdio> for implementation experience).
Issue:
needs to include the wording changes for
.
Action: ⚠️ Include changes
(see § 9.8 Header <cinttypes> for wording).
4.3.12. Concurrency support library
Issue:
needs to support
.
Action: ✔️ No impact on the standard
(see § 8.2.18 std::atomic for implementation experience).
Issue: There should be additional aliases
et al. aliases.
Action: ⚠️ Define aliases
(see § 9.9 Atomic operations for wording).
5. Impact on implementations
5.1. Estimated implementation effort
The following table summarizes the affected standard library parts and the estimated effort required to implement the proposed changes.
Affected library part  Work definitely required  Implementation experience 

 ✔️ no  § 8.2.1 std::to_integer 
 § 9.1 Header <version>  
 add specializations  
 add macro constants  
 ✔️ no  § 8.2.2 <type_traits> 
 ✔️ no  § 8.2.2 <type_traits> 
 ✔️ no  § 8.2.3 std::cmp_xxx 
 ✔️ no  
 § 9.4 Class template bitset  § 8.2.4 <bitset> 
 ✔️ no  § 8.2.5 <charconv> 
 ✔️ no  § 8.2.6 <format> 
 support 128bit  § 8.2.7 <bit> 
 § 9.5 Numeric conversions  § 8.2.8 std::to_string 
 § 9.6 Iota view  § 5.2 std::ranges::iota_view ABI issue 
,
 ✔️ no  § 8.2.9 std::gcd, std::lcm 
 ✔️ no  § 8.2.11 std::midpoint 
 ✔️ no  § 8.2.12 std::xxx_sat 
 256bit LCG  § 8.2.10 <random> 
 ✔️ no  § 8.2.15 std::valarray 
overloads
 ✔️ no  § 8.2.14 <cmath> 
 § 9.7 Absolute values  § 8.2.13 std::abs 
 ✔️ no  § 8.2.16 <linalg> 
,
 ✔️ no  § 8.2.17 <cstdio> 
 § 9.3 Header <inttypes.h>  
 § 9.8 Header <cinttypes>  
 § 9.9 Atomic operations  § 8.2.18 std::atomic 
When deciding "Work definitely required", this paper does not consider menial changes like
relaxing
and such,
which may be present in functions such as
.
Also, if there exists at least one standard library which implements these features, it is assumed that they can be adapted into other libraries with relative ease.
5.2. std :: ranges :: iota_view
ABI issue
libstdc++ defines
for
to be
.
Since a
alias would likely be defined as
, there is no ABI impact.
Other libraries are not so fortunate.
5.2.1. Affected implementations
By contrast, the
for
in libc++
is
.
The MSVC STL uses a class type
.
Even trivially copyable classes aren’t passed via register in the Microsoft x86_64 ABI,
so this type is passed via the stack.
Redefining this to be an integer type would break ABI, assuming that a Microsoft
would be passed via registers.
5.2.2. Cause of ABI break
The ABI break stems from the fact that
for
is defined to be:
a signed integer type of width greater than the width of
if such a type exists.
W
Currently, no such type exists, but if
did exist, it would no longer be valid
to use a class type or
as a
.
5.2.3. Possible solution
See § 9.6 Iota view for a proposed solution which resolves this issue without requiring action from implementors.
5.2.4. What about iota_view < std :: int_least128_t >
?
Besides the option to provide a ≥ 129bit
,
implementations can also define
to be a 128bit integer.
Neither the Cpp17RandomAccessIterator requirement nor the
concept
require the difference between two iterators to be representable using their
.
Therefore, this is a reasonable strategy which is easy to implement.
Of course, it has the adverse affect that
is possibly undefined behavior for two iterators.
In practice, will the user ever need a 128bit
, and if so, do they need to represent
such extreme differences?
These are qualityofimplementation issues which maintainers will need to consider.
libstdc++ already supports
, where the
is
.
Besides QoI questions, this proposal does not introduce any new issues.
6. Impact on existing code
With no core language changes and only additional standard library features, the impact on existing code should be minimal.
6.1. Possible semantic changes
However, this idea is put into question when it comes to integer literals.
auto x = 18446744073709551615 ; // 2 ^{64}1
If the widest signed integer type is a 64bit type, this code is illformed. Every compiler handles this differently:

clang declares
asx
and emits a warning (✔️ compliant).unsigned long long 
GCC declares
asx
and emits a misleading warning (✔️ compliant).__int128 
MSVC declares
asx
and emits no warning (❌ noncompliant).unsigned long long
The example demonstrates that in practice, introducing a 128bit integer may
impact some existing code.
To comply with the C++ standard, the type of
would have to be
assuming that
cannot represent the value.
Hexadecimal literals are not affected in the same way because they
are required to be of type
if
cannot represent the value.
The introduction of a 128bit integer type would not alter the signedness of existing literals.
6.2. Impact on overload sets
Besides templates, a popular technique for covering multiple integer types is to create an "exhaustive" overload sets like:
// support "all" signed integers (anything less than int is promoted) void foo ( int ); void foo ( long ); void foo ( long long );
I’m putting "exhaustive" in quotes because such code does not cover extended integer types, which can exist. Only the implementation knows the whole set of integer types and can ensure completeness.
Note: Creating an overload set from
,
,
,
and
is not possible because it only covers four out of five standard integer types,
making some calls ambiguous.
While creating sets like these outside of language implementations is not ideal,
the proposal can minimize the impact by making
a distinct type from
standard integer types.
std :: int_least128_t
is long long
, the following code is illformed:
void foo ( int ) { } void foo ( long ) { } void foo ( long long ) { } void foo ( std :: int_least128_t ) { } // redefinition of foo(long long)
long long
is 128bit, so no code is really affected.
Note: A workaround is to write
.
However, this solution is not obvious, and should not be necessary.
6.2.1. Proposed solution
There should exist a natural and universally correct way to extend such overload sets,
so that the effort of "upgrading" to 128bit is minimal.
Therefore
should be distinct.
Guaranteeing that
is distinct means that even if
is a 128bit type, it won’t be chosen by this alias.
This breaks the conventions of
and may be surprising,
but no implementation with
aliases beyond
exists,
and no implementation where
is 128bit exists.
No existing code is affected; this is a purely academic problem.
6.3. Possible assumption violations
There is obviously a substantial amount of code which assumes that integers are no wider
than 64 bits.
There is also a substantial amount of code which assumes that
is
the widest integer type, and this assumption would be broken by introducing
.
The exact impact is investigated in this proposal. Assumptions about hardware or integer width limitations cannot hold back language development. C would be stuck with 32bit types if that had ever been a convincing rationale. Also, the introduction of a 128bit type does not break existing code unless the user chooses to use it.
6.4. std :: bitset
constructor semantic changes
The only overload which accepts integers is
.
Ideally, we would like to construct bitsets from wider integer types, if available. My proposed solution changes the semantics of this constructor (see § 9.4 Class template bitset for wording).
The existing constructor is problematic for multiple reasons:

If extended by a
overload, a callstd :: int_least128_t
would become ambiguous.bitset < N > ( 0 ) 
When called with negative numbers, a sign extension only takes place up to the width of
. Beyond that, the bits are zerofilled.unsigned long long
#include <bitset>constexpr std :: bitset < 128 > bits ( 1 ); static_assert ( bits . count () == 64 );
The original behavior is very difficult to preserve if we add more overloads.
If we added an
overload, then
would be ambiguous.
Therefore, we must at least have an overload for all integers with a conversion rank
of
or greater.
However, if so,
under the current definition would result int a
that has only 32 onebits (assuming 32bit
).
We could preserve the current behavior exactly if signextension occurred up to the width
of
; beyond that, zeroextension would be used.
This is not proposed because the design makes no sense outside of its historical context.
6.4.1. Proposed solution
Therefore, I propose to perform signextension for the full size of the
.
In other words,
would always be a bitset where every bit is set.
This almost certainly matches the intent of the user.
A GitHub code search for
finds 30 uses of constructing a bitset from a negative literal.
Of the ones which use
, all uses are of the form

wherestd :: bitset < N > ( 1 )
is less than 64, orN 
.std :: bitset < N > ( static_cast < T > ( 1 ))
None of these existing uses would be affected.
Note: See § 9.4 Class template bitset for wording.
7. Design considerations
The goal of this proposal is to obtain a mandatory 128bit type with strong library support.
A
alias is the only option that does not involve any changes
to the core language.
Therefore, it is the obvious design choice for this proposal.
Note: Unlike the existing
and
aliases, this type is distinct.
See § 6.2 Impact on overload sets for rationale, and § 9.2 Header <cstdint> for wording.
Besides the current approach, there are a few alternatives which have been considered:
7.1. Why no standard integer type?
Why standardize a
type alias but no standard integer type? Essentially, why no
std :: uint_least128_t ?
unsigned long long long
Firstly, naming is a problem here.
A standard integer type would likely warrant the ability to name it by keyword, and an
everincreasing sequence of
s isn’t an attractive solution.
Even with a concise keyword such as
, it is unclear what advantage such a keyword
would have over a type alias, other than saving one
directive.
Secondly, it is useful to keep
a secondclass citizen by not making it a
standard integer type.
For example, in the formatting library, a format string can
specify a dynamic width for an argument, which must be a standard integer.
A width that cannot be represented by a 64bit number is unreasonable,
so it makes sense to limit support to standard integers.
Thirdly, as already stated in § 4.1 C Compatibility, C23’s
must be the
widest standard integer type.
To not break ABI and be C23compatible,
must be
an extended integer type.
7.2. Why no mandatory std :: int128_t
type?
Mandating any exact
inadvertently restricts the byte width
because exactwidth types cannot have any padding.
implies that the width of a byte is a power of two ≤ 128,
and historically, C++ has not restricted implementations to a specific byte size.
This decrease in portability also has no good rationale.
If
is mandatory and an implementation is able to define it without padding,
then
is effectively mandatory.
Hypothetically, a malicious implementation could define
to be a 1000bit
integer with 872 padding bits, even if it was able to define a paddingfree 128bit integer.
However, malicious implementations have never been a strong argument to guide design.
7.3. Why no std :: int_least256_t
type?
256bit integers are also useful and one could use many of the arguments in favor of 128bit integers to also propose them. However, there are are a few strong reasons against including them in this proposal:

The wider the bit sizes, the fewer the use cases are. For example, 128 bits are sufficient for highprecision clocks and most financial applications.

There is tremendously less hardware support for 256bit integers. x86 has instructions to perform a 64to128bit multiplication, but no such 128to256bit instruction exists.

There are fewer existing implementations of 256bit integers.

Many use cases of 256bit integers are simply bit manipulation. The wider the type, the less common arithmetic becomes. Bitwise operations (
,&
,
) are best done through vector registers, but the ABI for~
is to use general purpose registers or the stack, and_BitInt ( 256 )
would likely be the same. Since there is strong hardware support for § 3.1.3 Widening operations which is based on general purpose registers, the choice for 128bit is easy; not so much for 256bit.std :: int_least256_t 
is a "support type" forstd :: uint128_t
which simplifies the implementation of manystd :: float128_t
functions. There exists no hardware with binary256 support to motivate a< cmath >
support type.std :: uint256_t 
Longer integer literals are a major reason to get a fundamental type. 256bit hexadecimal literals are up to 64 digits long, which degrades code quality too much. With indentation and digit separators, using the full width can exceed the autoformatter’s column limit.
It is also unclear whether there should ever be a mandatory 256bit extended integer type, or if support should be provided through 256bit bitprecise integers. Overall, this proposal is more focused if it includes only 128bit.
Nevertheless, many of the changes in § 9 Proposed wording pave the way for a future
or even
.
There would be no wording impact other than defining the necessary aliases and macros.
7.4. Why no class type?
Instead of an extended integer type, it would also be possible to provide the user with a
128bit class type.
This could even be done through a general
class.
However, there are compelling reasons against doing so:

is sufficiently common to where the added cost of class type (overload resolution for operators, function call evaluation in constant expressions, etc.) would be a burden to the user.std :: int_least128_t
is the "work horse" of any multiprecision library which uses 64to128bit widening operations (see § 3.1.3.2 Multiprecision operations). This cost could add up quickly.__int128 
A fundamental type also comes with integer literals, and up to 128bit, there are still reasonable use cases for integer literals. § 3 Motivation and scope shows multiple examples where 128bit literals were used. Besides these use cases, it would be nice to represent an IPv6 address using a hexadecimal literal (which is the typical representation of these addresses).

There are 128bit architectures where the general purpose register size is 128bit. For example, the RV128I variant of RISCV is such an architecture. To be fair, there exists no implementation of RV128I yet. Still, it would be unusual not to have a fundamental type that represents the general purpose register.
In essence, 128bit is still "special" enough to deserve a fundamental type. Beyond 128bit, even hexadecimal literals become hard to read due to their sheer length, and we are unlikely to find any ISA with a 256bit register size, even counting theoretical ISAs like RV128I.
7.5. Why no bitprecise integers?
Instead of putting work into 128bit integers, it would also be possible to integrate
bitprecise integers (C’s
type, proposed in [N2763]) into the C++ standard.
This would be a sufficient alternative if
was a fundamental type,
had integer literal support, and had strong library support.
However, there are numerous reasons why
is not the right path for this proposal, described below.
In short, this proposal argues that
does not bring sufficient value to C++ relative to its
impact, and would better be exposed via a class type
than a fundamental type.
7.5.1. _BitInt
has no existing C++ work
After enquiry in the stdproposals mailing list, no one has expressed that they are working on
, nor has anyone expressed interest on beginning work on this.
Right now,
is a purely hypothetical feature.
7.5.2. _BitInt
has less motivation in C++
A significant part of the rationale for [N2709] was that only
can utilize hardware
resources optimally on hardware such as FPGAs that have a native 2031bit type.
C++ is much less ambitious in its goal of supporting all hardware,
with changes such as C++20 effectively mandating two’s complement signed integers.
C23 supporting
is not rationale for a C++ fundamental type in itself.
Therefore, a limited solution as focusing on 128bit is not unreasonable.
7.5.3. _BitInt
should be exposed as a class type
By default, all new language features are library features. If it’s possible to express Nbit integers through a class, then this is the goto solution.
In C++, it is possible to expose the compiler’s
functionality as follows:
inline constexpr size_t big_int_max_width = BITINT_MAXWIDTH ; template < size_t N > requires ( N <= BITINT_MAXWIDTH ) struct big_int { _BitInt ( N ) _M_value ; // TODO: constructors, operator overloads, etc. }; // TODO: define big_uint similarly // compatibility macros: #ifdef __cplusplus #define _BigInt(...) ::std::big_int<__VA_ARGS__> #define _BigUint(...) ::std::big_uint<__VA_ARGS__> #else #define _BigInt(...) _BitInt(__VA_ARGS__) #define _BIgUint(...) unsigned _BitInt(__VA_ARGS__) #endif
There is precedent for this design:

is compatible with C’sstd :: complex < T >
._Complex T 
is compatible with C’sstd :: atomic < T >
._Atomic ( T )
Unless an extremely strong case for a new category of integer types in the core language can be made, this is the obvious solution.
Of course, a class type is more costly in terms of compilation slowdown as well as
performance on debug builds and in constant evaluations.
This is acceptable because
is not a replacement for the existing integers,
but an infrequently used specialpurpose type which only comes into play when no other size
would suffice.
7.5.4. _BitInt
is not a replacement for standard integers
In C,
is a secondclass citizen.
The overwhelming majority of library functions take
,
, and other standard integer types.
Conversion rules are also biased in favor of standard integers.
For example,
is of type
in C, if
is 32bit.
Standard integers are essentially engraved into the language and have special status, both in C
and in C++.
Billions of lines of code use these integers, and this is never going to change.
Virtually all learning resources in existence teach standard integers or
aliases,
which are standard integers in all implementations.
Even if
was a replacement for the existing integers, the transition process would take a century.
It is better to think of it as an extension of the existing integers.
7.5.5. _BitInt
does not guarantee 128bit support
The whole point of this proposal is to guarantee developers a 128bit type.
However,
is only valid for
, where
is guaranteed to be no more than the width of
.
7.5.6. _BitInt
requires more library effort
This proposal only mandates
, not any specific width.
Therefore, implementers have the luxury of relying on all integers being paddingfree
and all integers having a width which is 2^{N} times the platform’s byte size.
These assumptions greatly simplify the implementation of various algorithms.
From personal experience, implementing any functions in
is tremendously easier
when it is guaranteed that integers have a 2^{N} width.
Full
library support requires tremendously greater library effort.
It is unclear what parts of the standard library can be burdened.
7.5.7. _BitInt
breaks more existing code
also breaks assumptions in existing, generic code.
C++ users have enjoyed the luxury of paddingfree integers on conventional implementations
for a very long time, and some code depends on it.
It may depend through antipatterns like using
for comparison of
s storing integers,
or more justified uses like
.
inevitably breaks assumptions about padding and size, which is a great challenge
to both the implementation and C++ users.
_BitInt ( 30 )
has padding bits.
template < std :: integral T > auto to_byte_array ( const T & x ) { return std :: bit_cast < std :: array < std :: byte , sizeof ( T ) >> ( x ); } int main () { _BitInt ( 30 ) x = ...; write_buffer_to_file ( file , to_byte_array ( x )); }
Assuming a 4byte array is returned,
one of these bytes would have indeterminate
value because the corresponding byte in
has padding bits.
Reading this indeterminate value to store it in a file is undefined behavior.
While the assumption that all integers are paddingfree is not universally correct,
C++ users have enjoyed this guarantee for decades, and an unknown amount of code depends on it.
If
was just another integral type, it could silently break existing code like in the example,
despite a
constraint.
Note:
does not have the same problem because the implementation can define
it as a type with more than 128 bits which has no padding, if need be.
7.5.8. _BitInt
has teachability issues
has different promotion and conversion rules.
These rules are not necessarily obvious, especially when bitprecise integers interact with
other types.
For example,
is of type
if
is of type
or narrower, and
is 32bit.
Not only does the user have to learn the existing integer conversion and promotion rules,
the user also has to learn this new
system and how it interacts with the old system.
This also complicates overload resolution:
void foo ( int ); void foo ( _BitInt ( 32 )); int main () { _BitInt ( 32 ) x = 0 ; foo ( x ); // calls which? }
If
dominates in implicit conversions, should it also dominate in overload resolution so that
is selected?
The answer is not entirely clear.
void foo ( int ); void foo ( _BitInt ( 32 )); int main () { foo ( 1'000'000'000 ); // calls which? }
On a target where
is 16bit, the integer literal is of type
.
It is possible to convert it to
in a narrowing conversion, and possible
to convert it to
losslessly.

Should overload resolution prefer the lossless conversion to
? After all, lossless is better._BitInt ( 32 ) 
Should the call be ambiguous? After all, that’s what normally happens when there are two viable candidates with equally long conversion sequences.

Should
be called? After all, standard integers are normally dominant, andfoo ( int )
could be of type1 ’000 ’000 ’000
if onlyint
was 32bit.int
There is a compelling case for each of these options, and no matter what design choice is made, the complexity of the language increases significantly.
7.5.9. _BitInt
in C may be too permissive
Many C++ users have lamented that integers are too permissive.
As is tradition, C has not restricted the behavior of
substantially:

Signed/unsigned mixed comparison are permitted.

Signed/unsigned mixed arithmetic is permitted.

Implicit truncating conversion is permitted.

Implicit signed/unsigned conversion is permitted.
One substantial difference is that
is not promoted to
, other than that,
the semantics are the same as the old integers.
If
behaves almost the same as standard integers and brings all of this bugprone
behavior with it, how can one justify adding it as a new fundamental type?
Of course, C++ could decide to make these semantics more restrictive for its
type,
similar to how implicit casts from
are only permitted in C, but not in C++.
However, this would complicate writing C/C++ interoperable code
and make the language even less teachable because
semantics would become languagespecific.
Furthermore, the more distinct the
semantics become, the less of a dropin replacement for
it becomes:
std :: uint64_t mul_hi ( std :: uint64_t x , std :: uint64_t y ) { return u128 ( x ) * u128 ( y ) >> 64 ; }
If
is extended integer type, this code is wellformed.
If
is a Cstyle
, this code is wellformed.
If
is a more restrictive C++
which forbids narrowing,
this code is illformed.
In short, the dilemma is as follows:

If
is as permissive as existing integers, it is harder to justify its addition to the language because it innovates too little._BitInt 
If
is more restrictive, its impact on existing code and on language teachability is greater._BitInt
There is no obvious path here, only a bottomless potential for discussion.
By comparison,
has exactly the same rules as existing integers,
which
follows.
The user can use it as a dropin replacement:
#ifdef __SIZEOF_INT128__ using i128 = __int128 std :: int128_t ; #else // struct i128 { /* ... */ }; #endif
7.5.10. _BitInt
false dichotomy
The
vs.
argument is a false dichotomy.
Bitprecise integers essentially create a parallel, alternative type system with
different rules for promotion, implicit conversion, and possibly overload resolution.
Defining any
/
type alias as a bitprecise integer would be hugely surprising
to language users, who have certain expectations towards aliases in these headers.
These expectations have been formed over the past 30 years.
Therefore, even if all issues regarding
mentioned in this paper were resolved
and
becomes a fundamental type,
it would be reasonable to maintain the "legacy" type system in parallel.
7.6. Why not make it optional?
Instead of making
entirely mandatory, it would also be possible to
make it an optional type, or to make it mandatory only on hosted implementations.
First and foremost, making the type optional has severe consequences. Library authors still have to write twice the code: one version with 128bit support, one version without. To C++ users, the ability to write portable code is more valuable than the underlying implementation effort, or potential performance issues. I will go into these two issues more:
7.6.1. Implementation effort is not too high
The criticism is:
On freestanding/embedded platforms, the implementation effort of
is too great.
std :: int_least128_t
While this concern is valid, C23 requires arbitraryprecision arithmetic through
anyway
and GCC and clang support
already (see § 8.1.4 _BitInt(128) for support).
Assuming that vendors care about Ccompatibility,
this proposal merely requires vendors to provide
.
Note: Bitprecise integers have slightly different semantics than extended integers.
However, these differences don’t matter if
is the widest integer,
making it valid to use as
.
The remaining impact is limited to the standard library.
For the most part, it is simple to generalize library algorithms to an arbitrary bit size.
It is especially easy when the implementation can ensure that all integers are paddingfree
and have a size that is a 2^{N} multiple of the byte size.
Only
is mandatory (not the exactwidth types),
so the implementation can ensure it.
7.6.2. Software emulation is acceptable
The criticism is:
should not be mandatory if software emulation degrades performance.
std :: int_least128_t
There is also merit to this concern. A mandatory type may give the user a false sense of hardware support which simply doesn’t exist, especially on 32bit or even 8bit hardware.
However, this problem is innate to standard integers as well.
If a user is compiling for a 32bit architecture, a 64bit
will have to be
softwareemulated, and 64bit integer division can have dramatic cost.
Why should a 64bit
be mandatory on an 8bit architecture?
The answer is: because it’s useful to rely on
so we can write portable code,
even if we try to avoid the type for the sake of performance.
In the end, it’s the responsibility of the user to be vaguely aware of hardware capabilities
and not use integer types that are poorly supported.
If the user wants to perform a 128bit integer division on an 8bit machine,
the language shouldn’t artificially restrict them from doing so.
The same principle applies to
,
, C23’s
, etc.
7.7. Should extended integer semantics be changed?
An interesting question is whether extended integer semantics make sense in the first place, or require some form of changes. The relevant standard section is 6.8.6 [conv.rank].
In summary:
Relevant Rule  Example 

Unsigned integers have the same rank as signed integers of equal width. 

Wider integers have greater rank. 

Standard integers of equal width have greater rank. 
if is 128bit

Extended integers with the same width are ranked implementationdefined. 
if these types are distinct 
7.4 [expr.arith.conv] decides that for integers of equal rank, the common type is unsigned.
For example,
is of type
.
I believe these semantics to be sufficiently clear; they don’t require any change.
Note: The rules for determining the better overload are based on implicit conversion sequences. If the rules for conversions are unchanged, by proxy, overload resolution remains unchanged.
7.8. Do we need new userdefined literals?
No, not necessarily. If the user desperately wants to define a userdefined literal which accepts 128bit numeric values and beyond, they can write:
constexpr int operator "" _zero ( const char * ) { return 0 ; } int x = 100000000000000000000000000000000000000000000000000000000000000000 _zero ;
Obviously, this forces the user into parsing the input string at compiletime if they want
to obtain a numeric value.
A literal operator of the form
circumvents this problem,
but
is typically not 128bit.
Therefore, it could be argued that these rules should be expanded to save the user the trouble
of parsing.
However, this is not proposed because it lacks motivation. Userdefined literals have diminishing returns the longer they are:

The difference between
and0 s
is huge: 800%.chrono :: seconds { 0 } 
The difference between
and1000000000 s
is not: ~150%.chrono :: seconds { 1000000000 }
The shortest userdefined literal that does not fit into
is
, which is 20 characters long.
At this length, userdefined literals never provide overwhelming utility.
However, if WG21 favors new forms of
for
integer types wider than
, I am open to working on this.
Note: The standard currently does not allow
for any
except
.
7.9. Why no std :: div
?
is a function which returns the quotient and remainder of an integer division in one
operation.
This proposal intentionally doesn’t extend support to 128bit types because each overload of
returns a different type.
Namely, the current overloads for
,
, and
return
,
, and
respectively.
This scheme isn’t easy to generalize to 128bit integers or other extended integer types.
A possibly approach would be to define a class template
and redefine
the concrete types to be aliases for
etc.
However, this is arguably a breaking change because it alters what template argument deduction
is possible from these types.
Furthermore,
is arguably useless.
Optimizing compilers recognize separate uses of
and
and fuse them into a single
division which yields both quotient and remainder, at least on platforms where this is possible.
Note: In C89,
was useful because it had a well specified rounding mode,
whereas the division operator had implementationdefined rounding.
7.10. What about the std :: ratio
dilemma?
Assuming that C++26 is based on C23,
will be problematic
because it is defined as:
template < intmax_t Num , intmax_t Denom = 1 > class ratio { /* ... */ };
would no longer be the widest integer type, and certain extreme ratios
would become unrepresentable.
It is not possible to redefine it to have other types for template parameters
because the types of template arguments participate in name mangling.
This issue is not caused by this proposal, but the introduction of a 128bit integer first manifests it.
This proposal does not attempt to resolve it.
However, a possible path forward is to make
less dependent on
and allow ratiolike types instead.
8. Implementation experience
8.1. Existing 128bit integer types
8.1.1. __int128
(GNUlike)
GCC and clang already provide the 128bit integer types in the form of
and
.
However, this type is not available when compiling for 32bit targets.
Clang provides the same support.
8.1.2. __int128
(CUDA)
In NVIDIA CUDA 11.5, the NVCC compiler has added preview support for the
signed and unsigned
data types on platforms where the host compiler supports it.
See [NVIDIA].
8.1.3. std :: _Signed128
, std :: _Unsigned128
The MSVC STL provides the class types
and
defined in
.
These types implement all arithmetic operations and integer comparisons.
They satisfy the integerlike constraint and have been added to implement [P1522R1].
is possibly defined as
.
8.1.4. _BitInt ( 128 )
The C23 standard requires support for bitprecise integers
where
.
While this doesn’t strictly force support for 128bit integers,
GNUfamily implementations support more than 128 bits already.
As of February 2024, the support is as follows:
Compiler 
 Targets  Languages 

clang 14 
 all  C & C++ 
clang 16 
 all  C & C++ 
GCC 14 
 64bit only  C 
MSVC 19.38  ❌  ❌  ❌ 
Note: clang has supported
as an
compiler extension prior to C standardization.
It is possible that given enough time,
will be
supported by Microsoft as well.
Note: Microsoft Developer Community users have requested support for a 128bit type at [MSDN].
8.2. Library implementation experience
8.2.1. std :: to_integer
is equivalent to
with constraints.
8.2.2. < type_traits >
Assuming that
and
don’t simply delegate to a compiler intrinsic,
implementing these traits merely requires adding two specializations such as
.
See libstdc++'s
.
8.2.3. std :: cmp_xxx
Libstdc++ provides a widthagnostic implementation of
and other safe comparison functions in
.
Being able to extend to a wider type is helpful in principle (e.g. implementing
in terms of a comparison between
s),
however, the current implementations don’t make use of this opportunity anyway.
8.2.4. < bitset >
Note: See § 9.4 Class template bitset for proposed changes.
To implement these changes, a constructor and member function template can be defined:
bitset ( integral auto ); template < unsigned_integral T > T to ();
These are functionally equivalent to the existing
constructor
and
function respectively, just generalized.
8.2.5. < charconv >
libstdc++ already provides a widthagnostic implementation
of
in
,
and a widthagnostic implementation of
in
.
In general, it is not difficult to generalize
for any width.
Stringification uses integer division, which may be a problem.
However, the divisor is constant.
Due to strength reduction optimization (see § 3.1.4 Fixedpoint operations for an example),
no extreme cost is incurred no matter the width.
8.2.6. < format >
libstdc++ already supports
for
.
The localeindependent forms are simply implemented in terms of
and are not
affected by the introduction of 128bit integers.
As explained above,
implementations typically already support 128bit integers.
The new
function is not affected.
This function only checks for the type of a dynamic width or precision, and the arguments are required to be of standard integer type.
Realistically the user will never need a 128bit width or precision,
which is why no changes are proposed.
also requires no changes because
already covers extended integer and floatingpoint types.
Also, modifying the
within a
would be an avoidable ABIbreak.
8.2.7. < bit >
In [BitPermutations], I have implemented the majority of C++ bit manipulation functions
for any width, i.e. in a way that is compatible with
for any
.
Such an extremely generalized implementation is challenging, however, merely extending support to 128bit given a 64bit implementation is simple.
std :: popcount
, a 128bit implementation looks as follows:
int popcount ( uint128_t x ) { return popcount ( uint64_t ( x >> 64 )) + popcount ( uint64_t ( x )); }
std :: countr_zero
, a 128bit implementation looks as follows:
int countr_zero ( uint128_t x ) { int result = countr_zero ( uint64_t ( x )); return result < 64 ? result : 64 + countr_zero ( uint64_t ( x >> 64 )); }
All bit manipulation functions are easily constructed this way.
8.2.8. std :: to_string
Note: See § 9.5 Numeric conversions for proposed changes.
libstdc++ already implements
as an inline function which forwards to
.
In general,
simply needs to forward to
or a similar function,
and this is easily generalized.
8.2.9. std :: gcd
, std :: lcm
libstdc++ provides a
implementation which uses the Binary GCD algorithm.
The MSVC STL has a similar implementation.
This algorithm is easily generalized to any width.
It requires
for an efficient implementation, which is easy to implement
for 128bit integers (see § 8.2.7 <bit>).
libc++ uses a naive
implementation based on the Euclidean Algorithm, which relies on integer division.
Due to the immense cost of integer division for 128bit integers,
such an implementation may need revision.
requires no work because mathematically,
.
When solving for
,
.
The implementation effort (if any) is limited to
.
Note: By dividing by
prior to multiplication, overflow in
is avoided.
Overflow can only occur if
is not representable by the result type.
8.2.10. < random >
requires at least doublewide integers
to safely perform the operation
, where
is the LCG state.
Otherwise, the multiplication and addition could overflow.
libstdc++ solves this issue by performing all operations using
if available
(see
),
and otherwise:
static_assert ( __which < 0 , /* needs to be dependent */ "sorry, would be too much trouble for a slow result" );
Introducing 128bit integers would force implementations to also provide 256bit operations
solely for the purpose of
.
This can be considered reasonable because C23 requires implementations to provide
arbitraryprecision arithmetic anyway, and both GCC and clang already implement
for
(see § 8.1.4 _BitInt(128) for details on support).
8.2.11. std :: midpoint
Libstdc++ has a
implementation which is widthagnostic.
8.2.12. std :: xxx_sat
libstdc++ provides a widthagnostic implementation for all saturating arithmetic functions in
.
Saturating arithmetic is generally done through compiler intrinsics such as
.
These are already supported by GCC and Clang.
A software implementation of overflow detection may be very tedious as explained in [P0543R3], but that isn’t the chosen implementation anyway.
8.2.13. std :: abs
Note: See § 9.7 Absolute values for proposed changes.
can be easily implemented widthagnostically as
for any integer
.
Note that an overload must exist for every integer type to avoid calling
for
floatingpoint types.
Such an overload is proposed in § 9.7 Absolute values.
8.2.14. < cmath >
libstdc++, libc++, and the MSVC STL implement the integral math overloads using SFINAE.
Effectively, they define function templates using
.
Therefore, no changes are required.
8.2.15. std :: valarray
does not rely on any specific bitsize, or on
being any
type in general.
While it is possible to provide specializations for specific types that make
more optimal use of hardware, it is also possible to rely on the
optimizers autovectorization capabilities alone.
8.2.16. < linalg >
The linear algebra library introduced by [P1673R13] does not rely on any specific widths and
is generalized by default.
The corresponding reference implementation can operate on
.
Providing specializations for specific widths is a qualityofimplementation issue.
8.2.17. < cstdio >
Note: This proposal makes 128bit
/
support entirely optional
(see § 9.3 Header <inttypes.h> for wording).
Similar to
, extending support to 128bit integers for printing and parsing
requires only moderate effort because the underlying algorithm easily generalizes to any bit size.
The
et al. macros in
would also need to be defined,
and would expand to an implementationdefined format constant.
LLVM libc currently uses a
function for stringification.
This would require replacement, possibly with a function template.
Other standard libraries may be impacted more significantly.
std :: printf ( "%w128d \n " , std :: int_least128_t { 123 });
8.2.18. std :: atomic
Libc++ already provides support for fetchoperations for
.
For example,
delegates to
in libatomic.
In general, the
operations that
provides
must already have a software fallback for 32bit hardware, where no 64bit
atomic
instruction exists.
Such a software fallback may be implemented as a CASandretry loop.
The introduction of 128bit integers adds no new challenges.
9. Proposed wording
The proposed wording is relative to [CxxDraft], accessed 20240210.
9.1. Header < version >
In subclause 17.3.2 [version.syn], update the featuretesting macros as follows:
#define __cpp_lib_atomic_int128 20XXXX #define __cpp_lib_bitset 202306L 20XXXX #define __cpp_lib_bitset_int128 20XXXX #define __cpp_lib_int128 20XXXX #define __cpp_lib_to_string 202306L 20XXXX #define __cpp_lib_to_string_int128 20XXXX
Note: Featuredetection for
and
is intentionally omitted
because the user can detect whether
,
etc. are defined.
9.2. Header < cstdint >
In subclause 17.4.1 [cstdint.syn], update the synopsis as follows:
// all freestanding namespace std { using int8_t = signed integer type ; // optional using int16_t = signed integer type ; // optional using int32_t = signed integer type ; // optional using int64_t = signed integer type ; // optional using int128_t = signed integer type ; // optional using intN_t = see below ; // optional using int_fast8_t = signed integer type ; using int_fast16_t = signed integer type ; using int_fast32_t = signed integer type ; using int_fast64_t = signed integer type ; using int_fast128_t = signed integer type ; using int_fastN_t = see below ; // optional using int_least8_t = signed integer type ; using int_least16_t = signed integer type ; using int_least32_t = signed integer type ; using int_least64_t = signed integer type ; using int_least128_t = signed integer type ; using int_leastN_t = see below ; // optional using intmax_t = signed integer type ; using intptr_t = signed integer type ; // optional using uint8_t = unsigned integer type ; // optional using uint16_t = unsigned integer type ; // optional using uint32_t = unsigned integer type ; // optional using uint64_t = unsigned integer type ; // optional using uint128_t = unsigned integer type ; // optional using uintN_t = see below ; // optional using uint_fast8_t = unsigned integer type ; using uint_fast16_t = unsigned integer type ; using uint_fast32_t = unsigned integer type ; using uint_fast64_t = unsigned integer type ; using uint_fast128_t = unsigned integer type ; using uint_fastN_t = see below ; // optional using uint_least8_t = unsigned integer type ; using uint_least16_t = unsigned integer type ; using uint_least32_t = unsigned integer type ; using uint_least64_t = unsigned integer type ; using uint_least128_t = unsigned integer type ; using uint_leastN_t = see below ; // optional using uintmax_t = unsigned integer type ; using uintptr_t = unsigned integer type ; // optional }
In subclause 17.4.1 [cstdint.syn], update paragraph 3 as follows:
All types that use the placeholder N are optional when N is not,
8 ,
16 ,
32 or, or
64 . The exactwidth types
128 and
intN_t for N =
uintN_t ,
8 ,
16 ,
32 and, and
64 are also optional; however, if an implementation defines integer types with the corresponding width and no padding bits, it defines the corresponding typedefnames. Each of the macros listed in this subclause is defined if and only if the implementation defines the corresponding typedefname.
128
In subclause 17.4.1 [cstdint.syn], add the following paragraph:
None of the types that use the placeholder N are standard integers types ([basic.fundamental]) if N is greater than 64.
[Example:is an extended integer type.
int_least128_t is an extended integer type or a standard integer type whose width is at least 64. — end example]
int_least64_t
Note: This restriction is intended to address § 6.2 Impact on overload sets.
9.3. Header < inttypes . h >
In subclause 17.14 [support.c.headers], add the following subclause:
17.14.X Header
< inttypes . h > The contents of the C++ header
are the same as the C standard library header
< inttypes . h > with the following exception: The definition of the
< inttypes . h > and
fprintf macros for the corresponding integers in the header
fscanf is optional for any integer with a width greater than 64. However, if any macro for an integer with width N is defined, all macros corresponding to integers with the same or lower width as N shall be defined.
< stdint . h >
See also: ISO/IEC 9899:2018, 7.8.1
Note: This effectively makes
/
128bit support optional because without any
/
macros, the user has no standard way of using these functions with 128bit integers.
Note: After rebasing on C23, additional restrictions to
must be applied
so that
(see [N2680]) is not mandatory in C++.
9.4. Class template bitset
In subclause 22.9.2.1 [template.bitset.general], update the synopsis as follows:
// [bitset.cons], constructors constexpr bitset () noexcept ; constexpr bitset ( unsigned long long val ) noexcept ; constexpr bitset ( integer  least  int val ) noexcept ; [...] constexpr unsigned long to_ulong () const ; constexpr unsigned long long to_ullong () const ; template < class T > constexpr T to () const ;
In subclause 22.9.2.1 [template.bitset.general], add a paragraph:
For each function with a parameter of type integerleastint, the implementation provides an overload for each cvunqualified integer type ([basic.fundamental]) whose conversion rank is that of
or greater, where integerleastint in the function signature is replaced with that integer type.
int
Note: See § 6.4 std::bitset constructor semantic changes for discussion.
In subclause 22.9.2.2 [template.bitset.const], update the constructors as follows:
constexpr bitset ( unsigned long long val ) noexcept ; constexpr bitset ( integer  least  int val ) noexcept ; Effects: Initializes the first
bit positions to the corresponding bit values in val.
M is the smaller of
M and the
N number of bits in the value representationwidth ([basic.types.general]) ofintegerleastint . If
unsigned long long , the remaining bit positions are initialized to
M < N zeroone ifis negative, otherwise to zero .
val
In subclause 22.9.2.3 [bitset.members], make the following changes:
constexpr unsigned long to_ulong () const ;
Returns:.
x
Throws:if the integral value
overflow_error corresponding to the bits in
x cannot be represented as type
* this .
unsigned long constexpr unsigned long long to_ullong () const ; template < class T > constexpr T to () const ; Constraints:
is an unsigned integer type ([basic.fundamental]).
T Returns:
.
x Throws:
if the integral value
overflow_error corresponding to the bits in
x cannot be represented as
* this typethe return type of this function .
unsigned long long
9.5. Numeric conversions
Update subclause 23.4.2 [string.syn] as follows:
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( int_least128_t ); string to_string ( integer  least  int val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val ); [...] wstring to_wstring ( int val ); wstring to_wstring ( unsigned val ); wstring to_wstring ( long val ); wstring to_wstring ( unsigned long val ); wstring to_wstring ( long long val ); wstring to_wstring ( unsigned long long val ); wstring to_wstring ( integer  least  int val ); wstring to_wstring ( float val ); wstring to_wstring ( double val ); wstring to_wstring ( long double val );
In subclause 23.4.2 [string.syn], add a paragraph:
For each function with a parameter of type integerleastint,
the implementation provides an overload for each cvunqualified
integer type ([basic.fundamental]) whose conversion rank is that of int
or greater,
where integerleastint in the function signature is replaced with that integer type.
In subclause 23.4.5 [string.conversions], update
:
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( integer  least  int val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val ); Returns:
.
format ( "{}" , val )
In subclause 23.4.5 [string.conversions], update
:
wstring to_string ( int val ); wstring to_wstring ( unsigned val ); wstring to_wstring ( long val ); wstring to_wstring ( unsigned long val ); wstring to_wstring ( long long val ); wstring to_wstring ( unsigned long long val ); wstring to_wstring ( integer  least  int val ); wstring to_wstring ( float val ); wstring to_wstring ( double val ); wstring to_wstring ( long double val ); Returns:
.
format ( L"{}" , val )
9.6. Iota view
In subclause 26.6.4.2 [ranges.iota.view], update paragraph 1 as follows:
Letbe defined as follows:
IOTA  DIFF  T ( W )
If
is not an integral type, or if it is an integral type and
W is greater than
sizeof ( iter_difference_t < W > ) , then
sizeof ( W ) denotes
IOTA  DIFF  T ( W ) .
iter_difference_t < W > Otherwise,
is a signed standard integer type of width greater than the width of
IOTA  DIFF  T ( W ) if such a type exists.
W Otherwise,
is an unspecified signedintegerlike type ([iterator.concept.winc]) of width not less than the width of
IOTA  DIFF  T ( W ) .
W
[Note 1: It is unspecified whether this type satisfies. — end note]
weakly_incrementable
Note: This change resolves the potential ABI break explained in § 5.2 std::ranges::iota_view ABI issue.
This change purely increases implementor freedom.
An extended integer type still models signedintegerlike, so GCC’s existing implementation
using
remains valid.
However, a wider extended integer type is no longer the mandatory difference type (if it exists)
as per the second bullet.
9.7. Absolute values
In subclause 28.7.1 [cmath.syn], update the synopsis as follows:
// [c.math.abs], absolute values constexpr int abs ( int j ); // freestanding constexpr long int abs ( long int j ); // freestanding constexpr long long int abs ( long long int j ); // freestanding constexpr signed  integer  least  int abs ( signed  integer  least  int j ); // freestanding constexpr floating  point  type abs ( floating  point  type j ); // freestandingdeleted
In subclause 28.7.1 [cmath.syn], add a paragraph after paragraph 2:
For each function with a parameter of type signedintegerleastint,
the implementation provides an overload for each cvunqualified
signed integer type ([basic.fundamental]) whose conversion rank is that of int
or greater,
where all uses of signedintegerleastint in the function signature are replaced with that
signed integer type.
In subclause 28.7.2 [c.math.abs], make the following changes:
constexpr int abs ( int j ); constexpr long int abs ( long int j ); constexpr long long int abs ( long long int j ); constexpr signed  integer  least  int abs ( signed  integer  least  int j );
Effects: These functions have the semantics specified in the C standard library for the functionsReturns:,
abs , and
labs , respectively.
llabs .
j >= 0 ? j :  j ;
Note:
matches the semantics of the C functions exactly,
even in undefined cases like
.
Note: The floatingpoint overload set is intentionally not redefined to return
.
This expression is not equivalent to clearing the sign bit.
9.8. Header < cinttypes >
In subclause 31.13.2 [cinttypes.syn], update paragraph 1 as follows:
The contents and meaning of the headerare the same as the
< cinttypes > C standard libraryC++ header, with the following changes:
< inttypes . h >
Note: Unlike the C standard library header, the C++ header has the changes described in § 9.3 Header <inttypes.h> applied.
9.9. Atomic operations
In subclause 33.5.2 [atomics.syn], update the synopsis as follows:
// all freestanding namespace std { [...] using atomic_int8_t = atomic < int8_t > ; // freestanding using atomic_uint8_t = atomic < uint8_t > ; // freestanding using atomic_int16_t = atomic < int16_t > ; // freestanding using atomic_uint16_t = atomic < uint16_t > ; // freestanding using atomic_int32_t = atomic < int32_t > ; // freestanding using atomic_uint32_t = atomic < uint32_t > ; // freestanding using atomic_int64_t = atomic < int64_t > ; // freestanding using atomic_uint64_t = atomic < uint64_t > ; // freestanding using atomic_int128_t = atomic < int128_t > ; // freestanding using atomic_uint128_t = atomic < uint128_t > ; // freestanding using atomic_int_least8_t = atomic < int_least8_t > ; // freestanding using atomic_uint_least8_t = atomic < uint_least8_t > ; // freestanding using atomic_int_least16_t = atomic < int_least16_t > ; // freestanding using atomic_uint_least16_t = atomic < uint_least16_t > ; // freestanding using atomic_int_least32_t = atomic < int_least32_t > ; // freestanding using atomic_uint_least32_t = atomic < uint_least32_t > ; // freestanding using atomic_int_least64_t = atomic < int_least64_t > ; // freestanding using atomic_uint_least64_t = atomic < uint_least64_t > ; // freestanding using atomic_int_least128_t = atomic < int_least128_t > ; // freestanding using atomic_uint_least128_t = atomic < uint_least128_t > ; // freestanding using atomic_int_fast8_t = atomic < int_fast8_t > ; // freestanding using atomic_uint_fast8_t = atomic < uint_fast8_t > ; // freestanding using atomic_int_fast16_t = atomic < int_fast16_t > ; // freestanding using atomic_uint_fast16_t = atomic < uint_fast16_t > ; // freestanding using atomic_int_fast32_t = atomic < int_fast32_t > ; // freestanding using atomic_uint_fast32_t = atomic < uint_fast32_t > ; // freestanding using atomic_int_fast64_t = atomic < int_fast64_t > ; // freestanding using atomic_uint_fast64_t = atomic < uint_fast64_t > ; // freestanding using atomic_int_fast128_t = atomic < int_fast128_t > ; // freestanding using atomic_uint_fast128_t = atomic < uint_fast128_t > ; // freestanding [...] }
10. Acknowledgements
I thank Jonathan Wakely and other participants in the stdproposals mailing list whose feedback has helped me improve the quality of this proposal substantially.
I also thank Lénárd Szolnoki for contributing the example in § 2.1 Lifting library restrictions.
Note: See [stdproposals] for discussion of this proposal.