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