Skip to content

Commit 879e0fb

Browse files
authored
Merge 2025-06 LWG Motion 19
P3481R5 std::execution::bulk() issues
2 parents e0eccf1 + 958fc14 commit 879e0fb

File tree

1 file changed

+173
-23
lines changed

1 file changed

+173
-23
lines changed

source/exec.tex

Lines changed: 173 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,8 @@
631631
struct @\libglobal{let_error_t}@ { @\unspec@ };
632632
struct @\libglobal{let_stopped_t}@ { @\unspec@ };
633633
struct @\libglobal{bulk_t}@ { @\unspec@ };
634+
struct @\libglobal{bulk_chunked_t}@ { @\unspec@ };
635+
struct @\libglobal{bulk_unchunked_t}@ { @\unspec@ };
634636
struct @\libglobal{split_t}@ { @\unspec@ };
635637
struct @\libglobal{when_all_t}@ { @\unspec@ };
636638
struct @\libglobal{when_all_with_variant_t}@ { @\unspec@ };
@@ -651,6 +653,8 @@
651653
inline constexpr let_error_t @\libglobal{let_error}@{};
652654
inline constexpr let_stopped_t @\libglobal{let_stopped}@{};
653655
inline constexpr bulk_t @\libglobal{bulk}@{};
656+
inline constexpr bulk_chunked_t @\libglobal{bulk_chunked}@{};
657+
inline constexpr bulk_unchunked_t @\libglobal{bulk_unchunked}@{};
654658
inline constexpr split_t @\libglobal{split}@{};
655659
inline constexpr when_all_t @\libglobal{when_all}@{};
656660
inline constexpr when_all_with_variant_t @\libglobal{when_all_with_variant}@{};
@@ -3677,52 +3681,122 @@
36773681
propagates the other completion operations sent by \tcode{sndr}.
36783682
\end{itemize}
36793683

3680-
\rSec3[exec.bulk]{\tcode{execution::bulk}}
3684+
\rSec3[exec.bulk]{\tcode{execution::bulk}, \tcode{execution::bulk_chunked}, and \tcode{execution::bulk_unchunked}}
36813685

36823686
\pnum
3683-
\tcode{bulk} runs a task repeatedly for every index in an index space.
3687+
\tcode{bulk}, \tcode{bulk_chunked}, and \tcode{bulk_unchunked}
3688+
run a task repeatedly for every index in an index space.
36843689

3685-
The name \tcode{bulk} denotes a pipeable sender adaptor object.
3686-
For subexpressions \tcode{sndr}, \tcode{shape}, and \tcode{f},
3687-
let \tcode{Shape} be \tcode{decltype(auto(shape))}.
3690+
\pnum
3691+
The names \tcode{bulk}, \tcode{bulk_chunked}, and \tcode{bulk_unchunked}
3692+
denote pipeable sender adaptor objects.
3693+
Let \tcode{\placeholder{bulk-algo}} be either
3694+
\tcode{bulk}, \tcode{bulk_chunked}, or \tcode{bulk_unchunked}.
3695+
For subexpressions \tcode{sndr}, \tcode{policy}, \tcode{shape}, and \tcode{f},
3696+
let
3697+
\tcode{Policy} be \tcode{remove_cvref_t<decltype(policy)>},
3698+
\tcode{Shape} be \tcode{decltype(auto(shape))}, and
3699+
\tcode{Func} be \tcode{decay_t<decltype((f))>}.
36883700
If
36893701
\begin{itemize}
36903702
\item
36913703
\tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or
36923704
\item
3705+
\tcode{is_execution_policy_v<Policy>} is \tcode{false}, or
3706+
\item
36933707
\tcode{Shape} does not satisfy \libconcept{integral}, or
36943708
\item
3695-
\tcode{decltype((f))} does not satisfy \exposconcept{movable-value},
3709+
\tcode{Func} does not model \libconcept{copy_constructible},
36963710
\end{itemize}
3697-
\tcode{bulk(sndr, shape, f)} is ill-formed.
3711+
\tcode{\placeholder{bulk-algo}(sndr, policy, shape, f)} is ill-formed.
36983712

36993713
\pnum
37003714
Otherwise,
3701-
the expression \tcode{bulk(sndr, shape, f)} is expression-equivalent to:
3715+
the expression \tcode{\placeholder{bulk-algo}(sndr, policy, shape, f)}
3716+
is expression-equivalent to:
37023717

