P3791R1: 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.
Changes
- R0 → R1: Paper was reviewed by LWG and voted 6/1/2 to be moved to straw poll pages during Brno meeting. During the review a concern was stated about implementability, I voluntarily withdraw the paper to provide adequate response to the concerns. This revision contains it in updated section Implementation experience. Otherwise the paper is unchanged and ready to move forward.
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.
TL;DR of next subsection: once you have constexpr <cmath> then after only constexpr sprinkling in both libc++ and libstdc++'s <random> you have a functional implementation of this paper successfully evaluating. Also the implementations both returns exactly same results during runtime and compile time.
Response to concerns state during LWG review in Brno
During the LWG review in Brno the paper was approved to go to plenary, but some authors were concerned about this change, as no implementation provides full constexpr <cmath implementation. I have voluntarily withdraw the paper from going to plenary in order to provide a material evidence about the implementability. One of the concerns were me not testing the newer std::poisson_distribution.
Jonathan sent me a code snippet and said he will be less concerned if the code he sent passes constant evaluation. The code now works as the experimental hana-clang contains the change.
libc++
In order to prove the paper is only sprinkling of constexpr I have prototyped constexpr <cmath> by implementing the necessary builtins, and marking all the functionality with constexpr. The change containing only <cmath> and compiler evaluator changes is available on my github.
As the next step I have just sprinkled the constexpr around in <random> header (and internal __random/FUNCTIONALITY.h headers), the change is also available on my github.
As you can now sea, the change is not complex. You can experiment with the implementation on the Compiler Explorer where you can see complex example based on Jonathan's code which evalautes each distributions (including Poisson) with every generator available by the standard.
Unrelated issues found during implementation
As I was implementing and evaluating the code, I have noticed that some of libc++'s <random> functionality is using #include <cmath> instead of relying on internal __math/FUNCTION.h, and used unqualified names of math functions. Which on my platform (MacOS) lead to selection of function declarations in system's math.h. Which can't be marked constexpr and their own builtins are independently defined in clang's builtin.td.
By fixing the unqualified lookup and inclusion of right internal header. The updated <random> is no longer have dependency on the system header and instead it uses explicit builtins instead. The change is also available to see on my github.
libstdc++
Excited about perceived simplicity I went to look into libstdc++ implementation in GCC. I tried to use GCC's -fimplicit-constexpr option and see if I run only into <cmath> not being defined issues. Some of the functions of libstdc++'s <random> are defined outside of their class definition and these functions are not implicitly inline, and they are not made implicitly constexpr by the switch which makes only inline functions implicitly constexpr.
So I took whole libstdc++'s <random> and its dependencies and put it all into the source editor of the Compiler Explorer and start making these functions constexpr manually, all of which was observed by (next to me sitting in audience on ACCU on Sea conference) Matt Godbolt (the author of the Godbolt Compiler Explorer) with a horror in his eyes accompanied with a question "how (incomprehensible) are you using it?" later followed by a message "Banananananananana" (in our private discord conversation) as he was probably calculating how much this exercise cost him on the compute (which was a negligible amount of the compute used by large language models about which was Andrei Alexandrescu keynoting on the stage few meters from us). And once I start reaching first errors due <math.h>'s symbols not being defined I moved my focus on the conference instead.
A few weeks later I got the idea to backport my -fimplicit-constexpr to hana-clang. It's a stronger version than GCC's (it makes every function implicitly constexpr). I made it while back as a PR for the upstream, some maintainers requested a RFC which was later sadly rejected as it's a niche functionality. Equiped with this in the hana-clang, I could finally try to run the same test code I tried with libc++.
I ran into a problem the functionality was using math.h instead, and these builtins I didn't make constexpr in my clang. So I have needed to modify my clang to make these normal-function-looking-builtins also constexpr. After a tiny change I tried again, and ... as you can see, it worked and flawlessly.
Does it return same results?
After I published this research on LWG mailing list, I talked with Tomasz who asked me "Does it give same results?" to which I answered with "I don't see reason why it shouldn't" and sent him a quick test code I made with Poisson distribution. Which shows the result is same.
I have made also a test which again tests all the distributions and engines called multiple times to return same result in runtime and compile time.
Research of MSSTL
Similar situation as in other two implementations, 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>