P3791R0: constexpr (deterministic) random
This paper proposes making all deterministic algorithms and types associated with random number generators constant evaluatable. This paper doesn't propose making random_device
type, rand()
and srand()
constexpr
functions as these are not deterministic. Your builds won't be nondeterministic if this paper is merged into the standard.
Motivation
Algorithms shuffle
, sample
are useful even in compile time, same applies to random distribution types which are fully deterministic.
Making these functions constexpr
limits the need for users to reimplement these and use if consteval
, limiting number of errors due duplication of code.
Probabilistic data structures
Special data structures types which use minimal amount of memory to classify items. They need random sampling. Users shouldn't reimplement sampling algorithms just because they can't use std::ranges::sample
.
constexpr sample_to_sketch(std::span<const T> items, sketch<T> & sk) noexcept {
// BEFORE: constructing object without non-constexpr constructor in constant evaluation
// AFTER: fine
auto engine = std::mt19937_64{/* default seed is fine*/};
// BEFORE: error: calling a non-constexpr function in constant evaluation
// AFTER: fine
std::ranges::sample(items, std::insert_iterator(sk), sk.capacity(), engine);
}
Consteval tests
Constant evaluatability of more code opens doors to run tests during compilation with constant evaluator, which also checks for undefined behaviour. When we will be able to measure proper path test coverage, including boundaries which introduce undefined behaviour in code. Reaching 100% such path coverage will allow us to detect all possible UB occurences. If there is still some UB reachable, constant evaluator will detect during test evaluation and fail to evaluate.
Implementation experience
All types and algorithms touched by this proposal are already template
-ed. Making these constexpr
is mostly exercise in marking these with keyword constexpr
. I talked with various implementors and they don't see any problem implementing this proposal.
Implementation in libc++
It can be found at my github, it will be available at compiler explorer once necessory changes there will be approved. Whole change was straightforward and without any problem.
Research of libstdc++
All code is in header (also bits/header.h and bits/header.tcc). I don't expect any problem.
Research of MSSTL
Similar situation situation, all defined in <random> header file, but there are few mathematical functions defined in .cpp
files, like: _XLgamma
.
Wording
What is changed and what is not
All deterministic algorithms and types in or depending on <random> should be constant evaluatable. This excludes random_device
type and functions rand()
and srand()
.
This proposal also excludes iostream compatibility functions operator<<
and operator>>
as iostream
types are not constexpr
compatible yet (they will be in future proposal P3758).
Header <algorithm> synopsis
26.4 Header <algorithm> synopsis [algorithm.syn]
Sample algorithm
26.7.12 Sample [alg.random.sample]
template<class PopulationIterator, class SampleIterator,
class Distance, class UniformRandomBitGenerator>
constexpr SampleIterator sample(PopulationIterator first, PopulationIterator last,
SampleIterator out, Distance n,
UniformRandomBitGenerator&& g);
template<input_iterator I, sentinel_for<I> S, weakly_incrementable O, class Gen>
requires (forward_iterator<I> || random_access_iterator<O>) &&
indirectly_copyable<I, O> &&
uniform_random_bit_generator<remove_reference_t<Gen>>
constexpr O ranges::sample(I first, S last, O out, iter_difference_t<I> n, Gen&& g);
template<input_range R, weakly_incrementable O, class Gen>
requires (forward_range<R> || random_access_iterator<O>) &&
indirectly_copyable<iterator_t<R>, O> &&
uniform_random_bit_generator<remove_reference_t<Gen>>
constexpr O ranges::sample(R&& r, O out, range_difference_t<R> n, Gen&& g);
- SampleIterator meets the Cpp17RandomAccessIterator requirements ([random.access.iterators]) unless PopulationIterator models forward_iterator ([iterator.concept.forward]).
- remove_reference_t<UniformRandomBitGenerator> meets the requirements of a uniform random bit generator type ([rand.req.urng]).
- For the overload in namespace std, stable if and only if PopulationIterator models forward_iterator.
- To the extent that the implementation of this function makes use of random numbers, the object g serves as the implementation's source of randomness.
Shuffle algorithm
26.7.13 Shuffle [alg.random.shuffle]
template<class RandomAccessIterator, class UniformRandomBitGenerator>
constexpr void shuffle(RandomAccessIterator first,
RandomAccessIterator last,
UniformRandomBitGenerator&& g);
template<random_access_iterator I, sentinel_for<I> S, class Gen>
requires permutable<I> &&
uniform_random_bit_generator<remove_reference_t<Gen>>
constexpr I ranges::shuffle(I first, S last, Gen&& g);
template<random_access_range R, class Gen>
requires permutable<iterator_t<R>> &&
uniform_random_bit_generator<remove_reference_t<Gen>>
constexpr borrowed_iterator_t<R> ranges::shuffle(R&& r, Gen&& g);
- The type remove_reference_t<UniformRandomBitGenerator> meets the uniform random bit generator ([rand.req.urng]) requirements.
Distributions and generators
29.5 Random number generation [rand]
29.5.1 General [rand.general]
- as boolean or equivalently as boolean-valued, if T is bool;
- otherwise as integral or equivalently as integer-valued, if numeric_limits<T>::is_integer is true;
- otherwise as floating-point or equivalently as real-valued.
29.5.2 Header <random> synopsis [rand.synopsis]
29.5.3 Requirements [rand.req]
29.5.3.1 General requirements [rand.req.genl]
- that has a template type parameter named Sseq is undefined unless the corresponding template argument is cv-unqualified and meets the requirements of seed sequence.
- that has a template type parameter named URBG is undefined unless the corresponding template argument is cv-unqualified and meets the requirements of uniform random bit generator.
- that has a template type parameter named Engine is undefined unless the corresponding template argument is cv-unqualified and meets the requirements of random number engine.
- that has a template type parameter named RealType is undefined unless the corresponding template argument is cv-unqualified and is one of float, double, or long double.
- that has a template type parameter named IntType is undefined unless the corresponding template argument is cv-unqualified and is one of short, int, long, long long, unsigned short, unsigned int, unsigned long, or unsigned long long.
- that has a template type parameter named UIntType is undefined unless the corresponding template argument is cv-unqualified and is one of unsigned short, unsigned int, unsigned long, or unsigned long long.
29.5.3.2 Seed sequence requirements [rand.req.seedseq]
- T is the type named by S's associated result_type;
- q is a value of type S and r is a value of type S or const S;
- ib and ie are input iterators with an unsigned integer value_type of at least 32 bits;
- rb and re are mutable random access iterators with an unsigned integer value_type of at least 32 bits;
- ob is an output iterator; and
- il is a value of type initializer_list<T>.
Expression | Return type | Pre/post-condition | Complexity |
S::result_type | T | ||
S() | Creates a seed sequence
with the same initial state as all other default-constructed seed sequences
of type S. | constant | |
S(ib,ie) | Creates a seed sequence
having internal state
that depends on some or all of the bits
of the supplied sequence [ib,ie). | ||
S(il) | Same as S(il.begin(), il.end()). | same as S(il.begin(), il.end()) | |
q.generate(rb,re) | void | ||
r.size() | size_t | The number of 32-bit units
that would be copied
by a call to r.param. | constant |
r.param(ob) | void | Copies to the given destination
a sequence of 32-bit units
that can be provided
to the constructor of a second object of type S,
and that would reproduce in that second object
a state indistinguishable
from the state of the first object. |
29.5.3.3 Uniform random bit generator requirements [rand.req.urng]
29.5.3.4 Random number engine requirements [rand.req.eng]
- the size of E's state in multiples of the size of result_type, given as an integral constant expression;
- the transition algorithm TA by which e's state e is advanced to its successor state e; and
- the generation algorithm GA by which an engine's state is mapped to a value of type result_type.
- T is the type named by E's associated result_type;
- e is a value of E, v is an lvalue of E, x and y are (possibly const) values of E;
- s is a value of T;
- q is an lvalue meeting the requirements of a seed sequence;
- z is a value of type unsigned long long;
- os is an lvalue of the type of some class template specialization basic_ostream<charT, traits>; and
- is is an lvalue of the type of some class template specialization basic_istream<charT, traits>;
Expression | Return type | Pre/post-condition | Complexity |
E() | Creates an engine
with the same initial state
as all other default-constructed engines
of type E. | ||
E(x) | Creates an engine
that compares equal to x. | ||
E(s) | Creates an engine
with initial state determined by s. | ||
E(q)241 | Creates an engine
with an initial state
that depends on a sequence
produced by one call
to q.generate. | same as complexity of q.generate
called on a sequence
whose length is size of state | |
e.seed() | void | Postconditions: e == E(). | same as E() |
e.seed(s) | void | Postconditions: e == E(s). | same as E(s) |
e.seed(q) | void | Postconditions: e == E(q). | same as E(q) |
e() | T | per [rand.req.urng] | |
e.discard(z)242 | void | no worse than the complexity
of z consecutive calls e() | |
x == y | bool | ||
x != y | bool | !(x == y). |
os << x
is >> v
29.5.3.5 Random number engine adaptor requirements [rand.req.adapt]
A::A();
bool operator==(const A& a1, const A& a2);
A::A(result_type s);
template<class Sseq> A::A(Sseq& q);
void seed();
void seed(result_type s);
template<class Sseq> void seed(Sseq& q);
- The complexity of each function shall not exceed the complexity of the corresponding function applied to the base engine.
- Copying A's state (e.g., during copy construction or copy assignment) shall include copying the state of the base engine of A.
- The textual representation of A shall include the textual representation of its base engine.
29.5.3.6 Random number distribution requirements [rand.req.dist]
- T is the type named by D's associated result_type;
- P is the type named by D's associated param_type;
- d is a value of D, and x and y are (possibly const) values of D;
- glb and lub are values of T respectively corresponding to the greatest lower bound and the least upper bound on the values potentially returned by d's operator(), as determined by the current values of d's parameters;
- p is a (possibly const) value of P;
- g, g1, and g2 are lvalues of a type meeting the requirements of a uniform random bit generator;
- os is an lvalue of the type of some class template specialization basic_ostream<charT, traits>; and
- is is an lvalue of the type of some class template specialization basic_istream<charT, traits>;
Expression | Return type | Pre/post-condition | Complexity |
D::result_type | T | ||
D::param_type | P | ||
D() | Creates a distribution whose behavior is indistinguishable
from that of any other newly default-constructed distribution
of type D. | constant | |
D(p) | Creates a distribution whose behavior is indistinguishable
from that of a distribution
newly constructed directly from the values used to construct p. | same as p's construction | |
d.reset() | void | constant | |
x.param() | P | no worse than the complexity of D(p) | |
d.param(p) | void | Postconditions: d.param() == p. | no worse than the complexity of D(p) |
d(g) | T | With ,
the sequence of numbers
returned by successive invocations
with the same object g
is randomly distributed
according to the associated
p(z |{p})
or
function. | amortized constant number of invocations of g |
d(g,p) | T | The sequence of numbers
returned by successive invocations
with the same objects g and p
is randomly distributed
according to the associated
p(z |{p})
or
function. | amortized constant number of invocations of g |
x.min() | T | Returns glb. | constant |
x.max() | T | Returns lub. | constant |
x == y | bool | constant | |
x != y | bool | !(x == y). | same as x == y. |
os << x
is >> d
29.5.4 Random number engine class templates [rand.eng]
29.5.4.1 General [rand.eng.general]
- if the constructor template<class Sseq> explicit X(Sseq& q); is called with a type Sseq that does not qualify as a seed sequence, then this constructor shall not participate in overload resolution;
- if the member function template<class Sseq> void seed(Sseq& q); is called with a type Sseq that does not qualify as a seed sequence, then this function shall not participate in overload resolution.
29.5.4.2 Class template linear_congruential_engine [rand.eng.lcong]
constexpr explicit linear_congruential_engine(result_type s);
template<class Sseq> constexpr explicit linear_congruential_engine(Sseq& q);
29.5.4.3 Class template mersenne_twister_engine [rand.eng.mers]
constexpr explicit mersenne_twister_engine(result_type value);
template<class Sseq> constexpr explicit mersenne_twister_engine(Sseq& q);
29.5.4.4 Class template subtract_with_carry_engine [rand.eng.sub]
constexpr explicit subtract_with_carry_engine(result_type value);
template<class Sseq> constexpr explicit subtract_with_carry_engine(Sseq& q);
29.5.4.5 Class template philox_engine [rand.eng.philox]
- X is the interpretation of the unsigned integer counter value of bits,
- K are keys, which are generated once from the seed (see constructors below) and remain constant unless the seed function ([rand.req.eng]) is invoked,
- Y stores a batch of output values, and
- i is an index for an element of the sequence Y.
- The output sequence of the previous round (X in case of the first round) is permuted to obtain the intermediate state V: where and is defined in Table 127.
- The following computations are applied to the elements of the V sequence:
where:
- mullo(a, b, w) is the low half of the modular multiplication of a and b: ,
- mulhi(a, b, w) is the high half of the modular multiplication of a and b: ,
- is the index in the sequences,
- is the index of the round,
- is the round key for round q, ,
- are the elements of the key sequence K,
- is multipliers[k], and
- is round_consts[k].
constexpr explicit philox_engine(result_type value);
template<class Sseq> constexpr explicit philox_engine(Sseq& q);
constexpr void set_counter(const array<result_type, n>& c);
29.5.5 Random number engine adaptor class templates [rand.adapt]
29.5.5.1 General [rand.adapt.general]
29.5.5.2 Class template discard_block_engine [rand.adapt.disc]
29.5.5.3 Class template independent_bits_engine [rand.adapt.ibits]
29.5.5.4 Class template shuffle_order_engine [rand.adapt.shuf]
29.5.6 Engines and engine adaptors with predefined parameters [rand.predef]
using minstd_rand0 =
linear_congruential_engine<uint_fast32_t, 16'807, 0, 2'147'483'647>;
using minstd_rand =
linear_congruential_engine<uint_fast32_t, 48'271, 0, 2'147'483'647>;
using mt19937 =
mersenne_twister_engine<uint_fast32_t, 32, 624, 397, 31,
0x9908'b0df, 11, 0xffff'ffff, 7, 0x9d2c'5680, 15, 0xefc6'0000, 18, 1'812'433'253>;
using mt19937_64 =
mersenne_twister_engine<uint_fast64_t, 64, 312, 156, 31,
0xb502'6f5a'a966'19e9, 29, 0x5555'5555'5555'5555, 17,
0x71d6'7fff'eda6'0000, 37, 0xfff7'eee0'0000'0000, 43, 6'364'136'223'846'793'005>;
using ranlux24_base =
subtract_with_carry_engine<uint_fast32_t, 24, 10, 24>;
using ranlux48_base =
subtract_with_carry_engine<uint_fast64_t, 48, 5, 12>;
using ranlux24 = discard_block_engine<ranlux24_base, 223, 23>;
using ranlux48 = discard_block_engine<ranlux48_base, 389, 11>;
using knuth_b = shuffle_order_engine<minstd_rand0,256>;
using philox4x32 =
philox_engine<uint_fast32_t, 32, 4, 10,
0xCD9E8D57, 0x9E3779B9, 0xD2511F53, 0xBB67AE85>;
using philox4x64 =
philox_engine<uint_fast64_t, 64, 4, 10,
0xCA5A826395121157, 0x9E3779B97F4A7C15, 0xD2E7470EE14C6C93, 0xBB67AE8584CAA73B>;
29.5.7 Class random_device [rand.device]
explicit random_device(const string& token);
double entropy() const noexcept;
result_type operator()();
29.5.8 Utilities [rand.util]
29.5.8.1 Class seed_seq [rand.util.seedseq]
constexpr seed_seq() noexcept;
template<class T>
constexpr seed_seq(initializer_list<T> il);
template<class InputIterator>
constexpr seed_seq(InputIterator begin, InputIterator end);
template<class RandomAccessIterator>
constexpr void generate(RandomAccessIterator begin, RandomAccessIterator end);
- By way of initialization, set each element of the range to the value 0x8b8b8b8b.Additionally, for use in subsequent steps, let and let , where
- With m as the larger of and n, transform the elements of the range: iteratively for , calculate values
and, in order, increment begin[] by , increment begin[] by , and set begin[k] to . - Transform the elements of the range again, beginning where the previous step ended: iteratively for , calculate values
and, in order, update begin[] by xoring it with , update begin[] by xoring it with , and set begin[k] to .
constexpr size_t size() const noexcept;
template<class OutputIterator>
constexpr void param(OutputIterator dest) const;
29.5.8.2 Function template generate_canonical [rand.util.canonical]
template<class RealType, size_t digits, class URBG>
constexpr RealType generate_canonical(URBG& g);
29.5.9 Random number distribution class templates [rand.dist]
29.5.9.1 General [rand.dist.general]
29.5.9.2 Uniform distributions [rand.dist.uni]
29.5.9.2.1 Class template uniform_int_distribution [rand.dist.uni.int]
constexpr explicit uniform_int_distribution(IntType a, IntType b = numeric_limits<IntType>::max());
constexpr result_type a() const;
constexpr result_type b() const;
29.5.9.2.2 Class template uniform_real_distribution [rand.dist.uni.real]
constexpr explicit uniform_real_distribution(RealType a, RealType b = 1.0);
constexpr result_type a() const;
constexpr result_type b() const;
29.5.9.3 Bernoulli distributions [rand.dist.bern]
29.5.9.3.1 Class bernoulli_distribution [rand.dist.bern.bernoulli]
constexpr explicit bernoulli_distribution(double p);
constexpr double p() const;
29.5.9.3.2 Class template binomial_distribution [rand.dist.bern.bin]
constexpr explicit binomial_distribution(IntType t, double p = 0.5);
constexpr IntType t() const;
constexpr double p() const;
29.5.9.3.3 Class template geometric_distribution [rand.dist.bern.geo]
constexpr explicit geometric_distribution(double p);
constexpr double p() const;
29.5.9.3.4 Class template negative_binomial_distribution [rand.dist.bern.negbin]
constexpr explicit negative_binomial_distribution(IntType k, double p = 0.5);
constexpr IntType k() const;
constexpr double p() const;
29.5.9.4 Poisson distributions [rand.dist.pois]
29.5.9.4.1 Class template poisson_distribution [rand.dist.pois.poisson]
constexpr explicit poisson_distribution(double mean);
constexpr double mean() const;
29.5.9.4.2 Class template exponential_distribution [rand.dist.pois.exp]
constexpr explicit exponential_distribution(RealType lambda);
constexpr RealType lambda() const;
29.5.9.4.3 Class template gamma_distribution [rand.dist.pois.gamma]
constexpr explicit gamma_distribution(RealType alpha, RealType beta = 1.0);
constexpr RealType alpha() const;
constexpr RealType beta() const;
29.5.9.4.4 Class template weibull_distribution [rand.dist.pois.weibull]
constexpr explicit weibull_distribution(RealType a, RealType b = 1.0);
constexpr RealType a() const;
constexpr RealType b() const;
29.5.9.4.5 Class template extreme_value_distribution [rand.dist.pois.extreme]
constexpr explicit extreme_value_distribution(RealType a, RealType b = 1.0);
constexpr RealType a() const;
constexpr RealType b() const;
29.5.9.5 Normal distributions [rand.dist.norm]
29.5.9.5.1 Class template normal_distribution [rand.dist.norm.normal]
constexpr explicit normal_distribution(RealType mean, RealType stddev = 1.0);
constexpr RealType mean() const;
constexpr RealType stddev() const;
29.5.9.5.2 Class template lognormal_distribution [rand.dist.norm.lognormal]
constexpr explicit lognormal_distribution(RealType m, RealType s = 1.0);
constexpr RealType m() const;
constexpr RealType s() const;
29.5.9.5.3 Class template chi_squared_distribution [rand.dist.norm.chisq]
constexpr explicit chi_squared_distribution(RealType n);
constexpr RealType n() const;
29.5.9.5.4 Class template cauchy_distribution [rand.dist.norm.cauchy]
constexpr explicit cauchy_distribution(RealType a, RealType b = 1.0);
constexpr RealType a() const;
constexpr RealType b() const;
29.5.9.5.5 Class template fisher_f_distribution [rand.dist.norm.f]
constexpr explicit fisher_f_distribution(RealType m, RealType n = 1);
constexpr RealType m() const;
constexpr RealType n() const;
29.5.9.5.6 Class template student_t_distribution [rand.dist.norm.t]
constexpr explicit student_t_distribution(RealType n);
constexpr RealType n() const;
29.5.9.6 Sampling distributions [rand.dist.samp]
29.5.9.6.1 Class template discrete_distribution [rand.dist.samp.discrete]
constexpr discrete_distribution();
template<class InputIterator>
constexpr discrete_distribution(InputIterator firstW, InputIterator lastW);
constexpr discrete_distribution(initializer_list<double> wl);
template<class UnaryOperation>
constexpr discrete_distribution(size_t nw, double xmin, double xmax, UnaryOperation fw);
constexpr vector<double> probabilities() const;
29.5.9.6.2 Class template piecewise_constant_distribution [rand.dist.samp.pconst]
in which the values , commonly known as the weights, shall be non-negative, non-NaN, and non-infinity.
constexpr piecewise_constant_distribution();
template<class InputIteratorB, class InputIteratorW>
constexpr piecewise_constant_distribution(InputIteratorB firstB, InputIteratorB lastB,
InputIteratorW firstW);
template<class UnaryOperation>
constexpr piecewise_constant_distribution(initializer_list<RealType> bl, UnaryOperation fw);
template<class UnaryOperation>
constexpr piecewise_constant_distribution(size_t nw, RealType xmin, RealType xmax, UnaryOperation fw);
constexpr vector<result_type> intervals() const;
constexpr vector<result_type> densities() const;
29.5.9.6.3 Class template piecewise_linear_distribution [rand.dist.samp.plinear]
constexpr piecewise_linear_distribution();
template<class InputIteratorB, class InputIteratorW>
constexpr piecewise_linear_distribution(InputIteratorB firstB, InputIteratorB lastB,
InputIteratorW firstW);
template<class UnaryOperation>
constexpr piecewise_linear_distribution(initializer_list<RealType> bl, UnaryOperation fw);
template<class UnaryOperation>
constexpr piecewise_linear_distribution(size_t nw, RealType xmin, RealType xmax, UnaryOperation fw);
constexpr vector<result_type> intervals() const;
constexpr vector<result_type> densities() const;
29.5.10 Low-quality random number generation [c.math.rand]
int rand();
void srand(unsigned int seed);
Feature test macros
17.3.2 Header <version> synopsis [version.syn]
#define __cpp_lib_constexpr_random 20????L // also in <random> and <algorithm>