|
3943 | 3943 | evaluates \tcode{state.\exposid{sh_state}->\exposid{start-op}()}.
|
3944 | 3944 | \end{itemize}
|
3945 | 3945 |
|
| 3946 | +\rSec3[exec.when.all]{\tcode{execution::when_all}} |
| 3947 | + |
| 3948 | +\pnum |
| 3949 | +\tcode{when_all} and \tcode{when_all_with_variant} |
| 3950 | +both adapt multiple input senders into a sender |
| 3951 | +that completes when all input senders have completed. |
| 3952 | +\tcode{when_all} only accepts senders |
| 3953 | +with a single value completion signature and |
| 3954 | +on success concatenates all the input senders' value result datums |
| 3955 | +into its own value completion operation. |
| 3956 | +\tcode{when_all_with_variant(sndrs...)} is semantically equivalent to |
| 3957 | +w\tcode{hen_all(into_variant(sndrs)...)}, |
| 3958 | +where \tcode{sndrs} is a pack of subexpressions |
| 3959 | +whose types model \libconcept{sender}. |
| 3960 | + |
| 3961 | +\pnum |
| 3962 | +The names \tcode{when_all} and \tcode{when_all_with_variant} denote |
| 3963 | +customization point objects. |
| 3964 | +Let \tcode{sndrs} be a pack of subexpressions, |
| 3965 | +let \tcode{Sndrs} be a pack of the types \tcode{decltype((sndrs))...}, and |
| 3966 | +let \tcode{CD} be |
| 3967 | +the type \tcode{common_type_t<decltype(\exposid{get-domain-early}(sndrs))...>}. |
| 3968 | +The expressions \tcode{when_all(sndrs...)} and |
| 3969 | +\tcode{when_all_with_variant(sndrs...)} are ill-formed |
| 3970 | +if any of the following is \tcode{true}: |
| 3971 | +\begin{itemize} |
| 3972 | +\item |
| 3973 | +\tcode{sizeof...(sndrs)} is \tcode{0}, or |
| 3974 | +\item |
| 3975 | +\tcode{(sender<Sndrs> \&\& ...)} is \tcode{false}, or |
| 3976 | +\item |
| 3977 | +\tcode{CD} is ill-formed. |
| 3978 | +\end{itemize} |
| 3979 | + |
| 3980 | +\pnum |
| 3981 | +The expression \tcode{when_all(sndrs...)} is expression-equivalent to: |
| 3982 | +\begin{codeblock} |
| 3983 | +transform_sender(CD(), @\exposid{make-sender}@(when_all, {}, sndrs...)) |
| 3984 | +\end{codeblock} |
| 3985 | + |
| 3986 | +\pnum |
| 3987 | +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} |
| 3988 | +is specialized for \tcode{when_all_t} as follows: |
| 3989 | +\begin{codeblock} |
| 3990 | +namespace std::execution { |
| 3991 | + template<> |
| 3992 | + struct @\exposid{impls-for}@<when_all_t> : @\exposid{default-impls}@ { |
| 3993 | + static constexpr auto @\exposid{get-attrs}@ = @\seebelow@; |
| 3994 | + static constexpr auto @\exposid{get-env}@ = @\seebelow@; |
| 3995 | + static constexpr auto @\exposid{get-state}@ = @\seebelow@; |
| 3996 | + static constexpr auto @\exposid{start}@ = @\seebelow@; |
| 3997 | + static constexpr auto @\exposid{complete}@ = @\seebelow@; |
| 3998 | + }; |
| 3999 | +} |
| 4000 | +\end{codeblock} |
| 4001 | + |
| 4002 | +\pnum |
| 4003 | +The member \tcode{\exposid{impls-for}<when_all_t>::\exposid{get-attrs}} |
| 4004 | +is initialized with a callable object |
| 4005 | +equivalent to the following lambda expression: |
| 4006 | +\begin{codeblock} |
| 4007 | +[](auto&&, auto&&... child) noexcept { |
| 4008 | + if constexpr (@\libconcept{same_as}@<CD, default_domain>) { |
| 4009 | + return empty_env(); |
| 4010 | + } else { |
| 4011 | + return @\exposid{MAKE-ENV}@(get_domain, CD()); |
| 4012 | + } |
| 4013 | +} |
| 4014 | +\end{codeblock} |
| 4015 | + |
| 4016 | +\pnum |
| 4017 | +The member \tcode{\exposid{impls-for}<when_all_t>::\exposid{get-env}} |
| 4018 | +is initialized with a callable object |
| 4019 | +equivalent to the following lambda expression: |
| 4020 | +\begin{codeblock} |
| 4021 | +[]<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept { |
| 4022 | + return @\exposid{JOIN-ENV}@( |
| 4023 | + @\exposid{MAKE-ENV}@(get_stop_token, state.@\exposid{stop_src}@.get_token()), get_env(rcvr)); |
| 4024 | +} |
| 4025 | +\end{codeblock} |
| 4026 | + |
| 4027 | +\pnum |
| 4028 | +The member \tcode{\exposid{impls-for}<when_all_t>::\exposid{get-state}} |
| 4029 | +is initialized with a callable object |
| 4030 | +equivalent to the following lambda expression: |
| 4031 | +\begin{codeblock} |
| 4032 | +[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(@$e$@) -> decltype(e) { |
| 4033 | + return @$e$@; |
| 4034 | +} |
| 4035 | +\end{codeblock} |
| 4036 | +where $e$ is the expression |
| 4037 | +\begin{codeblock} |
| 4038 | +std::forward<Sndr>(sndr).apply(@\exposid{make-state}@<Rcvr>()) |
| 4039 | +\end{codeblock} |
| 4040 | +and where \exposid{make-state} is the following exposition-only class template: |
| 4041 | +\begin{codeblock} |
| 4042 | +template<class Sndr, class Env> |
| 4043 | +concept @\defexposconcept{max-1-sender-in}@ = @\libconcept{sender_in}@<Sndr, Env> && // \expos |
| 4044 | + (tuple_size_v<value_types_of_t<Sndr, Env, tuple, tuple>> <= 1); |
| 4045 | + |
| 4046 | +enum class @\exposid{disposition}@ { @\exposid{started}@, @\exposid{error}@, @\exposid{stopped}@ }; // \expos |
| 4047 | + |
| 4048 | +template<class Rcvr> |
| 4049 | +struct @\exposid{make-state}@ { |
| 4050 | + template<@\exposconcept{max-1-sender-in}@<env_of_t<Rcvr>>... Sndrs> |
| 4051 | + auto operator()(auto, auto, Sndrs&&... sndrs) const { |
| 4052 | + using values_tuple = @\seebelow@; |
| 4053 | + using errors_variant = @\seebelow@; |
| 4054 | + using stop_callback = stop_callback_of_t<stop_token_of_t<env_of_t<Rcvr>>, @\exposid{on-stop-request}@>; |
| 4055 | + |
| 4056 | + struct @\exposid{state-type}@ { |
| 4057 | + void arrive(Rcvr& rcvr) noexcept { |
| 4058 | + if (0 == --count) { |
| 4059 | + @\exposid{complete}@(rcvr); |
| 4060 | + } |
| 4061 | + } |
| 4062 | + |
| 4063 | + void @\exposid{complete}@(Rcvr& rcvr) noexcept; // \seebelow |
| 4064 | + |
| 4065 | + atomic<size_t> @\exposid{count}@{sizeof...(sndrs)}; // \expos |
| 4066 | + inplace_stop_source @\exposid{stop_src}@{}; // \expos |
| 4067 | + atomic<@\exposid{disposition}@> disp{@\exposid{disposition}@::@\exposid{started}@}; // \expos |
| 4068 | + errors_variant @\exposid{errors}@{}; // \expos |
| 4069 | + values_tuple @\exposid{values}@{}; // \expos |
| 4070 | + optional<stop_callback> @\exposid{on_stop}@{nullopt}; // \expos |
| 4071 | + }; |
| 4072 | + |
| 4073 | + return @\exposid{state-type}@{}; |
| 4074 | + } |
| 4075 | +}; |
| 4076 | +\end{codeblock} |
| 4077 | + |
| 4078 | +\pnum |
| 4079 | +Let \exposid{copy-fail} be \tcode{exception_ptr} |
| 4080 | +if decay-copying any of the child senders' result datums can potentially throw; |
| 4081 | +otherwise, \exposid{none-such}, |
| 4082 | +where \exposid{none-such} is an unspecified empty class type. |
| 4083 | + |
| 4084 | +\pnum |
| 4085 | +The alias \tcode{values_tuple} denotes the type |
| 4086 | +\tcode{tuple<value_types_of_t<Sndrs, env_of_t<Rcvr>, decayed-tuple, optional>...>} |
| 4087 | +if that type is well-formed; otherwise, \tcode{tuple<>}. |
| 4088 | + |
| 4089 | +\pnum |
| 4090 | +The alias \tcode{errors_variant} denotes |
| 4091 | +the type \tcode{variant<none-such, copy-fail, Es...>} |
| 4092 | +with duplicate types removed, |
| 4093 | +where \tcode{Es} is the pack of the decayed types |
| 4094 | +of all the child senders' possible error result datums. |
| 4095 | + |
| 4096 | +\pnum |
| 4097 | +The member |
| 4098 | +\tcode{void \exposid{state-type}::\exposid{complete}(Rcvr\& rcvr) noexcept} |
| 4099 | +behaves as follows: |
| 4100 | +\begin{itemize} |
| 4101 | +\item |
| 4102 | +If \tcode{disp} is equal to \tcode{\exposid{disposition}::\exposid{started}}, |
| 4103 | +evaluates: |
| 4104 | +\begin{codeblock} |
| 4105 | +auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); }; |
| 4106 | +auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); }; |
| 4107 | + |
| 4108 | +@\exposid{on_stop}@.reset(); |
| 4109 | +apply( |
| 4110 | + [&](auto&... opts) noexcept { |
| 4111 | + apply(set, tuple_cat(tie(*opts)...)); |
| 4112 | + }, |
| 4113 | + values); |
| 4114 | +\end{codeblock} |
| 4115 | +\item |
| 4116 | +Otherwise, |
| 4117 | +if \tcode{disp} is equal to \tcode{\exposid{disposition}::\exposid{error}}, |
| 4118 | +evaluates: |
| 4119 | +\begin{codeblock} |
| 4120 | +@\exposid{on_stop}@.reset(); |
| 4121 | +visit( |
| 4122 | + [&]<class Error>(Error& error) noexcept { |
| 4123 | + if constexpr (!@\libconcept{same_as}@<Error, @\exposid{none-such}@>) { |
| 4124 | + set_error(std::move(rcvr), std::move(error)); |
| 4125 | + } |
| 4126 | + }, |
| 4127 | + errors); |
| 4128 | +\end{codeblock} |
| 4129 | +\item |
| 4130 | +Otherwise, evaluates: |
| 4131 | +\begin{codeblock} |
| 4132 | +@\exposid{on_stop}@.reset(); |
| 4133 | +set_stopped(std::move(rcvr)); |
| 4134 | +\end{codeblock} |
| 4135 | +\end{itemize} |
| 4136 | + |
| 4137 | +\pnum |
| 4138 | +The member \tcode{\exposid{impls-for}<when_all_t>::\exposid{start}} |
| 4139 | +is initialized with a callable object |
| 4140 | +equivalent to the following lambda expression: |
| 4141 | +\begin{codeblock} |
| 4142 | +[]<class State, class Rcvr, class... Ops>( |
| 4143 | + State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void { |
| 4144 | + state.@\exposid{on_stop}@.emplace( |
| 4145 | + get_stop_token(get_env(rcvr)), |
| 4146 | + @\exposid{on-stop-request}@{state.@\exposid{stop_src}@}); |
| 4147 | + if (state.@\exposid{stop_src}@.stop_requested()) { |
| 4148 | + state.@\exposid{on_stop.}@reset(); |
| 4149 | + set_stopped(std::move(rcvr)); |
| 4150 | + } else { |
| 4151 | + (start(ops), ...); |
| 4152 | + } |
| 4153 | +} |
| 4154 | +\end{codeblock} |
| 4155 | + |
| 4156 | +\pnum |
| 4157 | +The member \exposid{\tcode{impls-for}<when_all_t>::\exposid{complete}} |
| 4158 | +is initialized with a callable object |
| 4159 | +equivalent to the following lambda expression: |
| 4160 | +\begin{codeblock} |
| 4161 | +[]<class Index, class State, class Rcvr, class Set, class... Args>( |
| 4162 | + this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void { |
| 4163 | + if constexpr (@\libconcept{same_as}@<Set, set_error_t>) { |
| 4164 | + if (@\exposid{disposition}@::@\exposid{error}@ != state.disp.exchange(@\exposid{disposition}@::@\exposid{error}@)) { |
| 4165 | + state.@\exposid{stop_src}@.request_stop(); |
| 4166 | + @\exposid{TRY-EMPLACE-ERROR}@(state.errors, std::forward<Args>(args)...); |
| 4167 | + } |
| 4168 | + } else if constexpr (@\libconcept{same_as}@<Set, set_stopped_t>) { |
| 4169 | + auto expected = @\exposid{disposition}@::@\exposid{started}@; |
| 4170 | + if (state.disp.compare_exchange_strong(expected, @\exposid{disposition}@::@\exposid{stopped}@)) { |
| 4171 | + state.@\exposid{stop_src}@.request_stop(); |
| 4172 | + } |
| 4173 | + } else if constexpr (!@\libconcept{same_as}@<decltype(State::values), tuple<>>) { |
| 4174 | + if (state.disp == @\exposid{disposition}@::@\exposid{started}@) { |
| 4175 | + auto& opt = get<Index::value>(state.values); |
| 4176 | + @\exposid{TRY-EMPLACE-VALUE}@(complete, opt, std::forward<Args>(args)...); |
| 4177 | + } |
| 4178 | + } |
| 4179 | + state.@\exposid{arrive}@(rcvr); |
| 4180 | +} |
| 4181 | +\end{codeblock} |
| 4182 | +where \tcode{\exposid{TRY-EMPLACE-ERROR}(v, e)}, |
| 4183 | +for subexpressions \tcode{v} and \tcode{e}, is equivalent to: |
| 4184 | +\begin{codeblock} |
| 4185 | +try { |
| 4186 | + v.template emplace<decltype(auto(e))>(e); |
| 4187 | +} catch (...) { |
| 4188 | + v.template emplace<exception_ptr>(current_exception()); |
| 4189 | +} |
| 4190 | +\end{codeblock} |
| 4191 | +if the expression \tcode{decltype(auto(e))(e)} is potentially throwing; |
| 4192 | +otherwise, \tcode{v.template emplace<decltype(auto(e))>(e)}; |
| 4193 | +and where \tcode{\exposid{TRY-EMPLACE-VALUE}(c, o, as...)}, |
| 4194 | +for subexpressions \tcode{c}, \tcode{o}, and pack of subexpressions \tcode{as}, |
| 4195 | +is equivalent to: |
| 4196 | +\begin{codeblock} |
| 4197 | +try { |
| 4198 | + o.emplace(as...); |
| 4199 | +} catch (...) { |
| 4200 | + c(Index(), state, rcvr, set_error, current_exception()); |
| 4201 | + return; |
| 4202 | +} |
| 4203 | +\end{codeblock} |
| 4204 | +if the expression \tcode{decayed-tuple<decltype(as)...>\{as...\}} |
| 4205 | +is potentially throwing; |
| 4206 | +otherwise, \tcode{o.emplace(as...)}. |
| 4207 | + |
| 4208 | +\pnum |
| 4209 | +The expression \tcode{when_all_with_variant(sndrs...)} |
| 4210 | +is expression-equivalent to: |
| 4211 | +\begin{codeblock} |
| 4212 | +transform_sender(CD(), @\exposid{make-sender}@(when_all_with_variant, {}, sndrs...)); |
| 4213 | +\end{codeblock} |
| 4214 | + |
| 4215 | +\pnum |
| 4216 | +Given subexpressions \tcode{sndr} and \tcode{env}, |
| 4217 | +if |
| 4218 | +\tcode{\exposconcept{sender-for}<decltype((sndr)), when_all_with_variant_t>} |
| 4219 | +is \tcode{false}, |
| 4220 | +then the expression \tcode{when_all_with_variant.transform_sender(sndr, env)} |
| 4221 | +is ill-formed; |
| 4222 | +otherwise, it is equivalent to: |
| 4223 | +\begin{codeblock} |
| 4224 | +auto&& [_, _, ...child] = sndr; |
| 4225 | +return when_all(into_variant(std::forward_like<decltype((sndr))>(child))...); |
| 4226 | +\end{codeblock} |
| 4227 | +\begin{note} |
| 4228 | +This causes the \tcode{when_all_with_variant(sndrs...)} sender |
| 4229 | +to become \tcode{when_all(into_variant(sndrs)...)} |
| 4230 | +when it is connected with a receiver |
| 4231 | +whose execution domain does not customize \tcode{when_all_with_variant}. |
| 4232 | +\end{note} |
| 4233 | + |
3946 | 4234 | \rSec1[exec.util]{Sender/receiver utilities}
|
3947 | 4235 |
|
3948 | 4236 |
|
|
0 commit comments