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