This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
std::generate and std::ranges::generate wording is unclear for parallel algorithmsSection: 26.7.7 [alg.generate] Status: New Submitter: Ruslan Arutyunyan Opened: 2025-12-12 Last modified: 2025-12-20
Priority: Not Prioritized
View all issues with New status.
Discussion:
std::generate and std::ranges::generate are confusing for parallel algorithms. For both of those
Effects says "Assigns the result of successive evaluations of gen() through each iterator
in the range [first, first + N)." The word "successive" is confusing when we talk about parallelism.
This wording was the preexisting one; P3179 "Parallel Range Algorithms" didn't modify
that, so the problem existed even before.
Generator template
parameter type for C++17 parallel generate. Intel, NVIDIA, and GNU libstdc++ implementations do
multiple copies of Generator object to maximize parallelism, while it's not technically allowed if
I am reading the standard correctly. But without Generator being copyable we have point of contention
and extra synchronization (or we put synchronization part as users' responsibility, which is also not
obvious).
There is no clear solution right away. We could try to fix the wording for both ranges and C++17 parallel
generate but it seems like it requires extra effort because we need to at least verify the new behavior
with LLVM libc++ implementation if we want to make Generator copyable; libc++ currently does not
create per-thread copy of gen object. Perhaps, the best strategy for now is to remove
ranges::generate and ranges::generate_n and return them back in C++29 time frame when we figure
out the proper fix.
Proposed resolution:
This wording is relative to N5032.
Modify 26.4 [algorithm.syn], header <algorithm> synopsis, as indicated:
[…] // 26.7.7 [alg.generate], generate template<class ForwardIterator, class Generator> constexpr void generate(ForwardIterator first, ForwardIterator last, Generator gen); template<class ExecutionPolicy, class ForwardIterator, class Generator> void generate(ExecutionPolicy&& exec, // freestanding-deleted, see 26.3.5 [algorithms.parallel.overloads] ForwardIterator first, ForwardIterator last, Generator gen); template<class OutputIterator, class Size, class Generator> constexpr OutputIterator generate_n(OutputIterator first, Size n, Generator gen); template<class ExecutionPolicy, class ForwardIterator, class Size, class Generator> ForwardIterator generate_n(ExecutionPolicy&& exec, // freestanding-deleted, see 26.3.5 [algorithms.parallel.overloads] ForwardIterator first, Size n, Generator gen); namespace ranges { template<input_or_output_iterator O, sentinel_for<O> S, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> constexpr O generate(O first, S last, F gen); template<class R, copy_constructible F> requires invocable<F&> & output_range<R, invoke_result_t<F&>> constexpr borrowed_iterator_t<R> generate(R&& r, F gen); template<input_or_output_iterator O, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> constexpr O generate_n(O first, iter_difference_t<O> n, F gen);template<execution-policy Ep, random_access_iterator O, sized_sentinel_for<O> S, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> O generate(Ep&& exec, O first, S last, F gen); // freestanding-deleted template<execution-policy Ep, sized-random-access-range R, copy_constructible F> requires invocable<F&> & indirectly_writable<iterator_t<R>, invoke_result_t<F&>> borrowed_iterator_t<R> generate(Ep&& exec, R&& r, F gen); // freestanding-deleted template<execution-policy Ep, random_access_iterator O, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> O generate_n(Ep&& exec, O first, iter_difference_t<O> n, F gen); // freestanding-deleted} […]
Modify 26.7.7 [alg.generate] as indicated:
template<class ForwardIterator, class Generator> constexpr void generate(ForwardIterator first, ForwardIterator last, Generator gen); template<class ExecutionPolicy, class ForwardIterator, class Generator> void generate(ExecutionPolicy&& exec, ForwardIterator first, ForwardIterator last, Generator gen); template<class OutputIterator, class Size, class Generator> constexpr OutputIterator generate_n(OutputIterator first, Size n, Generator gen); template<class ExecutionPolicy, class ForwardIterator, class Size, class Generator> ForwardIterator generate_n(ExecutionPolicy&& exec, ForwardIterator first, Size n, Generator gen); template<input_or_output_iterator O, sentinel_for<O> S, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> constexpr O ranges::generate(O first, S last, F gen); template<class R, copy_constructible F> requires invocable<F&> & output_range<R, invoke_result_t<F&>> constexpr borrowed_iterator_t<R> ranges::generate(R&& r, F gen); template<input_or_output_iterator O, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> constexpr O ranges::generate_n(O first, iter_difference_t<O> n, F gen);template<execution-policy Ep, random_access_iterator O, sized_sentinel_for<O> S, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> O ranges::generate(Ep&& exec, O first, S last, F gen); template<execution-policy Ep, sized-random-access-range R, copy_constructible F> requires invocable<F&> & indirectly_writable<iterator_t<R>, invoke_result_t<F&>> borrowed_iterator_t<R> ranges::generate(Ep&& exec, R&& r, F gen); template<execution-policy Ep, random_access_iterator O, copy_constructible F> requires invocable<F&> & indirectly_writable<O, invoke_result_t<F&>> O ranges::generate_n(Ep&& exec, O first, iter_difference_t<O> n, F gen);-1- Let
[…]Nbemax(0, n)for thegenerate_nalgorithms, andlast - firstfor thegeneratealgorithms.