100.00% Lines (188/188) 100.00% Functions (45/45)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_WHEN_ALL_HPP 11   #ifndef BOOST_CAPY_WHEN_ALL_HPP
12   #define BOOST_CAPY_WHEN_ALL_HPP 12   #define BOOST_CAPY_WHEN_ALL_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/results_combinators.hpp> 15   #include <boost/capy/detail/results_combinators.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/concept/context_awaitable.hpp> 18   #include <boost/capy/concept/context_awaitable.hpp>
19   #include <coroutine> 19   #include <coroutine>
20   #include <boost/capy/detail/abandonable.hpp> 20   #include <boost/capy/detail/abandonable.hpp>
21   #include <boost/capy/detail/owned_range.hpp> 21   #include <boost/capy/detail/owned_range.hpp>
22   #include <boost/capy/ex/abandon.hpp> 22   #include <boost/capy/ex/abandon.hpp>
23   #include <boost/capy/ex/frame_alloc_mixin.hpp> 23   #include <boost/capy/ex/frame_alloc_mixin.hpp>
24   #include <boost/capy/ex/await_context.hpp> 24   #include <boost/capy/ex/await_context.hpp>
25   #include <boost/capy/ex/frame_allocator.hpp> 25   #include <boost/capy/ex/frame_allocator.hpp>
26   #include <boost/capy/task.hpp> 26   #include <boost/capy/task.hpp>
27   27  
28   #include <array> 28   #include <array>
29   #include <atomic> 29   #include <atomic>
30   #include <exception> 30   #include <exception>
31   #include <memory> 31   #include <memory>
32   #include <optional> 32   #include <optional>
33   #include <ranges> 33   #include <ranges>
34   #include <stdexcept> 34   #include <stdexcept>
35   #include <stop_token> 35   #include <stop_token>
36   #include <tuple> 36   #include <tuple>
37   #include <type_traits> 37   #include <type_traits>
38   #include <utility> 38   #include <utility>
39   #include <vector> 39   #include <vector>
40   40  
41   namespace boost { 41   namespace boost {
42   namespace capy { 42   namespace capy {
43   43  
44   namespace detail { 44   namespace detail {
45   45  
46   /** Holds the result of a single task within when_all. 46   /** Holds the result of a single task within when_all.
47   */ 47   */
48   template<typename T> 48   template<typename T>
49   struct result_holder 49   struct result_holder
50   { 50   {
51   std::optional<T> value_; 51   std::optional<T> value_;
52   52  
HITCBC 53   121 void set(T v) 53   121 void set(T v)
54   { 54   {
HITCBC 55   121 value_ = std::move(v); 55   121 value_ = std::move(v);
HITCBC 56   121 } 56   121 }
57   57  
HITCBC 58   105 T get() && 58   105 T get() &&
59   { 59   {
HITCBC 60   105 return std::move(*value_); 60   105 return std::move(*value_);
61   } 61   }
62   }; 62   };
63   63  
64   /** Core shared state for when_all operations. 64   /** Core shared state for when_all operations.
65   65  
66   Contains all members and methods common to both heterogeneous (variadic) 66   Contains all members and methods common to both heterogeneous (variadic)
67   and homogeneous (range) when_all implementations. State classes embed 67   and homogeneous (range) when_all implementations. State classes embed
68   this via composition to avoid CRTP destructor ordering issues. 68   this via composition to avoid CRTP destructor ordering issues.
69   69  
70   @par Thread Safety 70   @par Thread Safety
71   Atomic operations protect exception capture and completion count. 71   Atomic operations protect exception capture and completion count.
72   */ 72   */
73   struct when_all_core 73   struct when_all_core
74   { 74   {
75   std::atomic<std::size_t> remaining_count_; 75   std::atomic<std::size_t> remaining_count_;
76   76  
77   // Exception storage - first error wins, others discarded 77   // Exception storage - first error wins, others discarded
78   std::atomic<bool> has_exception_{false}; 78   std::atomic<bool> has_exception_{false};
79   std::exception_ptr first_exception_; 79   std::exception_ptr first_exception_;
80   80  
81   std::stop_source stop_source_; 81   std::stop_source stop_source_;
82   82  
83   // Bridges parent's stop token to our stop_source 83   // Bridges parent's stop token to our stop_source
84   struct stop_callback_fn 84   struct stop_callback_fn
85   { 85   {
86   std::stop_source* source_; 86   std::stop_source* source_;
HITCBC 87   5 void operator()() const 87   5 void operator()() const
88   { 88   {
89   // Stopping can lose the last branch and destroy the join, 89   // Stopping can lose the last branch and destroy the join,
90   // with its source, before the request returns; the copy 90   // with its source, before the request returns; the copy
91   // keeps the stop state alive until then. 91   // keeps the stop state alive until then.
HITCBC 92   5 std::stop_source keep = *source_; 92   5 std::stop_source keep = *source_;
HITCBC 93   5 keep.request_stop(); 93   5 keep.request_stop();
HITCBC 94   5 } 94   5 }
95   }; 95   };
96   using stop_callback_t = std::stop_callback<stop_callback_fn>; 96   using stop_callback_t = std::stop_callback<stop_callback_fn>;
97   std::optional<stop_callback_t> parent_stop_callback_; 97   std::optional<stop_callback_t> parent_stop_callback_;
98   98  
99   continuation continuation_; 99   continuation continuation_;
100   await_context const* caller_env_ = nullptr; 100   await_context const* caller_env_ = nullptr;
101   101  
102   // Set when a branch is abandoned before finishing: the parent is 102   // Set when a branch is abandoned before finishing: the parent is
103   // then abandoned rather than resumed. 103   // then abandoned rather than resumed.
104   std::atomic<bool> lost_{false}; 104   std::atomic<bool> lost_{false};
105   105  
106   /// Drop a share held by a runner that never finished. 106   /// Drop a share held by a runner that never finished.
HITCBC 107   8 void lose_branch() noexcept 107   8 void lose_branch() noexcept
108   { 108   {
HITCBC 109   8 lost_.store(true, std::memory_order_relaxed); 109   8 lost_.store(true, std::memory_order_relaxed);
HITCBC 110   8 stop_source_.request_stop(); 110   8 stop_source_.request_stop();
HITCBC 111   16 if(remaining_count_.fetch_sub(1, std::memory_order_acq_rel) == 1) 111   16 if(remaining_count_.fetch_sub(1, std::memory_order_acq_rel) == 1)
112   { 112   {
HITCBC 113   4 auto parent = continuation_; 113   4 auto parent = continuation_;
HITCBC 114   4 capy::abandon(parent); 114   4 capy::abandon(parent);
115   } 115   }
HITCBC 116   8 } 116   8 }
117   117  
HITCBC 118   98 explicit when_all_core(std::size_t count) noexcept 118   98 explicit when_all_core(std::size_t count) noexcept
HITCBC 119   98 : remaining_count_(count) 119   98 : remaining_count_(count)
120   { 120   {
HITCBC 121   98 } 121   98 }
122   122  
123   /** Capture an exception (first one wins). */ 123   /** Capture an exception (first one wins). */
HITCBC 124   21 void capture_exception(std::exception_ptr ep) 124   21 void capture_exception(std::exception_ptr ep)
125   { 125   {
HITCBC 126   21 bool expected = false; 126   21 bool expected = false;
HITCBC 127   21 if(has_exception_.compare_exchange_strong( 127   21 if(has_exception_.compare_exchange_strong(
128   expected, true, std::memory_order_relaxed)) 128   expected, true, std::memory_order_relaxed))
HITCBC 129   19 first_exception_ = ep; 129   19 first_exception_ = ep;
HITCBC 130   21 } 130   21 }
131   }; 131   };
132   132  
133   /** Shared state for heterogeneous when_all (variadic overload). 133   /** Shared state for heterogeneous when_all (variadic overload).
134   134  
135   @tparam Ts The result types of the tasks. 135   @tparam Ts The result types of the tasks.
136   */ 136   */
137   template<typename... Ts> 137   template<typename... Ts>
138   struct when_all_state 138   struct when_all_state
139   { 139   {
140   static constexpr std::size_t task_count = sizeof...(Ts); 140   static constexpr std::size_t task_count = sizeof...(Ts);
141   141  
142   when_all_core core_; 142   when_all_core core_;
143   std::tuple<result_holder<Ts>...> results_; 143   std::tuple<result_holder<Ts>...> results_;
144   std::array<continuation, task_count> runner_handles_{}; 144   std::array<continuation, task_count> runner_handles_{};
145   145  
146   std::atomic<bool> has_error_{false}; 146   std::atomic<bool> has_error_{false};
147   std::error_code first_error_; 147   std::error_code first_error_;
148   148  
HITCBC 149   78 when_all_state() 149   78 when_all_state()
HITCBC 150   78 : core_(task_count) 150   78 : core_(task_count)
151   { 151   {
HITCBC 152   78 } 152   78 }
153   153  
154   /** Record the first error (subsequent errors are discarded). */ 154   /** Record the first error (subsequent errors are discarded). */
HITCBC 155   46 void record_error(std::error_code ec) 155   46 void record_error(std::error_code ec)
156   { 156   {
HITCBC 157   46 bool expected = false; 157   46 bool expected = false;
HITCBC 158   46 if(has_error_.compare_exchange_strong( 158   46 if(has_error_.compare_exchange_strong(
159   expected, true, std::memory_order_relaxed)) 159   expected, true, std::memory_order_relaxed))
HITCBC 160   32 first_error_ = ec; 160   32 first_error_ = ec;
HITCBC 161   46 } 161   46 }
162   }; 162   };
163   163  
164   /** Shared state for homogeneous when_all (range overload). 164   /** Shared state for homogeneous when_all (range overload).
165   165  
166   Stores extracted results payloads in a vector indexed by task 166   Stores extracted results payloads in a vector indexed by task
167   position. Tracks the first error_code for error propagation. 167   position. Tracks the first error_code for error propagation.
168   168  
169   @tparam T The payload type extracted from `results`. 169   @tparam T The payload type extracted from `results`.
170   */ 170   */
171   template<typename T> 171   template<typename T>
172   struct when_all_homogeneous_state 172   struct when_all_homogeneous_state
173   { 173   {
174   when_all_core core_; 174   when_all_core core_;
175   std::vector<std::optional<T>> results_; 175   std::vector<std::optional<T>> results_;
176   std::unique_ptr<continuation[]> runner_handles_; 176   std::unique_ptr<continuation[]> runner_handles_;
177   177  
178   std::atomic<bool> has_error_{false}; 178   std::atomic<bool> has_error_{false};
179   std::error_code first_error_; 179   std::error_code first_error_;
180   180  
HITCBC 181   15 explicit when_all_homogeneous_state(std::size_t count) 181   15 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 182   15 : core_(count) 182   15 : core_(count)
HITCBC 183   30 , results_(count) 183   30 , results_(count)
HITCBC 184   15 , runner_handles_(std::make_unique<continuation[]>(count)) 184   15 , runner_handles_(std::make_unique<continuation[]>(count))
185   { 185   {
HITCBC 186   15 } 186   15 }
187   187  
HITCBC 188   27 void set_result(std::size_t index, T value) 188   27 void set_result(std::size_t index, T value)
189   { 189   {
HITCBC 190   27 results_[index].emplace(std::move(value)); 190   27 results_[index].emplace(std::move(value));
HITCBC 191   27 } 191   27 }
192   192  
193   /** Record the first error (subsequent errors are discarded). */ 193   /** Record the first error (subsequent errors are discarded). */
HITCBC 194   7 void record_error(std::error_code ec) 194   7 void record_error(std::error_code ec)
195   { 195   {
HITCBC 196   7 bool expected = false; 196   7 bool expected = false;
HITCBC 197   7 if(has_error_.compare_exchange_strong( 197   7 if(has_error_.compare_exchange_strong(
198   expected, true, std::memory_order_relaxed)) 198   expected, true, std::memory_order_relaxed))
HITCBC 199   5 first_error_ = ec; 199   5 first_error_ = ec;
HITCBC 200   7 } 200   7 }
201   }; 201   };
202   202  
203   /** Specialization for void `results` children (no payload storage). */ 203   /** Specialization for void `results` children (no payload storage). */
204   template<> 204   template<>
205   struct when_all_homogeneous_state<std::tuple<>> 205   struct when_all_homogeneous_state<std::tuple<>>
206   { 206   {
207   when_all_core core_; 207   when_all_core core_;
208   std::unique_ptr<continuation[]> runner_handles_; 208   std::unique_ptr<continuation[]> runner_handles_;
209   209  
210   std::atomic<bool> has_error_{false}; 210   std::atomic<bool> has_error_{false};
211   std::error_code first_error_; 211   std::error_code first_error_;
212   212  
HITCBC 213   5 explicit when_all_homogeneous_state(std::size_t count) 213   5 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 214   5 : core_(count) 214   5 : core_(count)
HITCBC 215   5 , runner_handles_(std::make_unique<continuation[]>(count)) 215   5 , runner_handles_(std::make_unique<continuation[]>(count))
216   { 216   {
HITCBC 217   5 } 217   5 }
218   218  
219   /** Record the first error (subsequent errors are discarded). */ 219   /** Record the first error (subsequent errors are discarded). */
HITCBC 220   1 void record_error(std::error_code ec) 220   1 void record_error(std::error_code ec)
221   { 221   {
HITCBC 222   1 bool expected = false; 222   1 bool expected = false;
HITCBC 223   1 if(has_error_.compare_exchange_strong( 223   1 if(has_error_.compare_exchange_strong(
224   expected, true, std::memory_order_relaxed)) 224   expected, true, std::memory_order_relaxed))
HITCBC 225   1 first_error_ = ec; 225   1 first_error_ = ec;
HITCBC 226   1 } 226   1 }
227   }; 227   };
228   228  
229   /** Wrapper coroutine that intercepts task completion for when_all. 229   /** Wrapper coroutine that intercepts task completion for when_all.
230   230  
231   Parameterized on StateType to work with both heterogeneous (variadic) 231   Parameterized on StateType to work with both heterogeneous (variadic)
232   and homogeneous (range) state types. All state types expose their 232   and homogeneous (range) state types. All state types expose their
233   shared members through a `core_` member of type when_all_core. 233   shared members through a `core_` member of type when_all_core.
234   234  
235   @tparam StateType The state type (when_all_state or when_all_homogeneous_state). 235   @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
236   */ 236   */
237   template<typename StateType> 237   template<typename StateType>
238   struct when_all_runner 238   struct when_all_runner
239   { 239   {
240   struct promise_type 240   struct promise_type
241   : detail::process_frame_mixin 241   : detail::process_frame_mixin
242   { 242   {
243   StateType* state_ = nullptr; 243   StateType* state_ = nullptr;
244   std::size_t index_ = 0; 244   std::size_t index_ = 0;
245   await_context env_; 245   await_context env_;
246   // Owns the branch's segment. 246   // Owns the branch's segment.
247   chain_owner segment_; 247   chain_owner segment_;
248   bool done_ = false; 248   bool done_ = false;
249   249  
HITCBC 250   209 ~promise_type() 250   209 ~promise_type()
251   { 251   {
HITCBC 252   209 if(!done_ && state_) 252   209 if(!done_ && state_)
HITCBC 253   8 state_->core_.lose_branch(); 253   8 state_->core_.lose_branch();
HITCBC 254   209 } 254   209 }
255   255  
HITCBC 256   209 when_all_runner get_return_object() noexcept 256   209 when_all_runner get_return_object() noexcept
257   { 257   {
258   return when_all_runner( 258   return when_all_runner(
HITCBC 259   209 std::coroutine_handle<promise_type>::from_promise(*this)); 259   209 std::coroutine_handle<promise_type>::from_promise(*this));
260   } 260   }
261   261  
HITCBC 262   209 std::suspend_always initial_suspend() noexcept 262   209 std::suspend_always initial_suspend() noexcept
263   { 263   {
HITCBC 264   209 return {}; 264   209 return {};
265   } 265   }
266   266  
HITCBC 267   201 auto final_suspend() noexcept 267   201 auto final_suspend() noexcept
268   { 268   {
269   struct awaiter 269   struct awaiter
270   { 270   {
271   promise_type* p_; 271   promise_type* p_;
HITCBC 272   201 bool await_ready() const noexcept { return false; } 272   201 bool await_ready() const noexcept { return false; }
HITCBC 273   201 auto await_suspend(std::coroutine_handle<> h) noexcept 273   201 auto await_suspend(std::coroutine_handle<> h) noexcept
274   { 274   {
HITCBC 275   201 auto& core = p_->state_->core_; 275   201 auto& core = p_->state_->core_;
HITCBC 276   201 auto* counter = &core.remaining_count_; 276   201 auto* counter = &core.remaining_count_;
HITCBC 277   201 auto* lost = &core.lost_; 277   201 auto* lost = &core.lost_;
HITCBC 278   201 auto* caller_env = core.caller_env_; 278   201 auto* caller_env = core.caller_env_;
HITCBC 279   201 auto& cont = core.continuation_; 279   201 auto& cont = core.continuation_;
280   280  
HITCBC 281   201 p_->done_ = true; 281   201 p_->done_ = true;
282   // Resumed to unwind its abandoned branch: lost, not 282   // Resumed to unwind its abandoned branch: lost, not
283   // finished. Stop is requested while this branch 283   // finished. Stop is requested while this branch
284   // still holds its share. 284   // still holds its share.
HITCBC 285   201 if(p_->segment_.abandoned()) 285   201 if(p_->segment_.abandoned())
286   { 286   {
HITCBC 287   19 lost->store(true, std::memory_order_relaxed); 287   19 lost->store(true, std::memory_order_relaxed);
HITCBC 288   19 core.stop_source_.request_stop(); 288   19 core.stop_source_.request_stop();
289   } 289   }
HITCBC 290   201 h.destroy(); 290   201 h.destroy();
291   291  
HITCBC 292   201 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 292   201 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 293   201 if(remaining != 1) 293   201 if(remaining != 1)
HITCBC 294   107 return detail::symmetric_transfer(std::noop_coroutine()); 294   107 return detail::symmetric_transfer(std::noop_coroutine());
HITCBC 295   94 if(lost->load(std::memory_order_relaxed)) 295   94 if(lost->load(std::memory_order_relaxed))
296   { 296   {
HITCBC 297   10 auto parent = cont; 297   10 auto parent = cont;
HITCBC 298   10 capy::abandon(parent); 298   10 capy::abandon(parent);
HITCBC 299   10 return detail::symmetric_transfer(std::noop_coroutine()); 299   10 return detail::symmetric_transfer(std::noop_coroutine());
300   } 300   }
HITCBC 301   84 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 301   84 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
302   } 302   }
303   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 303   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
304   }; 304   };
HITCBC 305   201 return awaiter{this}; 305   201 return awaiter{this};
306   } 306   }
307   307  
HITCBC 308   180 void return_void() noexcept {} 308   180 void return_void() noexcept {}
309   309  
HITCBC 310   21 void unhandled_exception() noexcept 310   21 void unhandled_exception() noexcept
311   { 311   {
HITCBC 312   21 state_->core_.capture_exception(std::current_exception()); 312   21 state_->core_.capture_exception(std::current_exception());
HITCBC 313   21 state_->core_.stop_source_.request_stop(); 313   21 state_->core_.stop_source_.request_stop();
HITCBC 314   21 } 314   21 }
315   315  
316   template<class Awaitable> 316   template<class Awaitable>
317   struct transform_awaiter 317   struct transform_awaiter
318   { 318   {
319   std::decay_t<Awaitable> a_; 319   std::decay_t<Awaitable> a_;
320   promise_type* p_; 320   promise_type* p_;
321   321  
HITCBC 322   203 bool await_ready() { return a_.await_ready(); } 322   203 bool await_ready() { return a_.await_ready(); }
HITCBC 323   201 decltype(auto) await_resume() { return a_.await_resume(); } 323   201 decltype(auto) await_resume() { return a_.await_resume(); }
324   324  
325   template<class Promise> 325   template<class Promise>
HITCBC 326   203 auto await_suspend(std::coroutine_handle<Promise> h) 326   203 auto await_suspend(std::coroutine_handle<Promise> h)
327   { 327   {
328   using R = decltype(a_.await_suspend(h, &p_->env_)); 328   using R = decltype(a_.await_suspend(h, &p_->env_));
329   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 329   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 330   203 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_)); 330   203 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
331   else 331   else
332   return a_.await_suspend(h, &p_->env_); 332   return a_.await_suspend(h, &p_->env_);
333   } 333   }
334   }; 334   };
335   335  
336   template<class Awaitable> 336   template<class Awaitable>
HITCBC 337   203 auto await_transform(Awaitable&& a) 337   203 auto await_transform(Awaitable&& a)
338   { 338   {
339   using A = std::decay_t<Awaitable>; 339   using A = std::decay_t<Awaitable>;
340   if constexpr (ContextAwaitable<A>) 340   if constexpr (ContextAwaitable<A>)
341   { 341   {
342   return transform_awaiter<Awaitable>{ 342   return transform_awaiter<Awaitable>{
HITCBC 343   203 std::forward<Awaitable>(a), this}; 343   203 std::forward<Awaitable>(a), this};
344   } 344   }
345   else 345   else
346   { 346   {
347   static_assert(sizeof(A) == 0, "requires ContextAwaitable"); 347   static_assert(sizeof(A) == 0, "requires ContextAwaitable");
348   } 348   }
349   } 349   }
350   }; 350   };
351   351  
352   std::coroutine_handle<promise_type> h_; 352   std::coroutine_handle<promise_type> h_;
353   353  
HITCBC 354   209 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept 354   209 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 355   209 : h_(h) 355   209 : h_(h)
356   { 356   {
HITCBC 357   209 } 357   209 }
358   358  
359   // Enable move for all clang versions - some versions need it 359   // Enable move for all clang versions - some versions need it
360   when_all_runner(when_all_runner&& other) noexcept 360   when_all_runner(when_all_runner&& other) noexcept
361   : h_(std::exchange(other.h_, nullptr)) 361   : h_(std::exchange(other.h_, nullptr))
362   { 362   {
363   } 363   }
364   364  
365   when_all_runner(when_all_runner const&) = delete; 365   when_all_runner(when_all_runner const&) = delete;
366   when_all_runner& operator=(when_all_runner const&) = delete; 366   when_all_runner& operator=(when_all_runner const&) = delete;
367   when_all_runner& operator=(when_all_runner&&) = delete; 367   when_all_runner& operator=(when_all_runner&&) = delete;
368   368  
HITCBC 369   209 auto release() noexcept 369   209 auto release() noexcept
370   { 370   {
HITCBC 371   209 return std::exchange(h_, nullptr); 371   209 return std::exchange(h_, nullptr);
372   } 372   }
373   }; 373   };
374   374  
375   /** Create a `results`-aware runner for a single awaitable (range path). 375   /** Create a `results`-aware runner for a single awaitable (range path).
376   376  
377   Checks the error code, records errors and requests stop on failure, 377   Checks the error code, records errors and requests stop on failure,
378   or extracts the payload on success. 378   or extracts the payload on success.
379   */ 379   */
380   template<ContextAwaitable Awaitable, typename StateType> 380   template<ContextAwaitable Awaitable, typename StateType>
381   when_all_runner<StateType> 381   when_all_runner<StateType>
HITCBC 382   48 make_when_all_homogeneous_runner(Awaitable* inner, StateType* state, std::size_t index) 382   48 make_when_all_homogeneous_runner(Awaitable* inner, StateType* state, std::size_t index)
383   { 383   {
384   // Taken from the join's storage only once running, into a local: 384   // Taken from the join's storage only once running, into a local:
385   // a runner destroyed unstarted owns no branch, and one destroyed 385   // a runner destroyed unstarted owns no branch, and one destroyed
386   // unfinished destroys it before its promise reports the loss. 386   // unfinished destroys it before its promise reports the loss.
387   Awaitable a(std::move(*inner)); 387   Awaitable a(std::move(*inner));
388   auto r = co_await abandonable<Awaitable>{a}; 388   auto r = co_await abandonable<Awaitable>{a};
389   if(!r) 389   if(!r)
390   co_return; 390   co_return;
391   auto& result = *r; 391   auto& result = *r;
392   392  
393   if(std::get<0>(result)) 393   if(std::get<0>(result))
394   { 394   {
395   state->record_error(std::get<0>(result)); 395   state->record_error(std::get<0>(result));
396   state->core_.stop_source_.request_stop(); 396   state->core_.stop_source_.request_stop();
397   } 397   }
398   else 398   else
399   { 399   {
400   using PayloadT = results_payload_t< 400   using PayloadT = results_payload_t<
401   awaitable_result_t<Awaitable>>; 401   awaitable_result_t<Awaitable>>;
402   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 402   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
403   { 403   {
404   state->set_result(index, 404   state->set_result(index,
405   extract_results_payload(std::move(result))); 405   extract_results_payload(std::move(result)));
406   } 406   }
407   } 407   }
HITCBC 408   96 } 408   96 }
409   409  
410   /** Create a runner for `results` children that requests stop on ec. */ 410   /** Create a runner for `results` children that requests stop on ec. */
411   template<std::size_t Index, ContextAwaitable Awaitable, typename... Ts> 411   template<std::size_t Index, ContextAwaitable Awaitable, typename... Ts>
412   when_all_runner<when_all_state<Ts...>> 412   when_all_runner<when_all_state<Ts...>>
HITCBC 413   161 make_when_all_results_runner(Awaitable* inner, when_all_state<Ts...>* state) 413   161 make_when_all_results_runner(Awaitable* inner, when_all_state<Ts...>* state)
414   { 414   {
415   // Taken from the join's storage only once running, into a local: 415   // Taken from the join's storage only once running, into a local:
416   // a runner destroyed unstarted owns no branch, and one destroyed 416   // a runner destroyed unstarted owns no branch, and one destroyed
417   // unfinished destroys it before its promise reports the loss. 417   // unfinished destroys it before its promise reports the loss.
418   Awaitable a(std::move(*inner)); 418   Awaitable a(std::move(*inner));
419   auto r = co_await abandonable<Awaitable>{a}; 419   auto r = co_await abandonable<Awaitable>{a};
420   if(!r) 420   if(!r)
421   co_return; 421   co_return;
422   auto ec = std::get<0>(*r); 422   auto ec = std::get<0>(*r);
423   std::get<Index>(state->results_).set(std::move(*r)); 423   std::get<Index>(state->results_).set(std::move(*r));
424   424  
425   if(ec) 425   if(ec)
426   { 426   {
427   state->record_error(ec); 427   state->record_error(ec);
428   state->core_.stop_source_.request_stop(); 428   state->core_.stop_source_.request_stop();
429   } 429   }
HITCBC 430   322 } 430   322 }
431   431  
432   /** Launcher that uses `results`-aware runners. */ 432   /** Launcher that uses `results`-aware runners. */
433   template<ContextAwaitable... Awaitables> 433   template<ContextAwaitable... Awaitables>
434   class when_all_results_launcher 434   class when_all_results_launcher
435   { 435   {
436   using state_type = when_all_state<awaitable_result_t<Awaitables>...>; 436   using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
437   437  
438   std::tuple<Awaitables...>* awaitables_; 438   std::tuple<Awaitables...>* awaitables_;
439   state_type* state_; 439   state_type* state_;
440   440  
441   public: 441   public:
HITCBC 442   78 when_all_results_launcher( 442   78 when_all_results_launcher(
443   std::tuple<Awaitables...>* awaitables, 443   std::tuple<Awaitables...>* awaitables,
444   state_type* state) 444   state_type* state)
HITCBC 445   78 : awaitables_(awaitables) 445   78 : awaitables_(awaitables)
HITCBC 446   78 , state_(state) 446   78 , state_(state)
447   { 447   {
HITCBC 448   78 } 448   78 }
449   449  
HITCBC 450   78 bool await_ready() const noexcept 450   78 bool await_ready() const noexcept
451   { 451   {
HITCBC 452   78 return sizeof...(Awaitables) == 0; 452   78 return sizeof...(Awaitables) == 0;
453   } 453   }
454   454  
HITCBC 455   78 std::coroutine_handle<> await_suspend( 455   78 std::coroutine_handle<> await_suspend(
456   std::coroutine_handle<> continuation, await_context const* caller_env) 456   std::coroutine_handle<> continuation, await_context const* caller_env)
457   { 457   {
HITCBC 458   78 state_->core_.continuation_.h = continuation; 458   78 state_->core_.continuation_.h = continuation;
HITCBC 459   78 state_->core_.continuation_.owner = caller_env->owner; 459   78 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 460   78 state_->core_.caller_env_ = caller_env; 460   78 state_->core_.caller_env_ = caller_env;
461   461  
HITCBC 462   78 if(caller_env->stop_token.stop_possible()) 462   78 if(caller_env->stop_token.stop_possible())
463   { 463   {
HITCBC 464   8 state_->core_.parent_stop_callback_.emplace( 464   8 state_->core_.parent_stop_callback_.emplace(
HITCBC 465   4 caller_env->stop_token, 465   4 caller_env->stop_token,
HITCBC 466   4 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 466   4 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
467   467  
HITCBC 468   4 if(caller_env->stop_token.stop_requested()) 468   4 if(caller_env->stop_token.stop_requested())
HITCBC 469   1 state_->core_.stop_source_.request_stop(); 469   1 state_->core_.stop_source_.request_stop();
470   } 470   }
471   471  
HITCBC 472   78 auto token = state_->core_.stop_source_.get_token(); 472   78 auto token = state_->core_.stop_source_.get_token();
HITCBC 473   78 launch_all(std::index_sequence_for<Awaitables...>{}, 473   78 launch_all(std::index_sequence_for<Awaitables...>{},
474   caller_env->executor, token); 474   caller_env->executor, token);
475   475  
HITCBC 476   156 return std::noop_coroutine(); 476   156 return std::noop_coroutine();
HITCBC 477   78 } 477   78 }
478   478  
HITCBC 479   66 void await_resume() const noexcept {} 479   66 void await_resume() const noexcept {}
480   480  
481   private: 481   private:
482   template<std::size_t... Is> 482   template<std::size_t... Is>
HITCBC 483   78 void launch_all(std::index_sequence<Is...>, 483   78 void launch_all(std::index_sequence<Is...>,
484   executor_ref ex, std::stop_token token) 484   executor_ref ex, std::stop_token token)
485   { 485   {
HITCBC 486   78 (..., launch_one<Is>(ex, token)); 486   78 (..., launch_one<Is>(ex, token));
HITCBC 487   78 } 487   78 }
488   488  
489   template<std::size_t I> 489   template<std::size_t I>
HITCBC 490   161 void launch_one(executor_ref caller_ex, std::stop_token token) 490   161 void launch_one(executor_ref caller_ex, std::stop_token token)
491   { 491   {
HITCBC 492   161 auto runner = make_when_all_results_runner<I>( 492   161 auto runner = make_when_all_results_runner<I>(
HITCBC 493   161 &std::get<I>(*awaitables_), state_); 493   161 &std::get<I>(*awaitables_), state_);
494   494  
HITCBC 495   161 auto h = runner.release(); 495   161 auto h = runner.release();
HITCBC 496   161 h.promise().state_ = state_; 496   161 h.promise().state_ = state_;
HITCBC 497   161 h.promise().segment_.bind(h, caller_ex); 497   161 h.promise().segment_.bind(h, caller_ex);
HITCBC 498   161 h.promise().env_ = await_context{caller_ex, token, 498   161 h.promise().env_ = await_context{caller_ex, token,
HITCBC 499   161 state_->core_.caller_env_->frame_allocator, 499   161 state_->core_.caller_env_->frame_allocator,
HITCBC 500   322 &h.promise().segment_}; 500   322 &h.promise().segment_};
501   501  
HITCBC 502   161 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 502   161 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 503   161 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]); 503   161 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]);
HITCBC 504   322 } 504   322 }
505   }; 505   };
506   506  
507   /** Helper to extract a single result from state. 507   /** Helper to extract a single result from state.
508   This is a separate function to work around a GCC-11 ICE that occurs 508   This is a separate function to work around a GCC-11 ICE that occurs
509   when using nested immediately-invoked lambdas with pack expansion. 509   when using nested immediately-invoked lambdas with pack expansion.
510   */ 510   */
511   template<std::size_t I, typename... Ts> 511   template<std::size_t I, typename... Ts>
HITCBC 512   105 auto extract_single_result(when_all_state<Ts...>& state) 512   105 auto extract_single_result(when_all_state<Ts...>& state)
513   { 513   {
HITCBC 514   105 return std::move(std::get<I>(state.results_)).get(); 514   105 return std::move(std::get<I>(state.results_)).get();
515   } 515   }
516   516  
517   /** Extract all results from state as a tuple. 517   /** Extract all results from state as a tuple.
518   */ 518   */
519   template<typename... Ts> 519   template<typename... Ts>
HITCBC 520   50 auto extract_results(when_all_state<Ts...>& state) 520   50 auto extract_results(when_all_state<Ts...>& state)
521   { 521   {
HITCBC 522   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) { 522   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
523   // Explicit element types: CTAD would collapse a single 523   // Explicit element types: CTAD would collapse a single
524   // `results` child via the tuple copy deduction guide 524   // `results` child via the tuple copy deduction guide
525   return std::tuple< 525   return std::tuple<
526   decltype(extract_single_result<Is>(state))...>( 526   decltype(extract_single_result<Is>(state))...>(
HITCBC 527   50 extract_single_result<Is>(state)...); 527   50 extract_single_result<Is>(state)...);
HITCBC 528   100 }(std::index_sequence_for<Ts...>{}); 528   100 }(std::index_sequence_for<Ts...>{});
529   } 529   }
530   530  
531   /** Starts all homogeneous runners concurrently. 531   /** Starts all homogeneous runners concurrently.
532   532  
533   Two-phase approach: create all runners first, then post all. 533   Two-phase approach: create all runners first, then post all.
534   This avoids lifetime issues if a task completes synchronously. 534   This avoids lifetime issues if a task completes synchronously.
535   */ 535   */
536   template<typename Range> 536   template<typename Range>
537   class when_all_homogeneous_launcher 537   class when_all_homogeneous_launcher
538   { 538   {
539   using Awaitable = std::ranges::range_value_t<Range>; 539   using Awaitable = std::ranges::range_value_t<Range>;
540   using PayloadT = results_payload_t<awaitable_result_t<Awaitable>>; 540   using PayloadT = results_payload_t<awaitable_result_t<Awaitable>>;
541   541  
542   Range* range_; 542   Range* range_;
543   when_all_homogeneous_state<PayloadT>* state_; 543   when_all_homogeneous_state<PayloadT>* state_;
544   544  
545   public: 545   public:
HITCBC 546   20 when_all_homogeneous_launcher( 546   20 when_all_homogeneous_launcher(
547   Range* range, 547   Range* range,
548   when_all_homogeneous_state<PayloadT>* state) 548   when_all_homogeneous_state<PayloadT>* state)
HITCBC 549   20 : range_(range) 549   20 : range_(range)
HITCBC 550   20 , state_(state) 550   20 , state_(state)
551   { 551   {
HITCBC 552   20 } 552   20 }
553   553  
HITCBC 554   20 bool await_ready() const noexcept 554   20 bool await_ready() const noexcept
555   { 555   {
HITCBC 556   20 return std::ranges::empty(*range_); 556   20 return std::ranges::empty(*range_);
557   } 557   }
558   558  
HITCBC 559   20 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, await_context const* caller_env) 559   20 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, await_context const* caller_env)
560   { 560   {
HITCBC 561   20 state_->core_.continuation_.h = continuation; 561   20 state_->core_.continuation_.h = continuation;
HITCBC 562   20 state_->core_.continuation_.owner = caller_env->owner; 562   20 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 563   20 state_->core_.caller_env_ = caller_env; 563   20 state_->core_.caller_env_ = caller_env;
564   564  
HITCBC 565   20 if(caller_env->stop_token.stop_possible()) 565   20 if(caller_env->stop_token.stop_possible())
566   { 566   {
HITCBC 567   4 state_->core_.parent_stop_callback_.emplace( 567   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 568   2 caller_env->stop_token, 568   2 caller_env->stop_token,
HITCBC 569   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 569   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
570   570  
HITCBC 571   2 if(caller_env->stop_token.stop_requested()) 571   2 if(caller_env->stop_token.stop_requested())
HITCBC 572   1 state_->core_.stop_source_.request_stop(); 572   1 state_->core_.stop_source_.request_stop();
573   } 573   }
574   574  
HITCBC 575   20 auto token = state_->core_.stop_source_.get_token(); 575   20 auto token = state_->core_.stop_source_.get_token();
576   576  
577   // Phase 1: Create all runners without dispatching. 577   // Phase 1: Create all runners without dispatching.
HITCBC 578   20 std::size_t index = 0; 578   20 std::size_t index = 0;
HITCBC 579   68 for(auto&& a : *range_) 579   68 for(auto&& a : *range_)
580   { 580   {
HITCBC 581   48 auto runner = make_when_all_homogeneous_runner( 581   48 auto runner = make_when_all_homogeneous_runner(
582   &a, state_, index); 582   &a, state_, index);
583   583  
HITCBC 584   48 auto h = runner.release(); 584   48 auto h = runner.release();
HITCBC 585   48 h.promise().state_ = state_; 585   48 h.promise().state_ = state_;
HITCBC 586   48 h.promise().index_ = index; 586   48 h.promise().index_ = index;
HITCBC 587   48 h.promise().segment_.bind(h, caller_env->executor); 587   48 h.promise().segment_.bind(h, caller_env->executor);
HITCBC 588   48 h.promise().env_ = await_context{caller_env->executor, token, 588   48 h.promise().env_ = await_context{caller_env->executor, token,
HITCBC 589   96 caller_env->frame_allocator, &h.promise().segment_}; 589   96 caller_env->frame_allocator, &h.promise().segment_};
590   590  
HITCBC 591   48 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 591   48 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 592   48 ++index; 592   48 ++index;
593   } 593   }
594   594  
595   // Phase 2: Post all runners. Any may complete synchronously. 595   // Phase 2: Post all runners. Any may complete synchronously.
596   // After last post, state_ and this may be destroyed. 596   // After last post, state_ and this may be destroyed.
HITCBC 597   20 auto* handles = state_->runner_handles_.get(); 597   20 auto* handles = state_->runner_handles_.get();
HITCBC 598   20 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 598   20 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
HITCBC 599   68 for(std::size_t i = 0; i < count; ++i) 599   68 for(std::size_t i = 0; i < count; ++i)
HITCBC 600   48 caller_env->executor.post(handles[i]); 600   48 caller_env->executor.post(handles[i]);
601   601  
HITCBC 602   40 return std::noop_coroutine(); 602   40 return std::noop_coroutine();
HITCBC 603   68 } 603   68 }
604   604  
HITCBC 605   18 void await_resume() const noexcept 605   18 void await_resume() const noexcept
606   { 606   {
HITCBC 607   18 } 607   18 }
608   }; 608   };
609   609  
610   } // namespace detail 610   } // namespace detail
611   611  
612   /** Execute a range of `results`-returning awaitables concurrently. 612   /** Execute a range of `results`-returning awaitables concurrently.
613   613  
614   Starts all awaitables simultaneously and waits for all to complete. 614   Starts all awaitables simultaneously and waits for all to complete.
615   On success, extracted payloads are collected in a vector preserving 615   On success, extracted payloads are collected in a vector preserving
616   input order. The first error_code makes a stop request that every 616   input order. The first error_code makes a stop request that every
617   sibling observes, and is propagated in the outer results. 617   sibling observes, and is propagated in the outer results.
618   Exceptions always beat error codes. 618   Exceptions always beat error codes.
619   619  
620   @li All child awaitables run concurrently on the caller's executor. 620   @li All child awaitables run concurrently on the caller's executor.
621   @li Payloads are returned as a vector in input order. 621   @li Payloads are returned as a vector in input order.
622   @li First error_code wins and makes a stop request that siblings observe. 622   @li First error_code wins and makes a stop request that siblings observe.
623   @li Exception always beats error_code. 623   @li Exception always beats error_code.
624   @li Completes only after all children have finished. 624   @li Completes only after all children have finished.
625   625  
626   @par Await-effects 626   @par Await-effects
627   627  
628   Takes ownership of the range, creates one wrapper coroutine per 628   Takes ownership of the range, creates one wrapper coroutine per
629   element, then posts every wrapper to the caller's executor. All 629   element, then posts every wrapper to the caller's executor. All
630   children therefore run concurrently, each awaited with the caller's 630   children therefore run concurrently, each awaited with the caller's
631   executor and frame allocator and with a stop token owned by this 631   executor and frame allocator and with a stop token owned by this
632   operation. 632   operation.
633   633  
634   Awaiting an empty range throws `std::invalid_argument` before any 634   Awaiting an empty range throws `std::invalid_argument` before any
635   child is started. 635   child is started.
636   636  
637   A stop request is made on the operation's own stop token when: 637   A stop request is made on the operation's own stop token when:
638   638  
639   @li a child await-returns a non-zero `ec`, or 639   @li a child await-returns a non-zero `ec`, or
640   @li a child exits via an exception, or 640   @li a child exits via an exception, or
641   @li the caller's stop token is triggered. 641   @li the caller's stop token is triggered.
642   642  
643   Every sibling observes that request through the stop token it was 643   Every sibling observes that request through the stop token it was
644   awaited with. The request does not end the operation: the await 644   awaited with. The request does not end the operation: the await
645   completes only after every child has finished. 645   completes only after every child has finished.
646   646  
647   @par Await-returns 647   @par Await-returns
648   An object of type `results<std::vector<PayloadT>>` destructuring as 648   An object of type `results<std::vector<PayloadT>>` destructuring as
649   `[ec, values]`, where `PayloadT` is the payload of one child's 649   `[ec, values]`, where `PayloadT` is the payload of one child's
650   `results`. 650   `results`.
651   651  
652   `ec` is the first non-zero `ec` await-returned by a child, in 652   `ec` is the first non-zero `ec` await-returned by a child, in
653   completion order rather than input order. The `ec` of every other 653   completion order rather than input order. The `ec` of every other
654   child is discarded. 654   child is discarded.
655   655  
656   On success, `values` holds one payload per element of the input 656   On success, `values` holds one payload per element of the input
657   range, in input order. If `ec` is set, `values` is empty: the 657   range, in input order. If `ec` is set, `values` is empty: the
658   payloads of the children that did succeed are discarded. 658   payloads of the children that did succeed are discarded.
659   659  
660   If any child exits via an exception, the first such exception is 660   If any child exits via an exception, the first such exception is
661   rethrown instead of await-returning, even when a child also reported 661   rethrown instead of await-returning, even when a child also reported
662   an `ec`. 662   an `ec`.
663   663  
664   @par Await-postcondition 664   @par Await-postcondition
665   Every child has finished. `ec` is success only if every child 665   Every child has finished. `ec` is success only if every child
666   await-returned success. If `ec` is success, `values` holds one 666   await-returned success. If `ec` is success, `values` holds one
667   payload per input awaitable; otherwise `values` is empty. 667   payload per input awaitable; otherwise `values` is empty.
668   668  
669   @par Remarks 669   @par Remarks
670   Supports _ContextAwaitable cancellation_. 670   Supports _ContextAwaitable cancellation_.
671   671  
672   @par Thread Safety 672   @par Thread Safety
673   The returned task must be awaited from a single execution context. 673   The returned task must be awaited from a single execution context.
674   Child awaitables execute concurrently but complete through the caller's 674   Child awaitables execute concurrently but complete through the caller's
675   executor. 675   executor.
676   676  
677   @param awaitables Range of `results`-returning awaitables to execute 677   @param awaitables Range of `results`-returning awaitables to execute
678   concurrently (must not be empty). 678   concurrently (must not be empty).
679   679  
680   @return A task yielding `results<vector<PayloadT>>` where PayloadT 680   @return A task yielding `results<vector<PayloadT>>` where PayloadT
681   is the payload extracted from each child's results. 681   is the payload extracted from each child's results.
682   682  
683   @throws std::invalid_argument if range is empty (thrown before 683   @throws std::invalid_argument if range is empty (thrown before
684   coroutine suspends). 684   coroutine suspends).
685   685  
686   @par Exception Safety 686   @par Exception Safety
687   If a child throws, the first child exception is rethrown after 687   If a child throws, the first child exception is rethrown after
688   all children complete (exception beats error_code). 688   all children complete (exception beats error_code).
689   689  
690   @note If a branch's coroutine is abandoned before it finishes, for 690   @note If a branch's coroutine is abandoned before it finishes, for
691   example by teardown of its execution context, `when_all` 691   example by teardown of its execution context, `when_all`
692   requests stop on the remaining branches, and the awaiting 692   requests stop on the remaining branches, and the awaiting
693   parent is abandoned rather than resumed once the last branch 693   parent is abandoned rather than resumed once the last branch
694   has finished or been abandoned. 694   has finished or been abandoned.
695   695  
696   @par Example 696   @par Example
697   @par !example example_1 697   @par !example example_1
698   698  
699   699  
700   @see ContextAwaitableRange, when_all 700   @see ContextAwaitableRange, when_all
701   */ 701   */
702   template<ContextAwaitableRange R> 702   template<ContextAwaitableRange R>
703   requires detail::is_results_v< 703   requires detail::is_results_v<
704   awaitable_result_t<std::ranges::range_value_t<R>>> 704   awaitable_result_t<std::ranges::range_value_t<R>>>
705   && (!std::is_same_v< 705   && (!std::is_same_v<
706   detail::results_payload_t< 706   detail::results_payload_t<
707   awaitable_result_t<std::ranges::range_value_t<R>>>, 707   awaitable_result_t<std::ranges::range_value_t<R>>>,
708   std::tuple<>>) 708   std::tuple<>>)
HITCBC 709   17 [[nodiscard]] auto when_all(R&& awaitables) 709   17 [[nodiscard]] auto when_all(R&& awaitables)
710   -> task<results<std::vector< 710   -> task<results<std::vector<
711   detail::results_payload_t< 711   detail::results_payload_t<
712   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 712   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
713   { 713   {
714   using Awaitable = std::ranges::range_value_t<R>; 714   using Awaitable = std::ranges::range_value_t<R>;
715   using PayloadT = detail::results_payload_t< 715   using PayloadT = detail::results_payload_t<
716   awaitable_result_t<Awaitable>>; 716   awaitable_result_t<Awaitable>>;
717   using OwnedRange = detail::owned_range_t<R>; 717   using OwnedRange = detail::owned_range_t<R>;
718   718  
719   auto count = std::ranges::size(awaitables); 719   auto count = std::ranges::size(awaitables);
720   if(count == 0) 720   if(count == 0)
721   throw std::invalid_argument("when_all requires at least one awaitable"); 721   throw std::invalid_argument("when_all requires at least one awaitable");
722   722  
723   OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables)); 723   OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
724   724  
725   detail::when_all_homogeneous_state<PayloadT> state(count); 725   detail::when_all_homogeneous_state<PayloadT> state(count);
726   726  
727   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 727   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
728   &owned_awaitables, &state); 728   &owned_awaitables, &state);
729   729  
730   if(state.core_.first_exception_) 730   if(state.core_.first_exception_)
731   std::rethrow_exception(state.core_.first_exception_); 731   std::rethrow_exception(state.core_.first_exception_);
732   732  
733   if(state.has_error_.load(std::memory_order_relaxed)) 733   if(state.has_error_.load(std::memory_order_relaxed))
734   co_return results<std::vector<PayloadT>>{state.first_error_, {}}; 734   co_return results<std::vector<PayloadT>>{state.first_error_, {}};
735   735  
736   std::vector<PayloadT> values; 736   std::vector<PayloadT> values;
737   values.reserve(count); 737   values.reserve(count);
738   for(auto& opt : state.results_) 738   for(auto& opt : state.results_)
739   values.push_back(std::move(*opt)); 739   values.push_back(std::move(*opt));
740   740  
741   co_return results<std::vector<PayloadT>>{std::error_code(), std::move(values)}; 741   co_return results<std::vector<PayloadT>>{std::error_code(), std::move(values)};
HITCBC 742   34 } 742   34 }
743   743  
744   /** Execute a range of void `results`-returning awaitables concurrently. 744   /** Execute a range of void `results`-returning awaitables concurrently.
745   745  
746   Starts all awaitables simultaneously and waits for all to complete. 746   Starts all awaitables simultaneously and waits for all to complete.
747   Since all awaitables return results<>, no payload values are 747   Since all awaitables return results<>, no payload values are
748   collected. The first error_code makes a stop request that every 748   collected. The first error_code makes a stop request that every
749   sibling observes, and is propagated. Exceptions always beat error 749   sibling observes, and is propagated. Exceptions always beat error
750   codes. 750   codes.
751   751  
752   @par Await-effects 752   @par Await-effects
753   753  
754   Takes ownership of the range, creates one wrapper coroutine per 754   Takes ownership of the range, creates one wrapper coroutine per
755   element, then posts every wrapper to the caller's executor. All 755   element, then posts every wrapper to the caller's executor. All
756   children therefore run concurrently, each awaited with the caller's 756   children therefore run concurrently, each awaited with the caller's
757   executor and frame allocator and with a stop token owned by this 757   executor and frame allocator and with a stop token owned by this
758   operation. 758   operation.
759   759  
760   Awaiting an empty range throws `std::invalid_argument` before any 760   Awaiting an empty range throws `std::invalid_argument` before any
761   child is started. 761   child is started.
762   762  
763   A stop request is made on the operation's own stop token when: 763   A stop request is made on the operation's own stop token when:
764   764  
765   @li a child await-returns a non-zero `ec`, or 765   @li a child await-returns a non-zero `ec`, or
766   @li a child exits via an exception, or 766   @li a child exits via an exception, or
767   @li the caller's stop token is triggered. 767   @li the caller's stop token is triggered.
768   768  
769   Every sibling observes that request through the stop token it was 769   Every sibling observes that request through the stop token it was
770   awaited with. The request does not end the operation: the await 770   awaited with. The request does not end the operation: the await
771   completes only after every child has finished. 771   completes only after every child has finished.
772   772  
773   @par Await-returns 773   @par Await-returns
774   An object of type `results<>` destructuring as `[ec]`. The children 774   An object of type `results<>` destructuring as `[ec]`. The children
775   have no payloads, so nothing else is reported. 775   have no payloads, so nothing else is reported.
776   776  
777   `ec` is the first non-zero `ec` await-returned by a child, in 777   `ec` is the first non-zero `ec` await-returned by a child, in
778   completion order rather than input order. The `ec` of every other 778   completion order rather than input order. The `ec` of every other
779   child is discarded. 779   child is discarded.
780   780  
781   If any child exits via an exception, the first such exception is 781   If any child exits via an exception, the first such exception is
782   rethrown instead of await-returning, even when a child also reported 782   rethrown instead of await-returning, even when a child also reported
783   an `ec`. 783   an `ec`.
784   784  
785   @par Await-postcondition 785   @par Await-postcondition
786   Every child has finished. `ec` is success only if every child 786   Every child has finished. `ec` is success only if every child
787   await-returned success. 787   await-returned success.
788   788  
789   @par Remarks 789   @par Remarks
790   Supports _ContextAwaitable cancellation_. 790   Supports _ContextAwaitable cancellation_.
791   791  
792   @par Thread Safety 792   @par Thread Safety
793   The returned task must be awaited from a single execution context. 793   The returned task must be awaited from a single execution context.
794   Child awaitables execute concurrently but complete through the caller's 794   Child awaitables execute concurrently but complete through the caller's
795   executor. 795   executor.
796   796  
797   @param awaitables Range of `results<>`-returning awaitables to 797   @param awaitables Range of `results<>`-returning awaitables to
798   execute concurrently (must not be empty). 798   execute concurrently (must not be empty).
799   799  
800   @return A task yielding `results<>` whose ec is the first child 800   @return A task yielding `results<>` whose ec is the first child
801   error, or default-constructed on success. 801   error, or default-constructed on success.
802   802  
803   @throws std::invalid_argument if range is empty. 803   @throws std::invalid_argument if range is empty.
804   804  
805   @par Exception Safety 805   @par Exception Safety
806   If a child throws, the first child exception is rethrown after 806   If a child throws, the first child exception is rethrown after
807   all children complete (exception beats error_code). 807   all children complete (exception beats error_code).
808   808  
809   @note If a branch's coroutine is abandoned before it finishes, for 809   @note If a branch's coroutine is abandoned before it finishes, for
810   example by teardown of its execution context, `when_all` 810   example by teardown of its execution context, `when_all`
811   requests stop on the remaining branches, and the awaiting 811   requests stop on the remaining branches, and the awaiting
812   parent is abandoned rather than resumed once the last branch 812   parent is abandoned rather than resumed once the last branch
813   has finished or been abandoned. 813   has finished or been abandoned.
814   814  
815   @par Example 815   @par Example
816   @par !example example_2 816   @par !example example_2
817   817  
818   818  
819   @see ContextAwaitableRange, when_all 819   @see ContextAwaitableRange, when_all
820   */ 820   */
821   template<ContextAwaitableRange R> 821   template<ContextAwaitableRange R>
822   requires detail::is_results_v< 822   requires detail::is_results_v<
823   awaitable_result_t<std::ranges::range_value_t<R>>> 823   awaitable_result_t<std::ranges::range_value_t<R>>>
824   && std::is_same_v< 824   && std::is_same_v<
825   detail::results_payload_t< 825   detail::results_payload_t<
826   awaitable_result_t<std::ranges::range_value_t<R>>>, 826   awaitable_result_t<std::ranges::range_value_t<R>>>,
827   std::tuple<>> 827   std::tuple<>>
HITCBC 828   6 [[nodiscard]] auto when_all(R&& awaitables) -> task<results<>> 828   6 [[nodiscard]] auto when_all(R&& awaitables) -> task<results<>>
829   { 829   {
830   using OwnedRange = detail::owned_range_t<R>; 830   using OwnedRange = detail::owned_range_t<R>;
831   831  
832   auto count = std::ranges::size(awaitables); 832   auto count = std::ranges::size(awaitables);
833   if(count == 0) 833   if(count == 0)
834   throw std::invalid_argument("when_all requires at least one awaitable"); 834   throw std::invalid_argument("when_all requires at least one awaitable");
835   835  
836   OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables)); 836   OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
837   837  
838   detail::when_all_homogeneous_state<std::tuple<>> state(count); 838   detail::when_all_homogeneous_state<std::tuple<>> state(count);
839   839  
840   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 840   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
841   &owned_awaitables, &state); 841   &owned_awaitables, &state);
842   842  
843   if(state.core_.first_exception_) 843   if(state.core_.first_exception_)
844   std::rethrow_exception(state.core_.first_exception_); 844   std::rethrow_exception(state.core_.first_exception_);
845   845  
846   if(state.has_error_.load(std::memory_order_relaxed)) 846   if(state.has_error_.load(std::memory_order_relaxed))
847   co_return results<>{state.first_error_}; 847   co_return results<>{state.first_error_};
848   848  
849   co_return results<>{}; 849   co_return results<>{};
HITCBC 850   12 } 850   12 }
851   851  
852   /** Execute `results`-returning awaitables concurrently, inspecting error codes. 852   /** Execute `results`-returning awaitables concurrently, inspecting error codes.
853   853  
854   Overload selected when all children return results<Ts...>. 854   Overload selected when all children return results<Ts...>.
855   The error_code is lifted out of each child into a single outer 855   The error_code is lifted out of each child into a single outer
856   results. On success all values are returned; on failure the 856   results. On success all values are returned; on failure the
857   first error_code wins. 857   first error_code wins.
858   858  
859   @par Await-effects 859   @par Await-effects
860   860  
861   Creates and posts one wrapper coroutine per argument to the caller's 861   Creates and posts one wrapper coroutine per argument to the caller's
862   executor, in argument order. All children therefore run concurrently, 862   executor, in argument order. All children therefore run concurrently,
863   each awaited with the caller's executor and frame allocator and with 863   each awaited with the caller's executor and frame allocator and with
864   a stop token owned by this operation. The overload requires at least 864   a stop token owned by this operation. The overload requires at least
865   one awaitable, so there is no empty case. 865   one awaitable, so there is no empty case.
866   866  
867   A stop request is made on the operation's own stop token when: 867   A stop request is made on the operation's own stop token when:
868   868  
869   @li a child await-returns a non-zero `ec`, or 869   @li a child await-returns a non-zero `ec`, or
870   @li a child exits via an exception, or 870   @li a child exits via an exception, or
871   @li the caller's stop token is triggered. 871   @li the caller's stop token is triggered.
872   872  
873   Every sibling observes that request through the stop token it was 873   Every sibling observes that request through the stop token it was
874   awaited with. The request does not end the operation: the await 874   awaited with. The request does not end the operation: the await
875   completes only after every child has finished. 875   completes only after every child has finished.
876   876  
877   @par Await-returns 877   @par Await-returns
878   An object of type `results<P1, ..., Pn>` destructuring as 878   An object of type `results<P1, ..., Pn>` destructuring as
879   `[ec, v1, ..., vn]`, where `Pi` is the payload of the i-th child's 879   `[ec, v1, ..., vn]`, where `Pi` is the payload of the i-th child's
880   `results`. 880   `results`.
881   881  
882   `ec` is the first non-zero `ec` await-returned by a child, in 882   `ec` is the first non-zero `ec` await-returned by a child, in
883   completion order rather than argument order. The `ec` of every other 883   completion order rather than argument order. The `ec` of every other
884   child is discarded. 884   child is discarded.
885   885  
886   Each `vi` is the payload the i-th child itself await-returned, even 886   Each `vi` is the payload the i-th child itself await-returned, even
887   when that child or a sibling reported an `ec`. A failed child 887   when that child or a sibling reported an `ec`. A failed child
888   therefore still contributes whatever payload it produced. This 888   therefore still contributes whatever payload it produced. This
889   differs from the range overloads, which discard all payloads once any 889   differs from the range overloads, which discard all payloads once any
890   child fails. 890   child fails.
891   891  
892   If any child exits via an exception, the first such exception is 892   If any child exits via an exception, the first such exception is
893   rethrown instead of await-returning, even when a child also reported 893   rethrown instead of await-returning, even when a child also reported
894   an `ec`. 894   an `ec`.
895   895  
896   @par Await-postcondition 896   @par Await-postcondition
897   Every child has finished. Each `vi` holds the i-th child's payload, 897   Every child has finished. Each `vi` holds the i-th child's payload,
898   and `ec` is success only if every child await-returned success. 898   and `ec` is success only if every child await-returned success.
899   899  
900   @par Remarks 900   @par Remarks
901   Supports _ContextAwaitable cancellation_. 901   Supports _ContextAwaitable cancellation_.
902   902  
903   @par Thread Safety 903   @par Thread Safety
904   The returned task must be awaited from a single execution context. 904   The returned task must be awaited from a single execution context.
905   Child awaitables execute concurrently but complete through the caller's 905   Child awaitables execute concurrently but complete through the caller's
906   executor. 906   executor.
907   907  
908   @par Exception Safety 908   @par Exception Safety
909   If a child throws, the first child exception is rethrown after 909   If a child throws, the first child exception is rethrown after
910   all children complete (exception beats error_code). 910   all children complete (exception beats error_code).
911   911  
912   @note If a branch's coroutine is abandoned before it finishes, for 912   @note If a branch's coroutine is abandoned before it finishes, for
913   example by teardown of its execution context, `when_all` 913   example by teardown of its execution context, `when_all`
914   requests stop on the remaining branches, and the awaiting 914   requests stop on the remaining branches, and the awaiting
915   parent is abandoned rather than resumed once the last branch 915   parent is abandoned rather than resumed once the last branch
916   has finished or been abandoned. 916   has finished or been abandoned.
917   917  
918   @param awaitables One or more awaitables each returning 918   @param awaitables One or more awaitables each returning
919   results<Ts...>. 919   results<Ts...>.
920   920  
921   @return A task yielding `results<R1, R2, ..., Rn>` where each Ri 921   @return A task yielding `results<R1, R2, ..., Rn>` where each Ri
922   follows the payload flattening rules. 922   follows the payload flattening rules.
923   */ 923   */
924   template<ContextAwaitable... As> 924   template<ContextAwaitable... As>
925   requires (sizeof...(As) > 0) 925   requires (sizeof...(As) > 0)
926   && detail::all_results_awaitables<As...> 926   && detail::all_results_awaitables<As...>
HITCBC 927   78 [[nodiscard]] auto when_all(As... awaitables) 927   78 [[nodiscard]] auto when_all(As... awaitables)
928   -> task<results< 928   -> task<results<
929   detail::results_payload_t<awaitable_result_t<As>>...>> 929   detail::results_payload_t<awaitable_result_t<As>>...>>
930   { 930   {
931   using result_type = results< 931   using result_type = results<
932   detail::results_payload_t<awaitable_result_t<As>>...>; 932   detail::results_payload_t<awaitable_result_t<As>>...>;
933   933  
934   detail::when_all_state<awaitable_result_t<As>...> state; 934   detail::when_all_state<awaitable_result_t<As>...> state;
935   std::tuple<As...> awaitable_tuple(std::move(awaitables)...); 935   std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
936   936  
937   co_await detail::when_all_results_launcher<As...>(&awaitable_tuple, &state); 937   co_await detail::when_all_results_launcher<As...>(&awaitable_tuple, &state);
938   938  
939   // Exception always wins over error_code 939   // Exception always wins over error_code
940   if(state.core_.first_exception_) 940   if(state.core_.first_exception_)
941   std::rethrow_exception(state.core_.first_exception_); 941   std::rethrow_exception(state.core_.first_exception_);
942   942  
943   auto r = detail::build_when_all_results<result_type>( 943   auto r = detail::build_when_all_results<result_type>(
944   detail::extract_results(state)); 944   detail::extract_results(state));
945   if(state.has_error_.load(std::memory_order_relaxed)) 945   if(state.has_error_.load(std::memory_order_relaxed))
946   std::get<0>(r) = state.first_error_; 946   std::get<0>(r) = state.first_error_;
947   co_return r; 947   co_return r;
HITCBC 948   156 } 948   156 }
949   949  
950   } // namespace capy 950   } // namespace capy
951   } // namespace boost 951   } // namespace boost
952   952  
953   #endif 953   #endif