[exec.bulk] # 33 Execution control library [[exec]](./#exec) ## 33.9 Senders [[exec.snd]](exec.snd#exec.bulk) ### 33.9.12 Sender adaptors [[exec.adapt]](exec.adapt#exec.bulk) #### 33.9.12.11 execution​::​bulk, execution​::​bulk_chunked, and execution​::​bulk_unchunked [exec.bulk] [1](#1) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4109) bulk, bulk_chunked, and bulk_unchunked run a task repeatedly for every index in an index space[.](#1.sentence-1) [2](#2) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4113) The names bulk, bulk_chunked, and bulk_unchunked denote pipeable sender adaptor objects[.](#2.sentence-1) Let *bulk-algo* be eitherbulk, bulk_chunked, or bulk_unchunked[.](#2.sentence-2) For subexpressions sndr, policy, shape, and f, letPolicy be remove_cvref_t,Shape be decltype(auto(shape)), andFunc be decay_t[.](#2.sentence-3) If - [(2.1)](#2.1) decltype((sndr)) does not satisfy [sender](exec.snd.concepts#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]"), or - [(2.2)](#2.2) is_execution_policy_v is false, or - [(2.3)](#2.3) Shape does not satisfy [integral](concepts.arithmetic#concept:integral "18.4.7 Arithmetic concepts [concepts.arithmetic]"), or - [(2.4)](#2.4) Func does not model [copy_constructible](concept.copyconstructible#concept:copy_constructible "18.4.14 Concept copy_­constructible [concept.copyconstructible]"), *bulk-algo*(sndr, policy, shape, f) is ill-formed[.](#2.sentence-4) [3](#3) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4136) 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[.](#3.sentence-2) The first template argument of *product-type* is Policy if Policy models [copy_constructible](concept.copyconstructible#concept:copy_constructible "18.4.14 Concept copy_­constructible [concept.copyconstructible]"), andconst Policy& otherwise[.](#3.sentence-3) [4](#4) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4150) Let sndr and env be subexpressions such thatSndr is decltype((sndr))[.](#4.sentence-1) If [*sender-for*](exec.snd.concepts#concept:sender-for "33.9.3 Sender concepts [exec.snd.concepts]") 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](#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[.](#4.sentence-2) — *end note*] [5](#5) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4172) The exposition-only class template *impls-for* ([[exec.snd.expos]](exec.snd.expos "33.9.2 Exposition-only entities")) is specialized for bulk_chunked_t as follows: [🔗](#lib:impls-for%3cbulk_chunked_t%3e) namespace std::execution {template<>struct *impls-for* : *default-impls* {static constexpr auto *complete* = *see below*; templatestatic consteval void *check-types*(); };} The member *impls-for*​::​*complete* is initialized with a callable object equivalent to the following lambda:[](Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept-> void requires *see below* {if constexpr ([same_as](concept.same#concept:same_as "18.4.2 Concept same_­as [concept.same]")) {auto& [policy, shape, f] = state; constexpr bool nothrow = noexcept(f(auto(shape), auto(shape), args...)); *TRY-EVAL*(rcvr, [&]() noexcept(nothrow) { f(static_cast(0), auto(shape), args...); Tag()(std::move(rcvr), std::forward(args)...); }()); } else { Tag()(std::move(rcvr), std::forward(args)...); }} The expression in the [*requires-clause*](temp.pre#nt:requires-clause "13.1 Preamble [temp.pre]") of the lambda above istrue 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[.](#5.sentence-3) [6](#6) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4210) The exposition-only class template *impls-for* ([[exec.snd.expos]](exec.snd.expos "33.9.2 Exposition-only entities")) is specialized for bulk_unchunked_t as follows:namespace std::execution {template<>struct *impls-for* : *default-impls* {static constexpr auto *complete* = *see below*; };} The member *impls-for*​::​*complete* is initialized with a callable object equivalent to the following lambda:[](Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept-> void requires *see below* {if constexpr ([same_as](concept.same#concept:same_as "18.4.2 Concept same_­as [concept.same]")) {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)...); }()); } else { Tag()(std::move(rcvr), std::forward(args)...); }} The expression in the [*requires-clause*](temp.pre#nt:requires-clause "13.1 Preamble [temp.pre]") 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[.](#6.sentence-3) [🔗](#lib:check-types,impls-for%3cbulk_t%3e) `template static consteval void check-types(); ` [7](#7) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4253) *Effects*: Equivalent to:auto cs = get_completion_signatures<*child-type*, *FWD-ENV-T*(Env)...>();auto fn = [](set_value_t(*)(Ts...)) {if constexpr (![invocable](concept.invocable#concept:invocable "18.7.2 Concept invocable [concept.invocable]")>, Ts&...>)throw *unspecified-exception*();}; cs.*for-each*(*overload-set*(fn, [](auto){})); where *unspecified-exception* is a type derived from exception[.](#7.sentence-1) [8](#8) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4268) 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[.](#8.sentence-1) The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([[exec.async.ops]](exec.async.ops "33.3 Asynchronous operations")) that, when started: - [(8.1)](#8.1) If sndr has a successful completion, whereargs 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](concept.copyconstructible#concept:copy_constructible "18.4.14 Concept copy_­constructible [concept.copyconstructible]"), then: * [(8.1.1)](#8.1.1) If out_sndr also completes successfully, then: + [(8.1.1.1)](#8.1.1.1) for bulk, invokes f(i, args...) for every i of type Shape from 0 to shape; + [(8.1.1.2)](#8.1.1.2) 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[.](#8.1.1.2.sentence-2) + [(8.1.1.3)](#8.1.1.3) 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 b