33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.12 Sender adaptors [exec.adapt]

33.9.12.11 execution​::​bulk, execution​::​bulk_chunked, and execution​::​bulk_unchunked [exec.bulk]

bulk, bulk_chunked, and bulk_unchunked run a task repeatedly for every index in an index space.
The names bulk, bulk_chunked, and bulk_unchunked denote pipeable sender adaptor objects.
Let bulk-algo be either bulk, bulk_chunked, or bulk_unchunked.
For subexpressions sndr, policy, shape, and f, let Policy be remove_cvref_t<decltype(policy)>, Shape be decltype(auto(shape)), and Func be decay_t<decltype((f))>.
If bulk-algo(sndr, policy, shape, f) is ill-formed.
Otherwise, the expression bulk-algo(sndr, policy, shape, f) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender( bulk-algo, product-type<see below, Shape, Func>{policy, shape, f}, sndr)) except that sndr is evaluated only once.
The first template argument of product-type is Policy if Policy models copy_constructible, and const Policy& otherwise.
Let sndr and env be subexpressions such that Sndr is decltype((sndr)).
If sender-for<Sndr, bulk_t> is false, then the expression bulk.transform_sender(sndr, env) is ill-formed; otherwise, it is equivalent to: auto [_, data, child] = sndr; auto& [policy, shape, f] = data; auto new_f = [func = std::move(f)](Shape begin, Shape end, auto&&... vs) noexcept(noexcept(f(begin, vs...))) { while (begin != end) func(begin++, vs...); } return bulk_chunked(std::move(child), policy, shape, std::move(new_f));
[Note 1: 
This causes the bulk(sndr, policy, shape, f) sender to be expressed in terms of bulk_chunked(sndr, policy, shape, f) when it is connected to a receiver whose execution domain does not customize bulk.
— end note]
The exposition-only class template impls-for ([exec.snd.general]) is specialized for bulk_chunked_t as follows: namespace std::execution { template<> struct impls-for<bulk_chunked_t> : default-impls { static constexpr auto complete = see below; template<class Sndr, class... Env> static consteval void check-types(); }; }
The member impls-for<bulk_chunked_t>​::​complete is initialized with a callable object equivalent to the following lambda: []<class Index, class State, class Rcvr, class Tag, class... Args> (Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires see below { if constexpr (same_as<Tag, set_value_t>) { auto& [policy, shape, f] = state; constexpr bool nothrow = noexcept(f(auto(shape), auto(shape), args...)); TRY-EVAL(rcvr, [&]() noexcept(nothrow) { f(static_cast<decltype(auto(shape))>(0), auto(shape), args...); Tag()(std::move(rcvr), std::forward<Args>(args)...); }()); } else { Tag()(std::move(rcvr), std::forward<Args>(args)...); } }
The expression in the requires-clause of the lambda above is true if and only if Tag denotes a type other than set_value_t or if the expression f(auto(shape), auto(shape), args...) is well-formed.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for bulk_unchunked_t as follows: namespace std::execution { template<> struct impls-for<bulk_unchunked_t> : default-impls { static constexpr auto complete = see below; }; }
The member impls-for<bulk_unchunked_t>​::​complete is initialized with a callable object equivalent to the following lambda: []<class Index, class State, class Rcvr, class Tag, class... Args> (Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires see below { if constexpr (same_as<Tag, set_value_t>) { auto& [shape, f] = state; constexpr bool nothrow = noexcept(f(auto(shape), args...)); TRY-EVAL(rcvr, [&]() noexcept(nothrow) { for (decltype(auto(shape)) i = 0; i < shape; ++i) { f(auto(i), args...); } Tag()(std::move(rcvr), std::forward<Args>(args)...); }()); } else { Tag()(std::move(rcvr), std::forward<Args>(args)...); } }
The expression in the requires-clause of the lambda above is true if and only if Tag denotes a type other than set_value_t or if the expression f(auto(shape), args...) is well-formed.
template<class Sndr, class... Env> static consteval void check-types();
Effects: Equivalent to: auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>(); auto fn = []<class... Ts>(set_value_t(*)(Ts...)) { if constexpr (!invocable<remove_cvref_t<data-type<Sndr>>, Ts&...>) throw unspecified-exception(); }; cs.for-each(overload-set(fn, [](auto){})); where unspecified-exception is a type derived from exception.
Let the subexpression out_sndr denote the result of the invocation bulk-algo(sndr, policy, shape, f) or an object equal to such, and let the subexpression rcvr denote a receiver such that the expression connect(out_sndr, rcvr) is well-formed.
The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([exec.async.ops]) that, when started:
  • If sndr has a successful completion, where args is a pack of lvalue subexpressions referring to the value completion result datums of sndr, or decayed copies of those values if they model copy_constructible, then:
    • If out_sndr also completes successfully, then:
      • for bulk, invokes f(i, args...) for every i of type Shape from 0 to shape;
      • for bulk_unchunked, invokes f(i, args...) for every i of type Shape from 0 to shape;
        Recommended practice: The underlying scheduler should execute each iteration on a distinct execution agent.
      • for bulk_chunked, invokes f(b, e, args...) zero or more times with pairs of b and e of type Shape in range [0, shape], such that and for every i of type Shape from 0 to shape, there is exactly one invocation with a pair b and e, such that i is in the range [b, e).
    • If out_sndr completes with set_error(rcvr, eptr), then the asynchronous operation may invoke a subset of the invocations of f before the error completion handler is called, and eptr is an exception_ptr containing either:
      • an exception thrown by an invocation of f, or
      • a bad_alloc exception if the implementation fails to allocate required resources, or
      • an exception derived from runtime_error.
    • If out_sndr completes with set_stopped(rcvr), then the asynchronous operation may invoke a subset of the invocations of f before the stopped completion handler.
  • If sndr does not complete with set_value, then the completion is forwarded to recv.
  • For bulk-algo, the parameter policy describes the manner in which the execution of the asynchronous operations corresponding to these algorithms may be parallelized and the manner in which they apply f.
    Permissions and requirements on parallel algorithm element access functions ([algorithms.parallel.exec]) apply to f.
[Note 2: 
The asynchronous operation corresponding to bulk-algo(sndr, policy, shape, f) can complete with set_stopped if cancellation is requested or ignore cancellation requests.
— end note]