Supporting signed char and unsigned char
in random number generation
- Document number:
- P4037R0
- Date:
2026-03-06 - Audience:
- LEWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P4037/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/random-chars.cow
or in
currently results in undefined behavior.
Instead, it should be permitted.
There are further minor defects resulting from the restrictions in [rand.req.genl],
which this paper also addresses.
NB comment [RU-272] is resolved by this paper.
Contents
Introduction
Motivation
Design
Which types to support
Proposed IntType restrictions
Proposed RealType restrictions
Don't require character types
Don't require all extended integer types
Drive-by-fix for std::uint_leastN_t and std::uint_fastN_t
Treating engines and distributions differently
Implementation experience
Wording
References
1. Introduction
While it may seem perfectly reasonable to generate random bytes
in the form of ,
[rand.req.genl] requires that where
appears as a template parameter name (such as in ),
providing as an argument causes template instantiation
to have undefined behavior.
This is functionally equivalent to making the program ill-formed, no diagnostic required.
It should also be noted that many users rely on support for 8-bit types
in already, despite the undefined behavior.
A GitHub code search for
.
This is possible because both libstdc++ and libc++ support those types as an extension;
see §4. Implementation experience.
In 2013,
[LWG2326]
stated that
should be permittedit's just silly that we have a random number library with no natural way to generate random bytes
.
As silly as it may be,
the issue was closed as NAD (not a defect)
because it was perceived as a feature request rather than the resolution of a defect.
Writing a paper was considered,
but that paper never manifested.
In 2025,
[LWG4109] Instantiating templates in §[rand]
with
addressed the same problem,
and addresses NB comment [RU-272].
Various comments on [LWG4109] point out that support for
extended integer types and extended floating-point types would be useful,
and that many of the restrictions could be converted from compile-time UB
(effectively IFNDR)
to Mandates, i.e. / is undefined behavior.
This paper resolves all these known defects and discrepancies of [rand.req.genl] in one fell swoop.
exists in the first place.
The restriction dates back at least to [N1932], published 2006.
Discussion on [std-proposals]
suggests that it may exist because engines with 8-bit types
(like )
have dreadful statistical properties.
2. Motivation
Support for and
in distributions would be useful.
The ability to generate random bytes or octets is extremely valuable for
fuzz testing.
For example, when testing an implementation of an
LZ77 or LZ78 compression algorithm,
a user would typically
- generate a random sequence of bytes,
- compress that sequence using their algorithm,
- decompress the result, and
- compare whether the decompressed bytes are the same as the original bytes.
It is easy to imagine testing methodology that involves random bytes for any kind of codec, network protocol, compiler, syntax highlighter, etc.
is functionally equivalent to
.
3. Design
3.1. Which types to support
The current restriction which forbids is bad,
but it's not obvious how much the restriction should be relaxed.
While [LWG4109] offers the option to make the program ill-formed
when the existing restrictions are not satisfied
(Option B
in the issue),
it fails to mention that this would break
thousands of files of existing code;
see §1. Introduction.
We have to permit implementations to keep their current extensions,
without requiring them to support more than is reasonable.
We either have to continue treating anything other than the supported set of types
as IFNDR (Option A
in the issue),
or carve out an optionally supported set of types (proposed).
3.1.1. Proposed IntType restrictions
In short, the new restriction for parameters
is to ensure support for standard integers
(including and ),
allow the implementation to support further integral types
(such as extended integer types),
and reject anything that is not even an integral type.
However, some extended integer types are also required to be supported
to prevent highly defective behavior.
The following sections provide more details,
and the following diagram illustrates the behavior:
┌──────────────────────────────────────────────────────────────┐ │ ┌────────────────────────────────────────────────────────┐ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ REQUIRED: standard ints., some extended ints. │ │ │ │ │ │ e.g. unsigned char, int, int32_t, etc. │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ OPTIONAL: cv-unqualified integral types │ │ │ │ e.g. __int128, char32_t, etc. │ │ │ └────────────────────────────────────────────────────────┘ │ │ REJECTED: anything else │ │ e.g. std::vector, double, enum, const int, etc. │ └──────────────────────────────────────────────────────────────┘
The restrictions are the same,
except that signed types fall into the anything else
set.
3.1.2. Proposed RealType restrictions
The same restriction is mirrored for parameters:
there is mandatory support for , , and , and
optional support extended floating-point types.
┌──────────────────────────────────────────────────────────────┐ │ ┌────────────────────────────────────────────────────────┐ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ REQUIRED: cv-unqualified standard floating-point │ │ │ │ │ │ float, double, and long double │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ OPTIONAL: cv-unqualified extended floating-point │ │ │ │ e.g. float32_t, float128_t, bfloat16_t, etc. │ │ │ └────────────────────────────────────────────────────────┘ │ │ REJECTED: anything else │ │ e.g. std::vector, int, enum, const double, etc. │ └──────────────────────────────────────────────────────────────┘
3.2. Don't require character types
Supporting use of character types
(e.g. , , etc.)
is not motivated by this paper and
outside the scope of [RU-272].
It is not proposed,
despite character types being integral types,
despite seeming vaguely useful, and
despite GCC having support for this already (see §4. Implementation experience).
Such an extension could be proposed in a future paper targeting C++29.
Supporting only in addition to standard integer would also be strange,
although there is precedent for this in .
If is supported, why not character types in general?
The design becomes somewhat arbitrary.
or as ,
even if has the correct signedness.
3.3. Don't require all extended integer types
Supporting extended integer types in general
imposes an unreasonable implementation burden.
One of the biggest issues is
assuming the implementation provides an extended 128-bit integer type,
which recent versions of GCC and Clang do.
libstdc++ permits in most of ,
but rejects
using:
static_assert ( __which < 0 , /* needs to be dependent */ " sorry, would be too much trouble for a slow result " ) ;
The difficulty lies in the fact that
uses modular arithmetic,
which requires N×2-bit division for N-bit integer types.
For ,
the existence of makes this easy,
but for , 256-bit division would be required.
However, some extended integer types ought to be supported; more on that in the following section.
3.4. Drive-by-fix for std::uint_leastN_t and std::uint_fastN_t
Since we are changing the set of types
that and support,
we can also fix another defect as a drive-by product:
[rand] uses the type aliases
, ,
code{uint_fast32_t}, and
in a number of places,
but there is no guarantee that those are aliases for standard integer types
rather than extended integer types.
was an alias for some extended integer type.
While it is obvious that we don't want the use of
to have undefined behavior,
it is not obvious how to fix the problem.
Allowing all extended integer types would include ,
which we don't want to support, as explained above.
A good compromise is to support all integer types whose width
at least that of and at most that of .
This ensures that , ,
and many other types are supported by ,
regardless of whether they are standard integer types,
assuming they don't have an extreme width
(which the user can easily test using ).
This compromise is also future-proof for bit-precise integers (see [P3666R3]),
which could introduce extreme widths like or
that we may not want to support.
3.5. Treating engines and distributions differently
While the paper is motivated by supporting distributions,
engines such as
also use template parameters and thus disallow .
Using 8-bit types for engines is mostly unmotivated because the quality
of the generated numbers would be extremely low anyway.
That being said, using an 8-bit to get quick and dirty
random number generation on an 8-bit architecture is not inconceivable,
and
GitHub code search
finds uses of .
I argue that and should be supported
unilaterally in
(not just for distributions)
for the following reasons:
- There are existing uses of 8-bit engine types, and we don't need to break this existing code, however questionable that existing code may be.
-
It is unlikely that support for 8-bit types would cause ordinary users to
accidentally create an engine of low quality.
Ordinary users don't create their own engines with custom parameters;
they typically reach for
and other predefined aliases.std :: mt19937 - Creating an engine of extremely low quality is useful for statistical libraries that measure said quality. Sometimes, low quality is the goal, not a mistake.
-
A user can create a low-quality engine using both
andlinear_congruential_engine < uint8_t , 2 u , 3 u , 8 u > . The statistical quality is determined by the engine parameters, not by the type.linear_congruential_engine < uint64_t , 2 u , 3 u , 8 u > -
None of the other numerical templates in
or<numeric> single out a specific integer width as unsupported. This design is very surprising and actively hostile to generic programming.<cmath>
template type parameters accept .
4. Implementation experience
At the time of writing,
the MSVC STL has a which prevents instantiation
of .
It provides the minimum amount of support required by the standard; see:
https://github.com/microsoft/STL/blob/9b021cf66875d2fbf4627aad8db594595aebb148/stl/inc/random#L43
libc++ supports and as an extension.
This is the minimum amount of support assuming this paper is accepted; see:
https://github.com/llvm/llvm-project/blob/9d075d271ff15ec153ada3413d294540206a15ed/libcxx/include/__random/is_valid.h#L49.
libstdc++ supports any integral type as an extension; see:
https://github.com/gcc-mirror/gcc/blob/0635bfb53a145cc005a15657635abf8ea9e6e9ba/libstdc%2B%2B-v3/include/bits/uniform_int_dist.h#L90-L91.
5. Wording
The changes are relative to [N5032].
andsigned char are not supported.unsigned char and other such types are not guaranteed to be supported.uint_least32_t -
The effect of instantiating templates is undefined,
but many of the requirements (e.g. cv-unqualified types) are diagnosable;
even when they aren't, the wording idiom
ill-formed, no diagnostic required
is more fitting.
Change [rand.req.genl] paragraph 1 as follows:
Throughout [rand], the effect of instantiating a template has the following constraints:
-
thatThe program is ill-formed if has a template type parameter namedSseq is undefined unlessand the corresponding template argument is cv-unqualifiedand meetsor does not meet the requirements of seed sequence ([rand.req.seedseq]); no diagnostic is required for the latter condition. -
thatThe program is ill-formed if has a template type parameter namedURBG is undefined unlessand the corresponding template argument is cv-unqualifiedand meetsor does not meet the requirements of uniform random bit generator ([rand.req.urng]); no diagnostic is required for the latter condition. -
thatThe program is ill-formed if has a template type parameter namedEngine is undefined unlessand the corresponding template argument is cv-unqualifiedand meetsor does not meet the requirements of random number engine ([rand.req.eng]); no diagnostic is required for the latter condition. -
thatThe program is ill-formed if has a template type parameter namedRealType is undefined unlessand the corresponding template argumentis cv-unqualified and is one ofis not a cv-unqualified floating-point type ([basic.fundamental]). It is implementation-defined whether the program is ill-formed if is an extended floating-point type.,float , ordouble long double -
thatThe program is ill-formed if has a template type parameter namedIntType is undefined unlessand the corresponding template argumentis cv-unqualified and is one of,short ,int ,long ,long long ,unsigned short ,unsigned int , orunsigned long unsigned long long is not a cv-unqualified integral type ([basic.fundamental]). It is implementation-defined whether the program is ill-formed ifA is not a signed or unsigned integer type or if the width ofA is less than that ofA or more than that ofchar .long long -
thatThe program is ill-formed if has a template type parameter namedUIntType is undefined unlessand the corresponding template argumentis cv-unqualified and is one of,short ,int ,long ,long long ,unsigned short ,unsigned int , orunsigned long unsigned long long is not a cv-unqualified integral type ([basic.fundamental]) or ifA is ill-formed oris_signed_v < A > . It is implementation-defined whether the program is ill-formed iftrue is not an unsigned integer type or if the width ofA is less than that ofA or more than that ofchar .long long