37033718
\begin{codeblock}
3704-
transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(bulk, @\exposid{product-type}@{shape, f}, sndr))
3719+
transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(
3720+
@\placeholder{bulk-algo}@, @\exposid{product-type}@<@\seebelow@, Shape, Func>{policy, shape, f}, sndr))
37053721
\end{codeblock}
37063722
except that \tcode{sndr} is evaluated only once.
3723+
The first template argument of \exposid{product-type} is \tcode{Policy}
3724+
if \tcode{Policy} models \libconcept{copy_constructible}, and
3725+
\tcode{const Policy\&} otherwise.
3726+
3727+
\pnum
3728+
Let \tcode{sndr} and \tcode{env} be subexpressions such that
3729+
\tcode{Sndr} is \tcode{decltype((sndr))}.
3730+
If \tcode{\exposconcept{sender-for}<Sndr, bulk_t>} is \tcode{false}, then
3731+
the expression \tcode{bulk.transform_sender(sndr, env)} is ill-formed;
3732+
otherwise, it is equivalent to:
3733+
\begin{codeblock}
3734+
auto [_, data, child] = sndr;
3735+
auto& [policy, shape, f] = data;
3736+
auto new_f = [func = std::move(f)](Shape begin, Shape end, auto&&... vs)
3737+
noexcept(noexcept(f(begin, vs...))) {
3738+
while (begin != end) func(begin++, vs...);
3739+
}
3740+
return bulk_chunked(std::move(child), policy, shape, std::move(new_f));
3741+
\end{codeblock}
3742+
\begin{note}
3743+
This causes the \tcode{bulk(sndr, policy, shape, f)} sender to be
3744+
expressed in terms of \tcode{bulk_chunked(sndr, policy, shape, f)} when
3745+
it is connected to a receiver whose
3746+
execution domain does not customize \tcode{bulk}.
3747+
\end{note}
37073748

