100.00% Lines (191/191) 100.00% Functions (43/43)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
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_ANY_HPP 11   #ifndef BOOST_CAPY_WHEN_ANY_HPP
12   #define BOOST_CAPY_WHEN_ANY_HPP 12   #define BOOST_CAPY_WHEN_ANY_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/executor_ref.hpp> 20   #include <boost/capy/ex/executor_ref.hpp>
  21 + #include <boost/capy/detail/abandonable.hpp>
  22 + #include <boost/capy/detail/owned_range.hpp>
  23 + #include <boost/capy/ex/abandon.hpp>
21   #include <boost/capy/ex/frame_alloc_mixin.hpp> 24   #include <boost/capy/ex/frame_alloc_mixin.hpp>
22   #include <boost/capy/ex/frame_allocator.hpp> 25   #include <boost/capy/ex/frame_allocator.hpp>
23   #include <boost/capy/ex/io_env.hpp> 26   #include <boost/capy/ex/io_env.hpp>
24   #include <boost/capy/task.hpp> 27   #include <boost/capy/task.hpp>
25   28  
26   #include <array> 29   #include <array>
27   #include <atomic> 30   #include <atomic>
28   #include <exception> 31   #include <exception>
29   #include <memory> 32   #include <memory>
30   #include <mutex> 33   #include <mutex>
31   #include <optional> 34   #include <optional>
32   #include <ranges> 35   #include <ranges>
33   #include <stdexcept> 36   #include <stdexcept>
34   #include <stop_token> 37   #include <stop_token>
35   #include <tuple> 38   #include <tuple>
36   #include <type_traits> 39   #include <type_traits>
37   #include <utility> 40   #include <utility>
38   #include <variant> 41   #include <variant>
39   #include <vector> 42   #include <vector>
40   43  
41   /* 44   /*
42   when_any - Race multiple io_result tasks, select first success 45   when_any - Race multiple io_result tasks, select first success
43   ============================================================= 46   =============================================================
44   47  
45   OVERVIEW: 48   OVERVIEW:
46   --------- 49   ---------
47   when_any launches N io_result-returning tasks concurrently. A task 50   when_any launches N io_result-returning tasks concurrently. A task
48   wins by returning !ec; errors and exceptions do not win. Once a 51   wins by returning !ec; errors and exceptions do not win. Once a
49   winner is found, stop is requested for siblings and the winner's 52   winner is found, stop is requested for siblings and the winner's
50   payload is returned. If no winner exists (all fail), one of the 53   payload is returned. If no winner exists (all fail), one of the
51   failures is surfaced (an error_code at variant index 0, or a child's 54   failures is surfaced (an error_code at variant index 0, or a child's
52   exception rethrown); which one is unspecified. 55   exception rethrown); which one is unspecified.
53   56  
54   ARCHITECTURE: 57   ARCHITECTURE:
55   ------------- 58   -------------
56   The design mirrors when_all but with inverted completion semantics: 59   The design mirrors when_all but with inverted completion semantics:
57   60  
58   when_all: complete when remaining_count reaches 0 (all done) 61   when_all: complete when remaining_count reaches 0 (all done)
59   when_any: complete when has_winner becomes true (first done) 62   when_any: complete when has_winner becomes true (first done)
60   BUT still wait for remaining_count to reach 0 for cleanup 63   BUT still wait for remaining_count to reach 0 for cleanup
61   64  
62   Key components: 65   Key components:
63   - when_any_core: Shared state tracking winner and completion 66   - when_any_core: Shared state tracking winner and completion
64   - when_any_io_runner: Wrapper coroutine for each child task 67   - when_any_io_runner: Wrapper coroutine for each child task
65   - when_any_io_launcher/when_any_io_homogeneous_launcher: 68   - when_any_io_launcher/when_any_io_homogeneous_launcher:
66   Awaitables that start all runners concurrently 69   Awaitables that start all runners concurrently
67   70  
68   CRITICAL INVARIANTS: 71   CRITICAL INVARIANTS:
69   -------------------- 72   --------------------
70   1. Only a task returning !ec can become the winner (via atomic CAS) 73   1. Only a task returning !ec can become the winner (via atomic CAS)
71   2. All tasks must complete before parent resumes (cleanup safety) 74   2. All tasks must complete before parent resumes (cleanup safety)
72   3. Stop is requested immediately when winner is determined 75   3. Stop is requested immediately when winner is determined
73   4. Exceptions and errors do not claim winner status 76   4. Exceptions and errors do not claim winner status
74   77  
75   POSITIONAL VARIANT: 78   POSITIONAL VARIANT:
76   ------------------- 79   -------------------
77   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>. 80   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
78   Index 0 is error_code (failure/no-winner). Index 1..N identifies the 81   Index 0 is error_code (failure/no-winner). Index 1..N identifies the
79   winning child and carries its payload. 82   winning child and carries its payload.
80   83  
81   RANGE OVERLOAD: 84   RANGE OVERLOAD:
82   --------------- 85   ---------------
83   The range overload returns variant<error_code, pair<size_t, T>> for 86   The range overload returns variant<error_code, pair<size_t, T>> for
84   non-void children or variant<error_code, size_t> for void children. 87   non-void children or variant<error_code, size_t> for void children.
85   88  
86   MEMORY MODEL: 89   MEMORY MODEL:
87   ------------- 90   -------------
88   Synchronization chain from winner's write to parent's read: 91   Synchronization chain from winner's write to parent's read:
89   92  
90   1. Winner thread writes result_ (non-atomic) 93   1. Winner thread writes result_ (non-atomic)
91   2. Winner thread calls signal_completion() -> fetch_sub(acq_rel) on remaining_count_ 94   2. Winner thread calls signal_completion() -> fetch_sub(acq_rel) on remaining_count_
92   3. Last task thread (may be winner or non-winner) calls signal_completion() 95   3. Last task thread (may be winner or non-winner) calls signal_completion()
93   -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0 96   -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
94   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer 97   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
95   5. Parent coroutine resumes and reads result_ 98   5. Parent coroutine resumes and reads result_
96   99  
97   Synchronization analysis: 100   Synchronization analysis:
98   - All fetch_sub operations on remaining_count_ form a release sequence 101   - All fetch_sub operations on remaining_count_ form a release sequence
99   - Winner's fetch_sub releases; subsequent fetch_sub operations participate 102   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
100   in the modification order of remaining_count_ 103   in the modification order of remaining_count_
101   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the 104   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the
102   modification order, establishing happens-before from winner's writes 105   modification order, establishing happens-before from winner's writes
103   - Executor dispatch() is expected to provide queue-based synchronization 106   - Executor dispatch() is expected to provide queue-based synchronization
104   (release-on-post, acquire-on-execute) completing the chain to parent 107   (release-on-post, acquire-on-execute) completing the chain to parent
105   - Even inline executors work (same thread = sequenced-before) 108   - Even inline executors work (same thread = sequenced-before)
106   109  
107   EXCEPTION SEMANTICS: 110   EXCEPTION SEMANTICS:
108   -------------------- 111   --------------------
109   Exceptions do NOT claim winner status. If a child throws, the exception 112   Exceptions do NOT claim winner status. If a child throws, the exception
110   is recorded but the combinator keeps waiting for a success. Only when 113   is recorded but the combinator keeps waiting for a success. Only when
111   all children complete without a winner is a failure surfaced. There is 114   all children complete without a winner is a failure surfaced. There is
112   no priority between errors and exceptions, and no guarantee about which 115   no priority between errors and exceptions, and no guarantee about which
113   child's failure is reported: the result either returns an error_code at 116   child's failure is reported: the result either returns an error_code at
114   variant index 0 or rethrows a child's exception. 117   variant index 0 or rethrows a child's exception.
115   */ 118   */
116   119  
117   namespace boost { 120   namespace boost {
118   namespace capy { 121   namespace capy {
119   122  
120   namespace detail { 123   namespace detail {
121   124  
122   /** Core shared state for when_any operations. 125   /** Core shared state for when_any operations.
123   126  
124   Contains all members and methods common to both heterogeneous (variadic) 127   Contains all members and methods common to both heterogeneous (variadic)
125   and homogeneous (range) when_any implementations. State classes embed 128   and homogeneous (range) when_any implementations. State classes embed
126   this via composition to avoid CRTP destructor ordering issues. 129   this via composition to avoid CRTP destructor ordering issues.
127   130  
128   @par Thread Safety 131   @par Thread Safety
129   Atomic operations protect winner selection and completion count. 132   Atomic operations protect winner selection and completion count.
130   */ 133   */
131   struct when_any_core 134   struct when_any_core
132   { 135   {
133   std::atomic<std::size_t> remaining_count_; 136   std::atomic<std::size_t> remaining_count_;
134   std::size_t winner_index_{0}; 137   std::size_t winner_index_{0};
135   std::exception_ptr winner_exception_; 138   std::exception_ptr winner_exception_;
136   std::stop_source stop_source_; 139   std::stop_source stop_source_;
137   140  
138   // Bridges parent's stop token to our stop_source 141   // Bridges parent's stop token to our stop_source
139   struct stop_callback_fn 142   struct stop_callback_fn
140   { 143   {
141   std::stop_source* source_; 144   std::stop_source* source_;
HITCBC 142 - 3 void operator()() const noexcept { source_->request_stop(); } 145 + 4 void operator()() const noexcept
  146 + {
  147 + // Stopping can lose the last branch and destroy the join,
  148 + // with its source, before the request returns; the copy
  149 + // keeps the stop state alive until then.
HITGNC   150 + 4 std::stop_source keep = *source_;
HITGNC   151 + 4 keep.request_stop();
HITGNC   152 + 4 }
143   }; 153   };
144   using stop_callback_t = std::stop_callback<stop_callback_fn>; 154   using stop_callback_t = std::stop_callback<stop_callback_fn>;
145   std::optional<stop_callback_t> parent_stop_callback_; 155   std::optional<stop_callback_t> parent_stop_callback_;
146   156  
147   continuation continuation_; 157   continuation continuation_;
148   io_env const* caller_env_ = nullptr; 158   io_env const* caller_env_ = nullptr;
149   159  
  160 + // Set when a branch is abandoned before finishing: the parent is
  161 + // then abandoned rather than resumed.
  162 + std::atomic<bool> lost_{false};
  163 +
  164 + /// Drop a share held by a runner that never finished.
HITGNC   165 + 8 void lose_branch() noexcept
  166 + {
HITGNC   167 + 8 lost_.store(true, std::memory_order_relaxed);
HITGNC   168 + 8 stop_source_.request_stop();
HITGNC   169 + 16 if(remaining_count_.fetch_sub(1, std::memory_order_acq_rel) == 1)
  170 + {
HITGNC   171 + 4 auto parent = continuation_;
HITGNC   172 + 4 capy::abandon(parent);
  173 + }
HITGNC   174 + 8 }
  175 +
150   // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members) 176   // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
151   std::atomic<bool> has_winner_{false}; 177   std::atomic<bool> has_winner_{false};
152   178  
HITCBC 153   39 explicit when_any_core(std::size_t count) noexcept 179   53 explicit when_any_core(std::size_t count) noexcept
HITCBC 154   39 : remaining_count_(count) 180   53 : remaining_count_(count)
155   { 181   {
HITCBC 156   39 } 182   53 }
157   183  
158   /** Atomically claim winner status; exactly one task succeeds. */ 184   /** Atomically claim winner status; exactly one task succeeds. */
HITCBC 159   60 bool try_win(std::size_t index) noexcept 185   68 bool try_win(std::size_t index) noexcept
160   { 186   {
HITCBC 161   60 bool expected = false; 187   68 bool expected = false;
HITCBC 162   60 if(has_winner_.compare_exchange_strong( 188   68 if(has_winner_.compare_exchange_strong(
163   expected, true, std::memory_order_acq_rel)) 189   expected, true, std::memory_order_acq_rel))
164   { 190   {
HITCBC 165   28 winner_index_ = index; 191   32 winner_index_ = index;
HITCBC 166   28 stop_source_.request_stop(); 192   32 stop_source_.request_stop();
HITCBC 167   28 return true; 193   32 return true;
168   } 194   }
HITCBC 169   32 return false; 195   36 return false;
170   } 196   }
171   197  
172   /** @pre try_win() returned true. */ 198   /** @pre try_win() returned true. */
HITCBC 173   1 void set_winner_exception(std::exception_ptr ep) noexcept 199   1 void set_winner_exception(std::exception_ptr ep) noexcept
174   { 200   {
HITCBC 175   1 winner_exception_ = ep; 201   1 winner_exception_ = ep;
HITCBC 176   1 } 202   1 }
177   203  
178   // Runners signal completion directly via final_suspend; no member function needed. 204   // Runners signal completion directly via final_suspend; no member function needed.
179   }; 205   };
180   206  
181   } // namespace detail 207   } // namespace detail
182   208  
183   namespace detail { 209   namespace detail {
184   210  
185   // State for io_result-aware when_any: only !ec wins. 211   // State for io_result-aware when_any: only !ec wins.
186   template<typename... Ts> 212   template<typename... Ts>
187   struct when_any_io_state 213   struct when_any_io_state
188   { 214   {
189   static constexpr std::size_t task_count = sizeof...(Ts); 215   static constexpr std::size_t task_count = sizeof...(Ts);
190   using variant_type = std::variant<std::error_code, Ts...>; 216   using variant_type = std::variant<std::error_code, Ts...>;
191   217  
192   when_any_core core_; 218   when_any_core core_;
193   std::optional<variant_type> result_; 219   std::optional<variant_type> result_;
194   std::array<continuation, task_count> runner_handles_{}; 220   std::array<continuation, task_count> runner_handles_{};
195   221  
196   // A failure (error or exception) for the all-fail case. record_error 222   // A failure (error or exception) for the all-fail case. record_error
197   // and record_exception overwrite each other, so which one survives is 223   // and record_exception overwrite each other, so which one survives is
198   // unspecified (no priority between errors and exceptions). 224   // unspecified (no priority between errors and exceptions).
199   std::mutex failure_mu_; 225   std::mutex failure_mu_;
200   std::error_code last_error_; 226   std::error_code last_error_;
201   std::exception_ptr last_exception_; 227   std::exception_ptr last_exception_;
202   228  
HITCBC 203   23 when_any_io_state() 229   33 when_any_io_state()
HITCBC 204   23 : core_(task_count) 230   33 : core_(task_count)
205   { 231   {
HITCBC 206   23 } 232   33 }
207   233  
HITCBC 208   15 void record_error(std::error_code ec) 234   16 void record_error(std::error_code ec)
209   { 235   {
HITCBC 210   15 std::lock_guard lk(failure_mu_); 236   16 std::lock_guard lk(failure_mu_);
HITCBC 211   15 last_error_ = ec; 237   16 last_error_ = ec;
HITCBC 212   15 last_exception_ = nullptr; 238   16 last_exception_ = nullptr;
HITCBC 213   15 } 239   16 }
214   240  
HITCBC 215   7 void record_exception(std::exception_ptr ep) 241   7 void record_exception(std::exception_ptr ep)
216   { 242   {
HITCBC 217   7 std::lock_guard lk(failure_mu_); 243   7 std::lock_guard lk(failure_mu_);
HITCBC 218   7 last_exception_ = ep; 244   7 last_exception_ = ep;
HITCBC 219   7 last_error_ = {}; 245   7 last_error_ = {};
HITCBC 220   7 } 246   7 }
221   }; 247   };
222   248  
223   // Wrapper coroutine for io_result-aware when_any children. 249   // Wrapper coroutine for io_result-aware when_any children.
224   // unhandled_exception records the exception but does NOT claim winner status. 250   // unhandled_exception records the exception but does NOT claim winner status.
225   template<typename StateType> 251   template<typename StateType>
226 - struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner 252 + struct when_any_io_runner
227   { 253   {
228   struct promise_type 254   struct promise_type
229 - : frame_alloc_mixin 255 + : detail::process_frame_mixin
230   { 256   {
231   StateType* state_ = nullptr; 257   StateType* state_ = nullptr;
232   std::size_t index_ = 0; 258   std::size_t index_ = 0;
233   io_env env_; 259   io_env env_;
  260 + // Owns the branch's segment.
  261 + chain_owner segment_;
  262 + bool done_ = false;
  263 +
HITGNC   264 + 126 ~promise_type()
  265 + {
HITGNC   266 + 126 if(!done_ && state_)
HITGNC   267 + 8 state_->core_.lose_branch();
HITGNC   268 + 126 }
234   269  
HITCBC 235   95 when_any_io_runner get_return_object() noexcept 270   126 when_any_io_runner get_return_object() noexcept
236   { 271   {
237   return when_any_io_runner( 272   return when_any_io_runner(
HITCBC 238   95 std::coroutine_handle<promise_type>::from_promise(*this)); 273   126 std::coroutine_handle<promise_type>::from_promise(*this));
239   } 274   }
240   275  
HITCBC 241   95 std::suspend_always initial_suspend() noexcept { return {}; } 276   126 std::suspend_always initial_suspend() noexcept { return {}; }
242   277  
HITCBC 243   95 auto final_suspend() noexcept 278   118 auto final_suspend() noexcept
244   { 279   {
245   struct awaiter 280   struct awaiter
246   { 281   {
247   promise_type* p_; 282   promise_type* p_;
HITCBC 248   95 bool await_ready() const noexcept { return false; } 283   118 bool await_ready() const noexcept { return false; }
HITCBC 249   95 auto await_suspend(std::coroutine_handle<> h) noexcept 284   118 auto await_suspend(std::coroutine_handle<> h) noexcept
250   { 285   {
HITCBC 251   95 auto& core = p_->state_->core_; 286   118 auto& core = p_->state_->core_;
HITCBC 252   95 auto* counter = &core.remaining_count_; 287   118 auto* counter = &core.remaining_count_;
HITGNC   288 + 118 auto* lost = &core.lost_;
HITCBC 253   95 auto* caller_env = core.caller_env_; 289   118 auto* caller_env = core.caller_env_;
HITCBC 254   95 auto& cont = core.continuation_; 290   118 auto& cont = core.continuation_;
255   291  
HITGNC   292 + 118 p_->done_ = true;
  293 + // Resumed to unwind its abandoned branch: lost, not
  294 + // finished. Stop is requested while this branch
  295 + // still holds its share.
HITGNC   296 + 118 if(p_->segment_.abandoned())
  297 + {
HITGNC   298 + 14 lost->store(true, std::memory_order_relaxed);
HITGNC   299 + 14 core.stop_source_.request_stop();
  300 + }
HITCBC 256   95 h.destroy(); 301   118 h.destroy();
257   302  
HITCBC 258   95 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 303   118 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 259 - 95 if(remaining == 1) 304 + 118 if(remaining != 1)
HITCBC 260 - 39 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 305 + 69 return detail::symmetric_transfer(std::noop_coroutine());
HITCBC 261 - 56 return detail::symmetric_transfer(std::noop_coroutine()); 306 + 49 if(lost->load(std::memory_order_relaxed))
  307 + {
HITGNC   308 + 8 auto parent = cont;
HITGNC   309 + 8 capy::abandon(parent);
HITGNC   310 + 8 return detail::symmetric_transfer(std::noop_coroutine());
  311 + }
HITGNC   312 + 41 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
262   } 313   }
263   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 314   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
264   }; 315   };
HITCBC 265   95 return awaiter{this}; 316   118 return awaiter{this};
266   } 317   }
267   318  
HITCBC 268   82 void return_void() noexcept {} 319   105 void return_void() noexcept {}
269   320  
270   // Exceptions do NOT win in io_result when_any 321   // Exceptions do NOT win in io_result when_any
HITCBC 271   13 void unhandled_exception() noexcept 322   13 void unhandled_exception() noexcept
272   { 323   {
HITCBC 273   13 state_->record_exception(std::current_exception()); 324   13 state_->record_exception(std::current_exception());
HITCBC 274   13 } 325   13 }
275   326  
276   template<class Awaitable> 327   template<class Awaitable>
277   struct transform_awaiter 328   struct transform_awaiter
278   { 329   {
279   std::decay_t<Awaitable> a_; 330   std::decay_t<Awaitable> a_;
280   promise_type* p_; 331   promise_type* p_;
281   332  
HITCBC 282   95 bool await_ready() { return a_.await_ready(); } 333   120 bool await_ready() { return a_.await_ready(); }
HITCBC 283   95 decltype(auto) await_resume() { return a_.await_resume(); } 334   118 decltype(auto) await_resume() { return a_.await_resume(); }
284   335  
285   template<class Promise> 336   template<class Promise>
HITCBC 286   94 auto await_suspend(std::coroutine_handle<Promise> h) 337   119 auto await_suspend(std::coroutine_handle<Promise> h)
287   { 338   {
288   using R = decltype(a_.await_suspend(h, &p_->env_)); 339   using R = decltype(a_.await_suspend(h, &p_->env_));
289   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 340   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 290   94 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_)); 341   119 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
291   else 342   else
292   return a_.await_suspend(h, &p_->env_); 343   return a_.await_suspend(h, &p_->env_);
293   } 344   }
294   }; 345   };
295   346  
296   template<class Awaitable> 347   template<class Awaitable>
HITCBC 297   95 auto await_transform(Awaitable&& a) 348   120 auto await_transform(Awaitable&& a)
298   { 349   {
299   using A = std::decay_t<Awaitable>; 350   using A = std::decay_t<Awaitable>;
300   if constexpr (IoAwaitable<A>) 351   if constexpr (IoAwaitable<A>)
301   { 352   {
302   return transform_awaiter<Awaitable>{ 353   return transform_awaiter<Awaitable>{
HITCBC 303   188 std::forward<Awaitable>(a), this}; 354   120 std::forward<Awaitable>(a), this};
304   } 355   }
305   else 356   else
306   { 357   {
307   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 358   static_assert(sizeof(A) == 0, "requires IoAwaitable");
308   } 359   }
ECB 309   93 } 360   }
310   }; 361   };
311   362  
312   std::coroutine_handle<promise_type> h_; 363   std::coroutine_handle<promise_type> h_;
313   364  
HITCBC 314   95 explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept 365   126 explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 315   95 : h_(h) 366   126 : h_(h)
316   { 367   {
HITCBC 317   95 } 368   126 }
318   369  
319   when_any_io_runner(when_any_io_runner&& other) noexcept 370   when_any_io_runner(when_any_io_runner&& other) noexcept
320   : h_(std::exchange(other.h_, nullptr)) 371   : h_(std::exchange(other.h_, nullptr))
321   { 372   {
322   } 373   }
323   374  
324   when_any_io_runner(when_any_io_runner const&) = delete; 375   when_any_io_runner(when_any_io_runner const&) = delete;
325   when_any_io_runner& operator=(when_any_io_runner const&) = delete; 376   when_any_io_runner& operator=(when_any_io_runner const&) = delete;
326   when_any_io_runner& operator=(when_any_io_runner&&) = delete; 377   when_any_io_runner& operator=(when_any_io_runner&&) = delete;
327   378  
HITCBC 328   95 auto release() noexcept 379   126 auto release() noexcept
329   { 380   {
HITCBC 330   95 return std::exchange(h_, nullptr); 381   126 return std::exchange(h_, nullptr);
331   } 382   }
332   }; 383   };
333   384  
334   // Runner coroutine: only tries to win when the child returns !ec. 385   // Runner coroutine: only tries to win when the child returns !ec.
335   template<std::size_t I, IoAwaitable Awaitable, typename StateType> 386   template<std::size_t I, IoAwaitable Awaitable, typename StateType>
336   when_any_io_runner<StateType> 387   when_any_io_runner<StateType>
HITCBC 337 - 41 make_when_any_io_runner(Awaitable inner, StateType* state) 388 + 61 make_when_any_io_runner(Awaitable* inner, StateType* state)
338   { 389   {
339 - auto result = co_await std::move(inner); 390 + // Taken from the join's storage only once running, into a local:
  391 + // a runner destroyed unstarted owns no branch, and one destroyed
  392 + // unfinished destroys it before its promise reports the loss.
  393 + Awaitable a(std::move(*inner));
  394 + auto r = co_await abandonable<Awaitable>{a};
  395 + if(!r)
  396 + co_return;
  397 + auto& result = *r;
340   398  
341   if(!std::get<0>(result)) 399   if(!std::get<0>(result))
342   { 400   {
343   // Success: try to claim winner 401   // Success: try to claim winner
344   if(state->core_.try_win(I)) 402   if(state->core_.try_win(I))
345   { 403   {
346   try 404   try
347   { 405   {
348   state->result_.emplace( 406   state->result_.emplace(
349   std::in_place_index<I + 1>, 407   std::in_place_index<I + 1>,
350   detail::extract_io_payload(std::move(result))); 408   detail::extract_io_payload(std::move(result)));
351   } 409   }
352   catch(...) 410   catch(...)
353   { 411   {
354   state->core_.set_winner_exception(std::current_exception()); 412   state->core_.set_winner_exception(std::current_exception());
355   } 413   }
356   } 414   }
357   } 415   }
358   else 416   else
359   { 417   {
360   // Error: record but don't win 418   // Error: record but don't win
361   state->record_error(std::get<0>(result)); 419   state->record_error(std::get<0>(result));
362   } 420   }
HITCBC 363   82 } 421   122 }
364   422  
365   // Launcher for io_result-aware when_any. 423   // Launcher for io_result-aware when_any.
366   template<IoAwaitable... Awaitables> 424   template<IoAwaitable... Awaitables>
367   class when_any_io_launcher 425   class when_any_io_launcher
368   { 426   {
369   using state_type = when_any_io_state< 427   using state_type = when_any_io_state<
370   io_result_payload_t<awaitable_result_t<Awaitables>>...>; 428   io_result_payload_t<awaitable_result_t<Awaitables>>...>;
371   429  
372   std::tuple<Awaitables...>* tasks_; 430   std::tuple<Awaitables...>* tasks_;
373   state_type* state_; 431   state_type* state_;
374   432  
375   public: 433   public:
HITCBC 376   23 when_any_io_launcher( 434   33 when_any_io_launcher(
377   std::tuple<Awaitables...>* tasks, 435   std::tuple<Awaitables...>* tasks,
378   state_type* state) 436   state_type* state)
HITCBC 379   23 : tasks_(tasks) 437   33 : tasks_(tasks)
HITCBC 380   23 , state_(state) 438   33 , state_(state)
381   { 439   {
HITCBC 382   23 } 440   33 }
383   441  
HITCBC 384   23 bool await_ready() const noexcept 442   33 bool await_ready() const noexcept
385   { 443   {
HITCBC 386   23 return sizeof...(Awaitables) == 0; 444   33 return sizeof...(Awaitables) == 0;
387   } 445   }
388   446  
HITCBC 389   23 std::coroutine_handle<> await_suspend( 447   33 std::coroutine_handle<> await_suspend(
390   std::coroutine_handle<> continuation, io_env const* caller_env) 448   std::coroutine_handle<> continuation, io_env const* caller_env)
391   { 449   {
HITCBC 392   23 state_->core_.continuation_.h = continuation; 450   33 state_->core_.continuation_.h = continuation;
HITGNC   451 + 33 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 393   23 state_->core_.caller_env_ = caller_env; 452   33 state_->core_.caller_env_ = caller_env;
394   453  
HITCBC 395   23 if(caller_env->stop_token.stop_possible()) 454   33 if(caller_env->stop_token.stop_possible())
396   { 455   {
HITCBC 397   4 state_->core_.parent_stop_callback_.emplace( 456   6 state_->core_.parent_stop_callback_.emplace(
HITCBC 398   2 caller_env->stop_token, 457   3 caller_env->stop_token,
HITCBC 399   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 458   3 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
400   459  
HITCBC 401   2 if(caller_env->stop_token.stop_requested()) 460   3 if(caller_env->stop_token.stop_requested())
HITCBC 402   1 state_->core_.stop_source_.request_stop(); 461   1 state_->core_.stop_source_.request_stop();
403   } 462   }
404   463  
HITCBC 405   23 auto token = state_->core_.stop_source_.get_token(); 464   33 auto token = state_->core_.stop_source_.get_token();
HITCBC 406   23 launch_all(std::index_sequence_for<Awaitables...>{}, 465   33 launch_all(std::index_sequence_for<Awaitables...>{},
407   caller_env->executor, token); 466   caller_env->executor, token);
408   467  
HITCBC 409   46 return std::noop_coroutine(); 468   66 return std::noop_coroutine();
HITCBC 410   23 } 469   33 }
411   470  
HITCBC 412   23 void await_resume() const noexcept {} 471   23 void await_resume() const noexcept {}
413   472  
414   private: 473   private:
415   template<std::size_t... Is> 474   template<std::size_t... Is>
HITCBC 416   23 void launch_all(std::index_sequence<Is...>, 475   33 void launch_all(std::index_sequence<Is...>,
417   executor_ref ex, std::stop_token token) 476   executor_ref ex, std::stop_token token)
418   { 477   {
HITCBC 419   23 (..., launch_one<Is>(ex, token)); 478   33 (..., launch_one<Is>(ex, token));
HITCBC 420   23 } 479   33 }
421   480  
422   template<std::size_t I> 481   template<std::size_t I>
HITCBC 423   41 void launch_one(executor_ref caller_ex, std::stop_token token) 482   61 void launch_one(executor_ref caller_ex, std::stop_token token)
424   { 483   {
HITCBC 425   41 auto runner = make_when_any_io_runner<I>( 484   61 auto runner = make_when_any_io_runner<I>(
HITCBC 426 - 41 std::move(std::get<I>(*tasks_)), state_); 485 + 61 &std::get<I>(*tasks_), state_);
427   486  
HITCBC 428   41 auto h = runner.release(); 487   61 auto h = runner.release();
HITCBC 429   41 h.promise().state_ = state_; 488   61 h.promise().state_ = state_;
HITCBC 430   41 h.promise().index_ = I; 489   61 h.promise().index_ = I;
HITGNC   490 + 61 h.promise().segment_.bind(h, caller_ex);
HITCBC 431   41 h.promise().env_ = io_env{caller_ex, token, 491   61 h.promise().env_ = io_env{caller_ex, token,
HITCBC 432 - 41 state_->core_.caller_env_->frame_allocator}; 492 + 61 state_->core_.caller_env_->frame_allocator,
HITGNC   493 + 122 &h.promise().segment_};
433   494  
HITCBC 434   41 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 495   61 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 435   41 caller_ex.post(state_->runner_handles_[I]); 496   61 caller_ex.post(state_->runner_handles_[I]);
HITCBC 436   82 } 497   122 }
437   }; 498   };
438   499  
439   /** Shared state for homogeneous io_result-aware when_any (range overload). 500   /** Shared state for homogeneous io_result-aware when_any (range overload).
440   501  
441   @tparam T The payload type extracted from io_result. 502   @tparam T The payload type extracted from io_result.
442   */ 503   */
443   template<typename T> 504   template<typename T>
444   struct when_any_io_homogeneous_state 505   struct when_any_io_homogeneous_state
445   { 506   {
446   when_any_core core_; 507   when_any_core core_;
447   std::optional<T> result_; 508   std::optional<T> result_;
448   std::unique_ptr<continuation[]> runner_handles_; 509   std::unique_ptr<continuation[]> runner_handles_;
449   510  
450   std::mutex failure_mu_; 511   std::mutex failure_mu_;
451   std::error_code last_error_; 512   std::error_code last_error_;
452   std::exception_ptr last_exception_; 513   std::exception_ptr last_exception_;
453   514  
HITCBC 454   13 explicit when_any_io_homogeneous_state(std::size_t count) 515   15 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 455   13 : core_(count) 516   15 : core_(count)
HITCBC 456   13 , runner_handles_(std::make_unique<continuation[]>(count)) 517   15 , runner_handles_(std::make_unique<continuation[]>(count))
457   { 518   {
HITCBC 458   13 } 519   15 }
459   520  
HITCBC 460   6 void record_error(std::error_code ec) 521   6 void record_error(std::error_code ec)
461   { 522   {
HITCBC 462   6 std::lock_guard lk(failure_mu_); 523   6 std::lock_guard lk(failure_mu_);
HITCBC 463   6 last_error_ = ec; 524   6 last_error_ = ec;
HITCBC 464   6 last_exception_ = nullptr; 525   6 last_exception_ = nullptr;
HITCBC 465   6 } 526   6 }
466   527  
HITCBC 467   4 void record_exception(std::exception_ptr ep) 528   4 void record_exception(std::exception_ptr ep)
468   { 529   {
HITCBC 469   4 std::lock_guard lk(failure_mu_); 530   4 std::lock_guard lk(failure_mu_);
HITCBC 470   4 last_exception_ = ep; 531   4 last_exception_ = ep;
HITCBC 471   4 last_error_ = {}; 532   4 last_error_ = {};
HITCBC 472   4 } 533   4 }
473   }; 534   };
474   535  
475   /** Specialization for void io_result children (no payload storage). */ 536   /** Specialization for void io_result children (no payload storage). */
476   template<> 537   template<>
477   struct when_any_io_homogeneous_state<std::tuple<>> 538   struct when_any_io_homogeneous_state<std::tuple<>>
478   { 539   {
479   when_any_core core_; 540   when_any_core core_;
480   std::unique_ptr<continuation[]> runner_handles_; 541   std::unique_ptr<continuation[]> runner_handles_;
481   542  
482   std::mutex failure_mu_; 543   std::mutex failure_mu_;
483   std::error_code last_error_; 544   std::error_code last_error_;
484   std::exception_ptr last_exception_; 545   std::exception_ptr last_exception_;
485   546  
HITCBC 486   3 explicit when_any_io_homogeneous_state(std::size_t count) 547   5 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 487   3 : core_(count) 548   5 : core_(count)
HITCBC 488   3 , runner_handles_(std::make_unique<continuation[]>(count)) 549   5 , runner_handles_(std::make_unique<continuation[]>(count))
489   { 550   {
HITCBC 490   3 } 551   5 }
491   552  
HITCBC 492   1 void record_error(std::error_code ec) 553   1 void record_error(std::error_code ec)
493   { 554   {
HITCBC 494   1 std::lock_guard lk(failure_mu_); 555   1 std::lock_guard lk(failure_mu_);
HITCBC 495   1 last_error_ = ec; 556   1 last_error_ = ec;
HITCBC 496   1 last_exception_ = nullptr; 557   1 last_exception_ = nullptr;
HITCBC 497   1 } 558   1 }
498   559  
HITCBC 499   2 void record_exception(std::exception_ptr ep) 560   2 void record_exception(std::exception_ptr ep)
500   { 561   {
HITCBC 501   2 std::lock_guard lk(failure_mu_); 562   2 std::lock_guard lk(failure_mu_);
HITCBC 502   2 last_exception_ = ep; 563   2 last_exception_ = ep;
HITCBC 503   2 last_error_ = {}; 564   2 last_error_ = {};
HITCBC 504   2 } 565   2 }
505   }; 566   };
506   567  
507   /** Create an io_result-aware runner for homogeneous when_any (range path). 568   /** Create an io_result-aware runner for homogeneous when_any (range path).
508   569  
509   Only tries to win when the child returns !ec. 570   Only tries to win when the child returns !ec.
510   */ 571   */
511   template<IoAwaitable Awaitable, typename StateType> 572   template<IoAwaitable Awaitable, typename StateType>
512   when_any_io_runner<StateType> 573   when_any_io_runner<StateType>
HITCBC 513   54 make_when_any_io_homogeneous_runner( 574   65 make_when_any_io_homogeneous_runner(
514 - Awaitable inner, StateType* state, std::size_t index) 575 + Awaitable* inner, StateType* state, std::size_t index)
515   { 576   {
516 - auto result = co_await std::move(inner); 577 + // Taken from the join's storage only once running, into a local:
  578 + // a runner destroyed unstarted owns no branch, and one destroyed
  579 + // unfinished destroys it before its promise reports the loss.
  580 + Awaitable a(std::move(*inner));
  581 + auto r = co_await abandonable<Awaitable>{a};
  582 + if(!r)
  583 + co_return;
  584 + auto& result = *r;
517   585  
518   if(!std::get<0>(result)) 586   if(!std::get<0>(result))
519   { 587   {
520   if(state->core_.try_win(index)) 588   if(state->core_.try_win(index))
521   { 589   {
522   using PayloadT = io_result_payload_t< 590   using PayloadT = io_result_payload_t<
523   awaitable_result_t<Awaitable>>; 591   awaitable_result_t<Awaitable>>;
524   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 592   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
525   { 593   {
526   try 594   try
527   { 595   {
528   state->result_.emplace( 596   state->result_.emplace(
529   extract_io_payload(std::move(result))); 597   extract_io_payload(std::move(result)));
530   } 598   }
531   catch(...) 599   catch(...)
532   { 600   {
533   state->core_.set_winner_exception( 601   state->core_.set_winner_exception(
534   std::current_exception()); 602   std::current_exception());
535   } 603   }
536   } 604   }
537   } 605   }
538   } 606   }
539   else 607   else
540   { 608   {
541   state->record_error(std::get<0>(result)); 609   state->record_error(std::get<0>(result));
542   } 610   }
HITCBC 543   108 } 611   130 }
544   612  
545   /** Starts all io_result-aware homogeneous runners concurrently. */ 613   /** Starts all io_result-aware homogeneous runners concurrently. */
546   template<IoAwaitableRange Range> 614   template<IoAwaitableRange Range>
547   class when_any_io_homogeneous_launcher 615   class when_any_io_homogeneous_launcher
548   { 616   {
549   using Awaitable = std::ranges::range_value_t<Range>; 617   using Awaitable = std::ranges::range_value_t<Range>;
550   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>; 618   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
551   619  
552   Range* range_; 620   Range* range_;
553   when_any_io_homogeneous_state<PayloadT>* state_; 621   when_any_io_homogeneous_state<PayloadT>* state_;
554   622  
555   public: 623   public:
HITCBC 556   16 when_any_io_homogeneous_launcher( 624   20 when_any_io_homogeneous_launcher(
557   Range* range, 625   Range* range,
558   when_any_io_homogeneous_state<PayloadT>* state) 626   when_any_io_homogeneous_state<PayloadT>* state)
HITCBC 559   16 : range_(range) 627   20 : range_(range)
HITCBC 560   16 , state_(state) 628   20 , state_(state)
561   { 629   {
HITCBC 562   16 } 630   20 }
563   631  
HITCBC 564   16 bool await_ready() const noexcept 632   20 bool await_ready() const noexcept
565   { 633   {
HITCBC 566   16 return std::ranges::empty(*range_); 634   20 return std::ranges::empty(*range_);
567   } 635   }
568   636  
HITCBC 569   16 std::coroutine_handle<> await_suspend( 637   20 std::coroutine_handle<> await_suspend(
570   std::coroutine_handle<> continuation, io_env const* caller_env) 638   std::coroutine_handle<> continuation, io_env const* caller_env)
571   { 639   {
HITCBC 572   16 state_->core_.continuation_.h = continuation; 640   20 state_->core_.continuation_.h = continuation;
HITGNC   641 + 20 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 573   16 state_->core_.caller_env_ = caller_env; 642   20 state_->core_.caller_env_ = caller_env;
574   643  
HITCBC 575   16 if(caller_env->stop_token.stop_possible()) 644   20 if(caller_env->stop_token.stop_possible())
576   { 645   {
HITCBC 577   4 state_->core_.parent_stop_callback_.emplace( 646   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 578   2 caller_env->stop_token, 647   2 caller_env->stop_token,
HITCBC 579   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 648   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
580   649  
HITCBC 581   2 if(caller_env->stop_token.stop_requested()) 650   2 if(caller_env->stop_token.stop_requested())
HITCBC 582   1 state_->core_.stop_source_.request_stop(); 651   1 state_->core_.stop_source_.request_stop();
583   } 652   }
584   653  
HITCBC 585   16 auto token = state_->core_.stop_source_.get_token(); 654   20 auto token = state_->core_.stop_source_.get_token();
586   655  
587   // Phase 1: Create all runners without dispatching. 656   // Phase 1: Create all runners without dispatching.
HITCBC 588   16 std::size_t index = 0; 657   20 std::size_t index = 0;
HITCBC 589   70 for(auto&& a : *range_) 658   85 for(auto&& a : *range_)
590   { 659   {
HITCBC 591   54 auto runner = make_when_any_io_homogeneous_runner( 660   65 auto runner = make_when_any_io_homogeneous_runner(
ECB 592 - 54 std::move(a), state_, index); 661 + &a, state_, index);
593   662  
HITCBC 594   54 auto h = runner.release(); 663   65 auto h = runner.release();
HITCBC 595   54 h.promise().state_ = state_; 664   65 h.promise().state_ = state_;
HITCBC 596   54 h.promise().index_ = index; 665   65 h.promise().index_ = index;
HITGNC   666 + 65 h.promise().segment_.bind(h, caller_env->executor);
HITCBC 597   54 h.promise().env_ = io_env{caller_env->executor, token, 667   65 h.promise().env_ = io_env{caller_env->executor, token,
HITCBC 598 - 54 caller_env->frame_allocator}; 668 + 130 caller_env->frame_allocator, &h.promise().segment_};
599   669  
HITCBC 600   54 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 670   65 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 601   54 ++index; 671   65 ++index;
602   } 672   }
603   673  
604   // Phase 2: Post all runners. Any may complete synchronously. 674   // Phase 2: Post all runners. Any may complete synchronously.
HITCBC 605   16 auto* handles = state_->runner_handles_.get(); 675   20 auto* handles = state_->runner_handles_.get();
HITCBC 606   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 676   20 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
HITCBC 607   70 for(std::size_t i = 0; i < count; ++i) 677   85 for(std::size_t i = 0; i < count; ++i)
HITCBC 608   54 caller_env->executor.post(handles[i]); 678   65 caller_env->executor.post(handles[i]);
609   679  
HITCBC 610   32 return std::noop_coroutine(); 680   40 return std::noop_coroutine();
HITCBC 611   70 } 681   85 }
612   682  
HITCBC 613   16 void await_resume() const noexcept {} 683   18 void await_resume() const noexcept {}
614   }; 684   };
615   685  
616   } // namespace detail 686   } // namespace detail
617   687  
618   /** Race a range of io_result-returning awaitables (non-void payloads). 688   /** Race a range of io_result-returning awaitables (non-void payloads).
619   689  
620   Only a child returning !ec can win. Errors and exceptions do not 690   Only a child returning !ec can win. Errors and exceptions do not
621   claim winner status. If all children fail, an unspecified one of 691   claim winner status. If all children fail, an unspecified one of
622   the failures is reported — either an error_code at variant index 0, 692   the failures is reported — either an error_code at variant index 0,
623   or a child's exception rethrown. 693   or a child's exception rethrown.
624   694  
625   @par Await-effects 695   @par Await-effects
626   696  
627   Takes ownership of the range, creates one wrapper coroutine per 697   Takes ownership of the range, creates one wrapper coroutine per
628   element, then posts every wrapper to the caller's executor. All 698   element, then posts every wrapper to the caller's executor. All
629   children therefore run concurrently, each awaited with the caller's 699   children therefore run concurrently, each awaited with the caller's
630   executor and frame allocator and with a stop token owned by this 700   executor and frame allocator and with a stop token owned by this
631   operation. 701   operation.
632   702  
633   Awaiting an empty range throws `std::invalid_argument` before any 703   Awaiting an empty range throws `std::invalid_argument` before any
634   child is started. 704   child is started.
635   705  
636   The first child to await-return a zero `ec` claims the win. Claiming 706   The first child to await-return a zero `ec` claims the win. Claiming
637   the win requests stop on the operation's own stop token, which every 707   the win requests stop on the operation's own stop token, which every
638   sibling observes through the stop token it was awaited with. A child 708   sibling observes through the stop token it was awaited with. A child
639   that await-returns a non-zero `ec`, or that exits via an exception, 709   that await-returns a non-zero `ec`, or that exits via an exception,
640   does not claim the win and does not request stop. The operation keeps 710   does not claim the win and does not request stop. The operation keeps
641   waiting for a success. A stop request on the caller's stop token is 711   waiting for a success. A stop request on the caller's stop token is
642   also forwarded to every child. 712   also forwarded to every child.
643   713  
644   The await completes only after every child has finished, regardless of 714   The await completes only after every child has finished, regardless of
645   whether a win was claimed. 715   whether a win was claimed.
646   716  
647   @par Await-returns 717   @par Await-returns
648   An object of type 718   An object of type
649   `std::variant<std::error_code, std::pair<std::size_t, PayloadT>>`, 719   `std::variant<std::error_code, std::pair<std::size_t, PayloadT>>`,
650   where `PayloadT` is the payload of one child's `io_result`. 720   where `PayloadT` is the payload of one child's `io_result`.
651   721  
652   @li Index 1 holds the winner's position in the input range paired 722   @li Index 1 holds the winner's position in the input range paired
653   with its payload. 723   with its payload.
654   @li Index 0 holds a non-zero `error_code` when no child won, that is, 724   @li Index 0 holds a non-zero `error_code` when no child won, that is,
655   when every child failed. It is the `ec` of one of the failed 725   when every child failed. It is the `ec` of one of the failed
656   children; which one is unspecified. 726   children; which one is unspecified.
657   727  
658   A child that succeeds after the win has already been claimed 728   A child that succeeds after the win has already been claimed
659   contributes nothing: its payload is discarded. 729   contributes nothing: its payload is discarded.
660   730  
661   If no child won and the failure selected for reporting is an 731   If no child won and the failure selected for reporting is an
662   exception rather than an `ec`, that exception is rethrown instead of 732   exception rather than an `ec`, that exception is rethrown instead of
663   await-returning. The choice of child is unspecified. 733   await-returning. The choice of child is unspecified.
664   734  
665   @par Await-postcondition 735   @par Await-postcondition
666   Every child has finished. If at least one child await-returned a zero 736   Every child has finished. If at least one child await-returned a zero
667   `ec`, the result holds index 1. If producing the winner's payload 737   `ec`, the result holds index 1. If producing the winner's payload
668   threw, that exception is rethrown. Otherwise the result holds index 0, 738   threw, that exception is rethrown. Otherwise the result holds index 0,
669   or a failed child's exception is rethrown. 739   or a failed child's exception is rethrown.
670   740  
671   @par Remarks 741   @par Remarks
672   Supports _IoAwaitable cancellation_. A canceled child await-returns a 742   Supports _IoAwaitable cancellation_. A canceled child await-returns a
673   non-zero `ec` and so cannot win; if no child has already succeeded, 743   non-zero `ec` and so cannot win; if no child has already succeeded,
674   the result settles at index 0. 744   the result settles at index 0.
675   745  
676   @par Thread Safety 746   @par Thread Safety
677   The returned task must be awaited from a single execution context. 747   The returned task must be awaited from a single execution context.
678   Child awaitables execute concurrently but complete through the caller's 748   Child awaitables execute concurrently but complete through the caller's
679   executor. 749   executor.
680   750  
681   @param awaitables Range of io_result-returning awaitables (must 751   @param awaitables Range of io_result-returning awaitables (must
682   not be empty). 752   not be empty).
683   753  
684   @return A task yielding variant<error_code, pair<size_t, PayloadT>> 754   @return A task yielding variant<error_code, pair<size_t, PayloadT>>
685   where index 0 is failure and index 1 carries the winner's 755   where index 0 is failure and index 1 carries the winner's
686   index and payload. 756   index and payload.
687   757  
688   @throws std::invalid_argument if range is empty. 758   @throws std::invalid_argument if range is empty.
689   759  
690   @par Exception Safety 760   @par Exception Safety
691   The winner's exception is rethrown if extracting or 761   The winner's exception is rethrown if extracting or
692   move-constructing the winning payload throws. In that case a winner 762   move-constructing the winning payload throws. In that case a winner
693   was found, but its result could not be produced. If all children 763   was found, but its result could not be produced. If all children
694   fail and the reported failure is an exception, that child's 764   fail and the reported failure is an exception, that child's
695   exception is rethrown (which child is unspecified). 765   exception is rethrown (which child is unspecified).
696   766  
  767 + @note If a branch's coroutine is abandoned before it finishes, for
  768 + example by teardown of its execution context, `when_any`
  769 + requests stop on the remaining branches, and the awaiting
  770 + parent is abandoned rather than resumed once the last branch
  771 + has finished or been abandoned.
  772 +
697   @par Example 773   @par Example
698   @par !example example_1 774   @par !example example_1
699   775  
700   776  
701   @see IoAwaitableRange, when_any 777   @see IoAwaitableRange, when_any
702   */ 778   */
703   template<IoAwaitableRange R> 779   template<IoAwaitableRange R>
704   requires detail::is_io_result_v< 780   requires detail::is_io_result_v<
705   awaitable_result_t<std::ranges::range_value_t<R>>> 781   awaitable_result_t<std::ranges::range_value_t<R>>>
706   && (!std::is_same_v< 782   && (!std::is_same_v<
707   detail::io_result_payload_t< 783   detail::io_result_payload_t<
708   awaitable_result_t<std::ranges::range_value_t<R>>>, 784   awaitable_result_t<std::ranges::range_value_t<R>>>,
709   std::tuple<>>) 785   std::tuple<>>)
HITCBC 710   14 [[nodiscard]] auto when_any(R&& awaitables) 786   16 [[nodiscard]] auto when_any(R&& awaitables)
711   -> task<std::variant<std::error_code, 787   -> task<std::variant<std::error_code,
712   std::pair<std::size_t, 788   std::pair<std::size_t,
713   detail::io_result_payload_t< 789   detail::io_result_payload_t<
714   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 790   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
715   { 791   {
716   using Awaitable = std::ranges::range_value_t<R>; 792   using Awaitable = std::ranges::range_value_t<R>;
717   using PayloadT = detail::io_result_payload_t< 793   using PayloadT = detail::io_result_payload_t<
718   awaitable_result_t<Awaitable>>; 794   awaitable_result_t<Awaitable>>;
719   using result_type = std::variant<std::error_code, 795   using result_type = std::variant<std::error_code,
720   std::pair<std::size_t, PayloadT>>; 796   std::pair<std::size_t, PayloadT>>;
721 - using OwnedRange = std::remove_cvref_t<R>; 797 + using OwnedRange = detail::owned_range_t<R>;
722   798  
723   auto count = std::ranges::size(awaitables); 799   auto count = std::ranges::size(awaitables);
724   if(count == 0) 800   if(count == 0)
725   throw std::invalid_argument("when_any requires at least one awaitable"); 801   throw std::invalid_argument("when_any requires at least one awaitable");
726   802  
727 - OwnedRange owned_awaitables = std::forward<R>(awaitables); 803 + OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
728   804  
729   detail::when_any_io_homogeneous_state<PayloadT> state(count); 805   detail::when_any_io_homogeneous_state<PayloadT> state(count);
730   806  
731   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 807   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
732   &owned_awaitables, &state); 808   &owned_awaitables, &state);
733   809  
734   // Winner found 810   // Winner found
735   if(state.core_.has_winner_.load(std::memory_order_acquire)) 811   if(state.core_.has_winner_.load(std::memory_order_acquire))
736   { 812   {
737   if(state.core_.winner_exception_) 813   if(state.core_.winner_exception_)
738   std::rethrow_exception(state.core_.winner_exception_); 814   std::rethrow_exception(state.core_.winner_exception_);
739   co_return result_type{std::in_place_index<1>, 815   co_return result_type{std::in_place_index<1>,
740   std::pair{state.core_.winner_index_, std::move(*state.result_)}}; 816   std::pair{state.core_.winner_index_, std::move(*state.result_)}};
741   } 817   }
742   818  
743   // No winner — report the recorded failure 819   // No winner — report the recorded failure
744   if(state.last_exception_) 820   if(state.last_exception_)
745   std::rethrow_exception(state.last_exception_); 821   std::rethrow_exception(state.last_exception_);
746   co_return result_type{std::in_place_index<0>, state.last_error_}; 822   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 747   28 } 823   32 }
748   824  
749   /** Race a range of void io_result-returning awaitables. 825   /** Race a range of void io_result-returning awaitables.
750   826  
751   Only a child returning !ec can win. Returns the winner's index 827   Only a child returning !ec can win. Returns the winner's index
752   at variant index 1, or error_code at index 0 on all-fail. 828   at variant index 1, or error_code at index 0 on all-fail.
753   829  
754   @par Await-effects 830   @par Await-effects
755   831  
756   Takes ownership of the range, creates one wrapper coroutine per 832   Takes ownership of the range, creates one wrapper coroutine per
757   element, then posts every wrapper to the caller's executor. All 833   element, then posts every wrapper to the caller's executor. All
758   children therefore run concurrently, each awaited with the caller's 834   children therefore run concurrently, each awaited with the caller's
759   executor and frame allocator and with a stop token owned by this 835   executor and frame allocator and with a stop token owned by this
760   operation. 836   operation.
761   837  
762   Awaiting an empty range throws `std::invalid_argument` before any 838   Awaiting an empty range throws `std::invalid_argument` before any
763   child is started. 839   child is started.
764   840  
765   The first child to await-return a zero `ec` claims the win. Claiming 841   The first child to await-return a zero `ec` claims the win. Claiming
766   the win requests stop on the operation's own stop token, which every 842   the win requests stop on the operation's own stop token, which every
767   sibling observes through the stop token it was awaited with. A child 843   sibling observes through the stop token it was awaited with. A child
768   that await-returns a non-zero `ec`, or that exits via an exception, 844   that await-returns a non-zero `ec`, or that exits via an exception,
769   does not claim the win and does not request stop. The operation keeps 845   does not claim the win and does not request stop. The operation keeps
770   waiting for a success. A stop request on the caller's stop token is 846   waiting for a success. A stop request on the caller's stop token is
771   also forwarded to every child. 847   also forwarded to every child.
772   848  
773   The await completes only after every child has finished, regardless of 849   The await completes only after every child has finished, regardless of
774   whether a win was claimed. 850   whether a win was claimed.
775   851  
776   @par Await-returns 852   @par Await-returns
777   An object of type `std::variant<std::error_code, std::size_t>`. 853   An object of type `std::variant<std::error_code, std::size_t>`.
778   854  
779   @li Index 1 holds the winner's position in the input range. The 855   @li Index 1 holds the winner's position in the input range. The
780   children have no payloads, so nothing else is reported. 856   children have no payloads, so nothing else is reported.
781   @li Index 0 holds a non-zero `error_code` when no child won, that is, 857   @li Index 0 holds a non-zero `error_code` when no child won, that is,
782   when every child failed. It is the `ec` of one of the failed 858   when every child failed. It is the `ec` of one of the failed
783   children; which one is unspecified. 859   children; which one is unspecified.
784   860  
785   If no child won and the failure selected for reporting is an 861   If no child won and the failure selected for reporting is an
786   exception rather than an `ec`, that exception is rethrown instead of 862   exception rather than an `ec`, that exception is rethrown instead of
787   await-returning. The choice of child is unspecified. 863   await-returning. The choice of child is unspecified.
788   864  
789   @par Await-postcondition 865   @par Await-postcondition
790   Every child has finished. The result holds index 1 if at least one 866   Every child has finished. The result holds index 1 if at least one
791   child await-returned a zero `ec`. Otherwise the result holds index 0, 867   child await-returned a zero `ec`. Otherwise the result holds index 0,
792   or a failed child's exception is rethrown. 868   or a failed child's exception is rethrown.
793   869  
794   @par Remarks 870   @par Remarks
795   Supports _IoAwaitable cancellation_. A canceled child await-returns a 871   Supports _IoAwaitable cancellation_. A canceled child await-returns a
796   non-zero `ec` and so cannot win; if no child has already succeeded, 872   non-zero `ec` and so cannot win; if no child has already succeeded,
797   the result settles at index 0. 873   the result settles at index 0.
798   874  
799   @par Thread Safety 875   @par Thread Safety
800   The returned task must be awaited from a single execution context. 876   The returned task must be awaited from a single execution context.
801   Child awaitables execute concurrently but complete through the caller's 877   Child awaitables execute concurrently but complete through the caller's
802   executor. 878   executor.
803   879  
804   @param awaitables Range of io_result<>-returning awaitables (must 880   @param awaitables Range of io_result<>-returning awaitables (must
805   not be empty). 881   not be empty).
806   882  
807   @return A task yielding variant<error_code, size_t> where index 0 883   @return A task yielding variant<error_code, size_t> where index 0
808   is failure and index 1 carries the winner's index. 884   is failure and index 1 carries the winner's index.
809   885  
810   @throws std::invalid_argument if range is empty. 886   @throws std::invalid_argument if range is empty.
811   887  
812   @par Exception Safety 888   @par Exception Safety
813   If all children fail and the reported failure is an exception, 889   If all children fail and the reported failure is an exception,
814   that child's exception is rethrown (which child is unspecified). 890   that child's exception is rethrown (which child is unspecified).
815   891  
  892 + @note If a branch's coroutine is abandoned before it finishes, for
  893 + example by teardown of its execution context, `when_any`
  894 + requests stop on the remaining branches, and the awaiting
  895 + parent is abandoned rather than resumed once the last branch
  896 + has finished or been abandoned.
  897 +
816   @par Example 898   @par Example
817   @par !example example_2 899   @par !example example_2
818   900  
819   901  
820   @see IoAwaitableRange, when_any 902   @see IoAwaitableRange, when_any
821   */ 903   */
822   template<IoAwaitableRange R> 904   template<IoAwaitableRange R>
823   requires detail::is_io_result_v< 905   requires detail::is_io_result_v<
824   awaitable_result_t<std::ranges::range_value_t<R>>> 906   awaitable_result_t<std::ranges::range_value_t<R>>>
825   && std::is_same_v< 907   && std::is_same_v<
826   detail::io_result_payload_t< 908   detail::io_result_payload_t<
827   awaitable_result_t<std::ranges::range_value_t<R>>>, 909   awaitable_result_t<std::ranges::range_value_t<R>>>,
828   std::tuple<>> 910   std::tuple<>>
HITCBC 829   3 [[nodiscard]] auto when_any(R&& awaitables) 911   5 [[nodiscard]] auto when_any(R&& awaitables)
830   -> task<std::variant<std::error_code, std::size_t>> 912   -> task<std::variant<std::error_code, std::size_t>>
831   { 913   {
832 - using OwnedRange = std::remove_cvref_t<R>; 914 + using OwnedRange = detail::owned_range_t<R>;
833   using result_type = std::variant<std::error_code, std::size_t>; 915   using result_type = std::variant<std::error_code, std::size_t>;
834   916  
835   auto count = std::ranges::size(awaitables); 917   auto count = std::ranges::size(awaitables);
836   if(count == 0) 918   if(count == 0)
837   throw std::invalid_argument("when_any requires at least one awaitable"); 919   throw std::invalid_argument("when_any requires at least one awaitable");
838   920  
839 - OwnedRange owned_awaitables = std::forward<R>(awaitables); 921 + OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
840   922  
841   detail::when_any_io_homogeneous_state<std::tuple<>> state(count); 923   detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
842   924  
843   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 925   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
844   &owned_awaitables, &state); 926   &owned_awaitables, &state);
845   927  
846   // Winner found 928   // Winner found
847   if(state.core_.has_winner_.load(std::memory_order_acquire)) 929   if(state.core_.has_winner_.load(std::memory_order_acquire))
848   { 930   {
849   if(state.core_.winner_exception_) 931   if(state.core_.winner_exception_)
850   std::rethrow_exception(state.core_.winner_exception_); 932   std::rethrow_exception(state.core_.winner_exception_);
851   co_return result_type{std::in_place_index<1>, 933   co_return result_type{std::in_place_index<1>,
852   state.core_.winner_index_}; 934   state.core_.winner_index_};
853   } 935   }
854   936  
855   // No winner — report the recorded failure 937   // No winner — report the recorded failure
856   if(state.last_exception_) 938   if(state.last_exception_)
857   std::rethrow_exception(state.last_exception_); 939   std::rethrow_exception(state.last_exception_);
858   co_return result_type{std::in_place_index<0>, state.last_error_}; 940   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 859   6 } 941   10 }
860   942  
861   /** Race io_result-returning awaitables, selecting the first success. 943   /** Race io_result-returning awaitables, selecting the first success.
862   944  
863   Overload selected when all children return io_result<Ts...>. 945   Overload selected when all children return io_result<Ts...>.
864   Only a child returning !ec can win. Errors and exceptions do 946   Only a child returning !ec can win. Errors and exceptions do
865   not claim winner status. 947   not claim winner status.
866   948  
867   @par Await-effects 949   @par Await-effects
868   950  
869   Creates and posts one wrapper coroutine per argument to the caller's 951   Creates and posts one wrapper coroutine per argument to the caller's
870   executor, in argument order. All children therefore run concurrently, 952   executor, in argument order. All children therefore run concurrently,
871   each awaited with the caller's executor and frame allocator and with 953   each awaited with the caller's executor and frame allocator and with
872   a stop token owned by this operation. The overload requires at least 954   a stop token owned by this operation. The overload requires at least
873   one awaitable, so there is no empty case. 955   one awaitable, so there is no empty case.
874   956  
875   The first child to await-return a zero `ec` claims the win. Claiming 957   The first child to await-return a zero `ec` claims the win. Claiming
876   the win requests stop on the operation's own stop token, which every 958   the win requests stop on the operation's own stop token, which every
877   sibling observes through the stop token it was awaited with. A child 959   sibling observes through the stop token it was awaited with. A child
878   that await-returns a non-zero `ec`, or that exits via an exception, 960   that await-returns a non-zero `ec`, or that exits via an exception,
879   does not claim the win and does not request stop. The operation keeps 961   does not claim the win and does not request stop. The operation keeps
880   waiting for a success. A stop request on the caller's stop token is 962   waiting for a success. A stop request on the caller's stop token is
881   also forwarded to every child. 963   also forwarded to every child.
882   964  
883   The await completes only after every child has finished, regardless of 965   The await completes only after every child has finished, regardless of
884   whether a win was claimed. 966   whether a win was claimed.
885   967  
886   @par Await-returns 968   @par Await-returns
887   An object of type `std::variant<std::error_code, P1, ..., Pn>`, where 969   An object of type `std::variant<std::error_code, P1, ..., Pn>`, where
888   `Pi` is the payload of the i-th child's `io_result`. 970   `Pi` is the payload of the i-th child's `io_result`.
889   971  
890   @li Index i+1 identifies the i-th argument as the winner and holds 972   @li Index i+1 identifies the i-th argument as the winner and holds
891   its payload. 973   its payload.
892   @li Index 0 holds a non-zero `error_code` when no child won, that is, 974   @li Index 0 holds a non-zero `error_code` when no child won, that is,
893   when every child failed. It is the `ec` of one of the failed 975   when every child failed. It is the `ec` of one of the failed
894   children; which one is unspecified. 976   children; which one is unspecified.
895   977  
896   A child that succeeds after the win has already been claimed 978   A child that succeeds after the win has already been claimed
897   contributes nothing: its payload is discarded. 979   contributes nothing: its payload is discarded.
898   980  
899   If no child won and the failure selected for reporting is an 981   If no child won and the failure selected for reporting is an
900   exception rather than an `ec`, that exception is rethrown instead of 982   exception rather than an `ec`, that exception is rethrown instead of
901   await-returning. The choice of child is unspecified. 983   await-returning. The choice of child is unspecified.
902   984  
903   @par Await-postcondition 985   @par Await-postcondition
904   Every child has finished. If at least one child await-returned a zero 986   Every child has finished. If at least one child await-returned a zero
905   `ec`, the result holds the index of the winning child. If producing 987   `ec`, the result holds the index of the winning child. If producing
906   the winner's payload threw, that exception is rethrown. Otherwise the 988   the winner's payload threw, that exception is rethrown. Otherwise the
907   result holds index 0, or a failed child's exception is rethrown. 989   result holds index 0, or a failed child's exception is rethrown.
908   990  
909   @par Remarks 991   @par Remarks
910   Supports _IoAwaitable cancellation_. A canceled child await-returns a 992   Supports _IoAwaitable cancellation_. A canceled child await-returns a
911   non-zero `ec` and so cannot win; if no child has already succeeded, 993   non-zero `ec` and so cannot win; if no child has already succeeded,
912   the result settles at index 0. 994   the result settles at index 0.
913   995  
914   @par Thread Safety 996   @par Thread Safety
915   The returned task must be awaited from a single execution context. 997   The returned task must be awaited from a single execution context.
916   Child awaitables execute concurrently but complete through the caller's 998   Child awaitables execute concurrently but complete through the caller's
917   executor. 999   executor.
918   1000  
919   @param as The awaitables to race. Each must satisfy @ref 1001   @param as The awaitables to race. Each must satisfy @ref
920   IoAwaitable and is consumed (moved-from) when `when_any` 1002   IoAwaitable and is consumed (moved-from) when `when_any`
921   is awaited. 1003   is awaited.
922   1004  
923   @return A task yielding variant<error_code, R1, ..., Rn> where 1005   @return A task yielding variant<error_code, R1, ..., Rn> where
924   index 0 is the failure/no-winner case and index i+1 1006   index 0 is the failure/no-winner case and index i+1
925   identifies the winning child. On all-fail, index 0 holds 1007   identifies the winning child. On all-fail, index 0 holds
926   an error_code from one of the failed children (unspecified 1008   an error_code from one of the failed children (unspecified
927   which; no priority between errors and exceptions). 1009   which; no priority between errors and exceptions).
928   1010  
929   @par Exception Safety 1011   @par Exception Safety
930   The winner's exception is rethrown if extracting or constructing 1012   The winner's exception is rethrown if extracting or constructing
931   the winning payload throws. In that case a winner was found, but 1013   the winning payload throws. In that case a winner was found, but
932   its result could not be produced. If all children fail and the 1014   its result could not be produced. If all children fail and the
933   reported failure is an exception, that child's exception is 1015   reported failure is an exception, that child's exception is
934   rethrown (which child is unspecified). 1016   rethrown (which child is unspecified).
935   1017  
936   @note A failing child does not cancel its siblings; `when_any` 1018   @note A failing child does not cancel its siblings; `when_any`
937   waits for a success or for every child to finish. To make a 1019   waits for a success or for every child to finish. To make a
938   benign error (e.g. @c cond::canceled) count as a win, wrap 1020   benign error (e.g. @c cond::canceled) count as a win, wrap
939   the child to translate the error into success. See the 1021   the child to translate the error into success. See the
940   Concurrent Composition tutorial. 1022   Concurrent Composition tutorial.
  1023 +
  1024 + @note If a branch's coroutine is abandoned before it finishes, for
  1025 + example by teardown of its execution context, `when_any`
  1026 + requests stop on the remaining branches, and the awaiting
  1027 + parent is abandoned rather than resumed once the last branch
  1028 + has finished or been abandoned.
941   */ 1029   */
942   template<IoAwaitable... As> 1030   template<IoAwaitable... As>
943   requires (sizeof...(As) > 0) 1031   requires (sizeof...(As) > 0)
944   && detail::all_io_result_awaitables<As...> 1032   && detail::all_io_result_awaitables<As...>
HITCBC 945   23 [[nodiscard]] auto when_any(As... as) 1033   33 [[nodiscard]] auto when_any(As... as)
946   -> task<std::variant< 1034   -> task<std::variant<
947   std::error_code, 1035   std::error_code,
948   detail::io_result_payload_t<awaitable_result_t<As>>...>> 1036   detail::io_result_payload_t<awaitable_result_t<As>>...>>
949   { 1037   {
950   using result_type = std::variant< 1038   using result_type = std::variant<
951   std::error_code, 1039   std::error_code,
952   detail::io_result_payload_t<awaitable_result_t<As>>...>; 1040   detail::io_result_payload_t<awaitable_result_t<As>>...>;
953   1041  
954   detail::when_any_io_state< 1042   detail::when_any_io_state<
955   detail::io_result_payload_t<awaitable_result_t<As>>...> state; 1043   detail::io_result_payload_t<awaitable_result_t<As>>...> state;
956   std::tuple<As...> awaitable_tuple(std::move(as)...); 1044   std::tuple<As...> awaitable_tuple(std::move(as)...);
957   1045  
958   co_await detail::when_any_io_launcher<As...>( 1046   co_await detail::when_any_io_launcher<As...>(
959   &awaitable_tuple, &state); 1047   &awaitable_tuple, &state);
960   1048  
961   // Winner found: return their result 1049   // Winner found: return their result
962   if(state.result_.has_value()) 1050   if(state.result_.has_value())
963   co_return std::move(*state.result_); 1051   co_return std::move(*state.result_);
964   1052  
965   // Winner claimed but payload construction failed 1053   // Winner claimed but payload construction failed
966   if(state.core_.winner_exception_) 1054   if(state.core_.winner_exception_)
967   std::rethrow_exception(state.core_.winner_exception_); 1055   std::rethrow_exception(state.core_.winner_exception_);
968   1056  
969   // No winner — report the recorded failure 1057   // No winner — report the recorded failure
970   if(state.last_exception_) 1058   if(state.last_exception_)
971   std::rethrow_exception(state.last_exception_); 1059   std::rethrow_exception(state.last_exception_);
972   co_return result_type{std::in_place_index<0>, state.last_error_}; 1060   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 973   46 } 1061   66 }
974   1062  
975   } // namespace capy 1063   } // namespace capy
976   } // namespace boost 1064   } // namespace boost
977   1065  
978   #endif 1066   #endif