discard_block_engine
Section: 29.5.5.2 [rand.adapt.disc] Status: C++23 Submitter: Ilya Burylov Opened: 2021-06-03 Last modified: 2023-11-22
Priority: Not Prioritized
View all other issues in [rand.adapt.disc].
View all issues with C++23 status.
Discussion:
A discard_block_engine
engine adaptor is described in 29.5.5.2 [rand.adapt.disc]. It produces
random numbers from the underlying engine discarding (p - r)
last values of p - size
block,
where r
and p
are template parameters of std::size_t
type:
template<class Engine, size_t p, size_t r> class discard_block_engine;
The transition algorithm for discard_block_engine
is described as follows (paragraph 2):
The transition algorithm discards all but
r > 0
values from each block ofp = r
values delivered byℯ
. The state transition is performed as follows: Ifn = r
, advance the state ofe
fromei
toei+p-r
and setn
to0
. In any case, then incrementn
and advancee
's then-current stateej
toej+1
.
Where n
is of integer type. In the API of discard block engine, n
is represented
in the following way:
[…] int n; // exposition only
In cases where int
is equal to int32_t
, overflow is possible for n
that leads
to undefined behavior. Such situation can happen when the p
and r
template parameters exceed
INT_MAX
.
GNU Libstdc++ uses size_t
for n
— in this case no overflow happened even if template
parameters exceed INT_MAX
LLVM Libcxx uses int
for n
together with a static_assert
that is checking that
p
and r
values are <= INT_MAX
Such difference in implementation makes code not portable and may potentially breaks random number sequence consistency between different implementors of C++ std lib.
The problematic use case is the following one:#include <iostream> #include <random> #include <limits> int main() { std::minstd_rand0 generator; constexpr std::size_t skipped_size = static_cast<std::size_t>(std::numeric_limits<int>::max()); constexpr std::size_t block_size = skipped_size + 5; constexpr std::size_t used_block = skipped_size; std::cout << "block_size = " << block_size << std::endl; std::cout << "used_block = " << used_block << "\n" << std::endl; std::discard_block_engine<std::minstd_rand0, block_size, used_block> discard_generator; // Call discard procedures discard_generator.discard(used_block); generator.discard(block_size); // Call generation. Results should be equal for (std::int32_t i = 0; i < 10; ++i) { std::cout << discard_generator() << " should be equal " << generator() << std::endl; } }
We see no solid reason for n
to be an int
, given that the relevant template parameters are
std::size_t
. It seems like a perfectly fine use case to generate random numbers in amounts larger than
INT_MAX
.
std::size_t
:
size_t n; // exposition only
It will not mandate the existing libraries to break ABI, but at least guide for better implementation.
[2021-06-14; Reflector poll]
Set status to Tentatively Ready after six votes in favour during reflector poll.
[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4885.
Modify 29.5.5.2 [rand.adapt.disc], class template discard_block_engine
synopsis, as indicated:
[…] // generating functions result_type operator()(); void discard(unsigned long long z); // property functions const Engine& base() const noexcept { return e; }; private: Engine e; // exposition onlyintsize_t n; // exposition only };