Created on 2025-12-12.00:00:00 last changed 1 week ago
Proposed resolution:
This wording is relative to N5032.
Modify [algorithm.syn], header <algorithm> synopsis, as indicated:
[…] // [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 [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 [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 [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 N be `max(0, n)` for the `generate_n` algorithms, and `last - first` for the `generate` algorithms.
[…]
`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.
Another problem is that there is nothing about what is allowed to do with the `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.| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2025-12-20 14:33:18 | admin | set | messages: + msg15832 |
| 2025-12-12 00:00:00 | admin | create | |