37083749
\pnum
37093750
The exposition-only class template \exposid{impls-for}\iref{exec.snd.general}
3710-
is specialized for \tcode{bulk_t} as follows:
3751+
is specialized for \tcode{bulk_chunked_t} as follows:
37113752
\begin{codeblock}
37123753
namespace std::execution {
37133754
template<>
3714-
struct @\exposid{impls-for}@<bulk_t> : @\exposid{default-impls}@ {
3755+
struct @\exposid{impls-for}@<bulk_chunked_t> : @\exposid{default-impls}@ {
37153756
static constexpr auto @\exposid{complete}@ = @\seebelow@;
37163757
};
37173758
}
37183759
\end{codeblock}
3760+
The member \tcode{\exposid{impls-for}<bulk_chunked_t>::\exposid{complete}}
3761+
is initialized with a callable object equivalent to the following lambda:
3762+
\begin{codeblock}
3763+
[]<class Index, class State, class Rcvr, class Tag, class... Args>
3764+
(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept
3765+
-> void requires @\seebelow@ {
3766+
if constexpr (@\libconcept{same_as}@<Tag, set_value_t>) {
3767+
auto& [policy, shape, f] = state;
3768+
constexpr bool nothrow = noexcept(f(auto(shape), auto(shape), args...));
3769+
@\exposid{TRY-EVAL}@(rcvr, [&]() noexcept(nothrow) {
3770+
f(static_cast<decltype(auto(shape))>(0), auto(shape), args...);
3771+
Tag()(std::move(rcvr), std::forward<Args>(args)...);
3772+
}());
3773+
} else {
3774+
Tag()(std::move(rcvr), std::forward<Args>(args)...);
3775+
}
3776+
}
3777+
\end{codeblock}
3778+
The expression in the \grammarterm{requires-clause} of the lambda above is
3779+
\tcode{true} if and only
3780+
if \tcode{Tag} denotes a type other than \tcode{set_value_t} or
3781+
if the expression \tcode{f(auto(shape), auto(shape), args...)} is well-formed.
37193782

37203783
\pnum
3721-
The member \tcode{\exposid{impls-for}<bulk_t>::\exposid{complete}}
3784+
The exposition-only class template \exposid{impls-for}\iref{exec.snd.general}
3785+
is specialized for \tcode{bulk_unchunked_t} as follows:
3786+
\begin{codeblock}
3787+
namespace std::execution {
3788+
template<>
3789+
struct @\exposid{impls-for}@<bulk_unchunked_t> : @\exposid{default-impls}@ {
3790+
static constexpr auto @\exposid{complete}@ = @\seebelow@;
3791+
};
3792+
}
3793+
\end{codeblock}
3794+
The member \tcode{\exposid{impls-for}<bulk_unchunked_t>::\exposid{complete}}
37223795
is initialized with a callable object equivalent to the following lambda:
37233796
\begin{codeblock}
37243797
[]<class Index, class State, class Rcvr, class Tag, class... Args>
3725-
(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires @\seebelow@ {
3798+
(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept
3799+
-> void requires @\seebelow@ {
37263800
if constexpr (@\libconcept{same_as}@<Tag, set_value_t>) {
37273801
auto& [shape, f] = state;
37283802
constexpr bool nothrow = noexcept(f(auto(shape), args...));
@@ -3737,33 +3811,109 @@
37373811
}
37383812
}
37393813
\end{codeblock}
3740-
3741-
\pnum
37423814
The expression in the \grammarterm{requires-clause} of the lambda above
37433815
is \tcode{true} if and only
37443816
if \tcode{Tag} denotes a type other than \tcode{set_value_t} or
37453817
if the expression \tcode{f(auto(shape), args...)} is well-formed.
37463818

37473819
\pnum
37483820
Let the subexpression \tcode{out_sndr} denote
3749-
the result of the invocation \tcode{bulk(sndr, shape, f)} or
3821+
the result of the invocation
3822+
\tcode{\placeholder{bulk-algo}(sndr, policy, shape, f)} or
37503823
an object equal to such, and
37513824
let the subexpression \tcode{rcvr} denote a receiver
37523825
such that the expression \tcode{connect(out_sndr, rcvr)} is well-formed.
37533826
The expression \tcode{connect(out_sndr, rcvr)} has undefined behavior
37543827
unless it creates an asynchronous operation\iref{exec.async.ops} that,
3755-
when started,
3828+
when started:
3829+
37563830
\begin{itemize}
37573831
\item
3758-
on a value completion operation,
3759-
invokes \tcode{f(i, args...)}
3760-
for every \tcode{i} of type \tcode{Shape} in \range{\tcode{0}}{\tcode{shape}},
3761-
where \tcode{args} is a pack of lvalue subexpressions
3762-
referring to the value completion result datums of the input sender, and
3832+
If \tcode{sndr} has a successful completion, where
3833+
\tcode{args} is a pack of lvalue subexpressions
3834+
referring to the value completion result datums of \tcode{sndr}, or
3835+
decayed copies of those values if they model \libconcept{copy_constructible},
3836+
then:
3837+
3838+
\begin{itemize}
3839+
\item
3840+
If \tcode{out_sndr} also completes successfully, then:
3841+
3842+
\begin{itemize}
3843+
\item
3844+
for \tcode{bulk},
3845+
invokes \tcode{f($i$, args...)} for every $i$ of type \tcode{Shape}
3846+
from \tcode{0} to \tcode{shape};
3847+
3848+
\item
3849+
for \tcode{bulk_unchunked},
3850+
invokes \tcode{f($i$, args...)} for every $i$ of type \tcode{Shape}
3851+
from \tcode{0} to \tcode{shape};
3852+
3853+
\recommended
3854+
The underlying scheduler should execute each iteration
3855+
on a distinct execution agent.
3856+
3857+
\item
3858+
for \tcode{bulk_chunked},
3859+
invokes \tcode{f($b$, $e$, args...)} zero or more times
3860+
with pairs of $b$ and $e$ of type \tcode{Shape}
3861+
in range \crange{\tcode{0}}{\tcode{shape}},
3862+
such that $b < e$ and
3863+
for every $i$ of type \tcode{Shape} from \tcode{0} to \tcode{shape},
3864+
there is exactly one invocation with a pair $b$ and $e$,
3865+
such that $i$ is in the range \range{$b$}{$e$}.
3866+
\end{itemize}
3867+
3868+
\item
3869+
If \tcode{out_sndr} completes with \tcode{set_error(rcvr, eptr)}, then
3870+
the asynchronous operation may invoke a subset of
3871+
the invocations of \tcode{f}
3872+
before the error completion handler is called, and
3873+
\tcode{eptr} is an \tcode{exception_ptr} containing either:
3874+
\begin{itemize}
3875+
\item
3876+
an exception thrown by an invocation of \tcode{f}, or
3877+
\item
3878+
a \tcode{bad_alloc} exception if
3879+
the implementation fails to allocate required resources, or
3880+
\item
3881+
an exception derived from \tcode{runtime_error}.
3882+
\end{itemize}
3883+
3884+
\item
3885+
If \tcode{out_sndr} completes with \tcode{set_stopped(rcvr)}, then
3886+
the asynchronous operation may invoke a subset of
3887+
the invocations of \tcode{f}
3888+
before the stopped completion handler.
3889+
\end{itemize}
3890+
3891+
\item
3892+
If \tcode{sndr} does not complete with \tcode{set_value}, then
3893+
the completion is forwarded to \tcode{recv}.
3894+
37633895
\item
3764-
propagates all completion operations sent by \tcode{sndr}.
3896+
For \tcode{\placeholder{bulk-algo}},
3897+
the parameter \tcode{policy} describes
3898+
the manner in which
3899+
the execution of the asynchronous operations corresponding to these algorithms
3900+
may be parallelized and
3901+
the manner in which
3902+
%%FIXME: Should this be "apply to f"?
3903+
they apply \tcode{f}.
3904+
Permissions and requirements
3905+
on parallel algorithm element access functions\iref{algorithms.parallel.exec}
3906+
apply to \tcode{f}.
37653907
\end{itemize}
37663908

3909+
\pnum
3910+
\begin{note}
3911+
The asynchronous operation corresponding to
3912+
\tcode{\placeholder{bulk-algo}(sndr, policy, shape, f)}
3913+
can complete with \tcode{set_stopped} if cancellation is requested or
3914+
ignore cancellation requests.
3915+
\end{note}
3916+
37673917
\rSec3[exec.split]{\tcode{execution::split}}
37683918

37693919
\pnum

0 commit comments

Comments
 (0)