1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
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_ANY_HPP
10  
#ifndef BOOST_CAPY_WHEN_ANY_HPP
11  
#define BOOST_CAPY_WHEN_ANY_HPP
11  
#define BOOST_CAPY_WHEN_ANY_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/concept/executor.hpp>
14  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/io_awaitable.hpp>
15  
#include <boost/capy/concept/io_awaitable.hpp>
16  
#include <boost/capy/coro.hpp>
16  
#include <boost/capy/coro.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/frame_allocator.hpp>
18  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/task.hpp>
19  
#include <boost/capy/task.hpp>
20  

20  

21  
#include <array>
21  
#include <array>
22  
#include <atomic>
22  
#include <atomic>
23  
#include <exception>
23  
#include <exception>
24  
#include <optional>
24  
#include <optional>
25  
#include <ranges>
25  
#include <ranges>
26  
#include <stdexcept>
26  
#include <stdexcept>
27  
#include <stop_token>
27  
#include <stop_token>
28  
#include <tuple>
28  
#include <tuple>
29  
#include <type_traits>
29  
#include <type_traits>
30  
#include <utility>
30  
#include <utility>
31  
#include <variant>
31  
#include <variant>
32  
#include <vector>
32  
#include <vector>
33  

33  

34  
/*
34  
/*
35  
   when_any - Race multiple tasks, return first completion
35  
   when_any - Race multiple tasks, return first completion
36  
   ========================================================
36  
   ========================================================
37  

37  

38  
   OVERVIEW:
38  
   OVERVIEW:
39  
   ---------
39  
   ---------
40  
   when_any launches N tasks concurrently and completes when the FIRST task
40  
   when_any launches N tasks concurrently and completes when the FIRST task
41  
   finishes (success or failure). It then requests stop for all siblings and
41  
   finishes (success or failure). It then requests stop for all siblings and
42  
   waits for them to acknowledge before returning.
42  
   waits for them to acknowledge before returning.
43  

43  

44  
   ARCHITECTURE:
44  
   ARCHITECTURE:
45  
   -------------
45  
   -------------
46  
   The design mirrors when_all but with inverted completion semantics:
46  
   The design mirrors when_all but with inverted completion semantics:
47  

47  

48  
     when_all:  complete when remaining_count reaches 0 (all done)
48  
     when_all:  complete when remaining_count reaches 0 (all done)
49  
     when_any:  complete when has_winner becomes true (first done)
49  
     when_any:  complete when has_winner becomes true (first done)
50  
                BUT still wait for remaining_count to reach 0 for cleanup
50  
                BUT still wait for remaining_count to reach 0 for cleanup
51  

51  

52  
   Key components:
52  
   Key components:
53  
     - when_any_state:    Shared state tracking winner and completion
53  
     - when_any_state:    Shared state tracking winner and completion
54  
     - when_any_runner:   Wrapper coroutine for each child task
54  
     - when_any_runner:   Wrapper coroutine for each child task
55  
     - when_any_launcher: Awaitable that starts all runners concurrently
55  
     - when_any_launcher: Awaitable that starts all runners concurrently
56  

56  

57  
   CRITICAL INVARIANTS:
57  
   CRITICAL INVARIANTS:
58  
   --------------------
58  
   --------------------
59  
   1. Exactly one task becomes the winner (via atomic compare_exchange)
59  
   1. Exactly one task becomes the winner (via atomic compare_exchange)
60  
   2. All tasks must complete before parent resumes (cleanup safety)
60  
   2. All tasks must complete before parent resumes (cleanup safety)
61  
   3. Stop is requested immediately when winner is determined
61  
   3. Stop is requested immediately when winner is determined
62  
   4. Only the winner's result/exception is stored
62  
   4. Only the winner's result/exception is stored
63  

63  

64  
   TYPE DEDUPLICATION:
64  
   TYPE DEDUPLICATION:
65  
   -------------------
65  
   -------------------
66  
   std::variant requires unique alternative types. Since when_any can race
66  
   std::variant requires unique alternative types. Since when_any can race
67  
   tasks with identical return types (e.g., three task<int>), we must
67  
   tasks with identical return types (e.g., three task<int>), we must
68  
   deduplicate types before constructing the variant.
68  
   deduplicate types before constructing the variant.
69  

69  

70  
   Example: when_any(task<int>, task<string>, task<int>)
70  
   Example: when_any(task<int>, task<string>, task<int>)
71  
     - Raw types after void->monostate: int, string, int
71  
     - Raw types after void->monostate: int, string, int
72  
     - Deduplicated variant: std::variant<int, string>
72  
     - Deduplicated variant: std::variant<int, string>
73  
     - Return: pair<size_t, variant<int, string>>
73  
     - Return: pair<size_t, variant<int, string>>
74  

74  

75  
   The winner_index tells you which task won (0, 1, or 2), while the variant
75  
   The winner_index tells you which task won (0, 1, or 2), while the variant
76  
   holds the result. Use the index to determine how to interpret the variant.
76  
   holds the result. Use the index to determine how to interpret the variant.
77  

77  

78  
   VOID HANDLING:
78  
   VOID HANDLING:
79  
   --------------
79  
   --------------
80  
   void tasks contribute std::monostate to the variant (then deduplicated).
80  
   void tasks contribute std::monostate to the variant (then deduplicated).
81  
   All-void tasks result in: pair<size_t, variant<monostate>>
81  
   All-void tasks result in: pair<size_t, variant<monostate>>
82  

82  

83  
   MEMORY MODEL:
83  
   MEMORY MODEL:
84  
   -------------
84  
   -------------
85  
   Synchronization chain from winner's write to parent's read:
85  
   Synchronization chain from winner's write to parent's read:
86  

86  

87  
   1. Winner thread writes result_/winner_exception_ (non-atomic)
87  
   1. Winner thread writes result_/winner_exception_ (non-atomic)
88  
   2. Winner thread calls signal_completion() → fetch_sub(acq_rel) on remaining_count_
88  
   2. Winner thread calls signal_completion() → fetch_sub(acq_rel) on remaining_count_
89  
   3. Last task thread (may be winner or non-winner) calls signal_completion()
89  
   3. Last task thread (may be winner or non-winner) calls signal_completion()
90  
      → fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
90  
      → fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
91  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
91  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
92  
   5. Parent coroutine resumes and reads result_/winner_exception_
92  
   5. Parent coroutine resumes and reads result_/winner_exception_
93  

93  

94  
   Synchronization analysis:
94  
   Synchronization analysis:
95  
   - All fetch_sub operations on remaining_count_ form a release sequence
95  
   - All fetch_sub operations on remaining_count_ form a release sequence
96  
   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
96  
   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
97  
     in the modification order of remaining_count_
97  
     in the modification order of remaining_count_
98  
   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the
98  
   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the
99  
     modification order, establishing happens-before from winner's writes
99  
     modification order, establishing happens-before from winner's writes
100  
   - Executor dispatch() is expected to provide queue-based synchronization
100  
   - Executor dispatch() is expected to provide queue-based synchronization
101  
     (release-on-post, acquire-on-execute) completing the chain to parent
101  
     (release-on-post, acquire-on-execute) completing the chain to parent
102  
   - Even inline executors work (same thread = sequenced-before)
102  
   - Even inline executors work (same thread = sequenced-before)
103  

103  

104  
   Alternative considered: Adding winner_ready_ atomic (set with release after
104  
   Alternative considered: Adding winner_ready_ atomic (set with release after
105  
   storing winner data, acquired before reading) would make synchronization
105  
   storing winner data, acquired before reading) would make synchronization
106  
   self-contained and not rely on executor implementation details. Current
106  
   self-contained and not rely on executor implementation details. Current
107  
   approach is correct but requires careful reasoning about release sequences
107  
   approach is correct but requires careful reasoning about release sequences
108  
   and executor behavior.
108  
   and executor behavior.
109  

109  

110  
   EXCEPTION SEMANTICS:
110  
   EXCEPTION SEMANTICS:
111  
   --------------------
111  
   --------------------
112  
   Unlike when_all (which captures first exception, discards others), when_any
112  
   Unlike when_all (which captures first exception, discards others), when_any
113  
   treats exceptions as valid completions. If the winning task threw, that
113  
   treats exceptions as valid completions. If the winning task threw, that
114  
   exception is rethrown. Exceptions from non-winners are silently discarded.
114  
   exception is rethrown. Exceptions from non-winners are silently discarded.
115  
*/
115  
*/
116  

116  

117  
namespace boost {
117  
namespace boost {
118  
namespace capy {
118  
namespace capy {
119  

119  

120  
namespace detail {
120  
namespace detail {
121  

121  

122  
/** Convert void to monostate for variant storage.
122  
/** Convert void to monostate for variant storage.
123  

123  

124  
    std::variant<void, ...> is ill-formed, so void tasks contribute
124  
    std::variant<void, ...> is ill-formed, so void tasks contribute
125  
    std::monostate to the result variant instead. Non-void types
125  
    std::monostate to the result variant instead. Non-void types
126  
    pass through unchanged.
126  
    pass through unchanged.
127  

127  

128  
    @tparam T The type to potentially convert (void becomes monostate).
128  
    @tparam T The type to potentially convert (void becomes monostate).
129  
*/
129  
*/
130  
template<typename T>
130  
template<typename T>
131  
using void_to_monostate_t = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
131  
using void_to_monostate_t = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
132  

132  

133  
// Type deduplication: std::variant requires unique alternative types.
133  
// Type deduplication: std::variant requires unique alternative types.
134  
// Fold left over the type list, appending each type only if not already present.
134  
// Fold left over the type list, appending each type only if not already present.
135  
template<typename Variant, typename T>
135  
template<typename Variant, typename T>
136  
struct variant_append_if_unique;
136  
struct variant_append_if_unique;
137  

137  

138  
template<typename... Vs, typename T>
138  
template<typename... Vs, typename T>
139  
struct variant_append_if_unique<std::variant<Vs...>, T>
139  
struct variant_append_if_unique<std::variant<Vs...>, T>
140  
{
140  
{
141  
    using type = std::conditional_t<
141  
    using type = std::conditional_t<
142  
        (std::is_same_v<T, Vs> || ...),
142  
        (std::is_same_v<T, Vs> || ...),
143  
        std::variant<Vs...>,
143  
        std::variant<Vs...>,
144  
        std::variant<Vs..., T>>;
144  
        std::variant<Vs..., T>>;
145  
};
145  
};
146  

146  

147  
template<typename Accumulated, typename... Remaining>
147  
template<typename Accumulated, typename... Remaining>
148  
struct deduplicate_impl;
148  
struct deduplicate_impl;
149  

149  

150  
template<typename Accumulated>
150  
template<typename Accumulated>
151  
struct deduplicate_impl<Accumulated>
151  
struct deduplicate_impl<Accumulated>
152  
{
152  
{
153  
    using type = Accumulated;
153  
    using type = Accumulated;
154  
};
154  
};
155  

155  

156  
template<typename Accumulated, typename T, typename... Rest>
156  
template<typename Accumulated, typename T, typename... Rest>
157  
struct deduplicate_impl<Accumulated, T, Rest...>
157  
struct deduplicate_impl<Accumulated, T, Rest...>
158  
{
158  
{
159  
    using next = typename variant_append_if_unique<Accumulated, T>::type;
159  
    using next = typename variant_append_if_unique<Accumulated, T>::type;
160  
    using type = typename deduplicate_impl<next, Rest...>::type;
160  
    using type = typename deduplicate_impl<next, Rest...>::type;
161  
};
161  
};
162  

162  

163  
// Deduplicated variant; void types become monostate before deduplication
163  
// Deduplicated variant; void types become monostate before deduplication
164  
template<typename T0, typename... Ts>
164  
template<typename T0, typename... Ts>
165  
using unique_variant_t = typename deduplicate_impl<
165  
using unique_variant_t = typename deduplicate_impl<
166  
    std::variant<void_to_monostate_t<T0>>,
166  
    std::variant<void_to_monostate_t<T0>>,
167  
    void_to_monostate_t<Ts>...>::type;
167  
    void_to_monostate_t<Ts>...>::type;
168  

168  

169  
// Result: (winner_index, deduplicated_variant). Use index to disambiguate
169  
// Result: (winner_index, deduplicated_variant). Use index to disambiguate
170  
// when multiple tasks share the same return type.
170  
// when multiple tasks share the same return type.
171  
template<typename T0, typename... Ts>
171  
template<typename T0, typename... Ts>
172  
using when_any_result_t = std::pair<std::size_t, unique_variant_t<T0, Ts...>>;
172  
using when_any_result_t = std::pair<std::size_t, unique_variant_t<T0, Ts...>>;
173 -
// Extract result type from any awaitable via await_resume()
 
174 -
template<typename A>
 
175 -
using awaitable_result_t = decltype(std::declval<std::decay_t<A>&>().await_resume());
 
176 -

 
177  

173  

178  
/** Core shared state for when_any operations.
174  
/** Core shared state for when_any operations.
179  

175  

180  
    Contains all members and methods common to both heterogeneous (variadic)
176  
    Contains all members and methods common to both heterogeneous (variadic)
181  
    and homogeneous (range) when_any implementations. State classes embed
177  
    and homogeneous (range) when_any implementations. State classes embed
182  
    this via composition to avoid CRTP destructor ordering issues.
178  
    this via composition to avoid CRTP destructor ordering issues.
183  

179  

184  
    @par Thread Safety
180  
    @par Thread Safety
185  
    Atomic operations protect winner selection and completion count.
181  
    Atomic operations protect winner selection and completion count.
186  
*/
182  
*/
187  
struct when_any_core
183  
struct when_any_core
188  
{
184  
{
189  
    std::atomic<std::size_t> remaining_count_;
185  
    std::atomic<std::size_t> remaining_count_;
190  
    std::size_t winner_index_{0};
186  
    std::size_t winner_index_{0};
191  
    std::exception_ptr winner_exception_;
187  
    std::exception_ptr winner_exception_;
192  
    std::stop_source stop_source_;
188  
    std::stop_source stop_source_;
193  

189  

194  
    // Bridges parent's stop token to our stop_source
190  
    // Bridges parent's stop token to our stop_source
195  
    struct stop_callback_fn
191  
    struct stop_callback_fn
196  
    {
192  
    {
197  
        std::stop_source* source_;
193  
        std::stop_source* source_;
198  
        void operator()() const noexcept { source_->request_stop(); }
194  
        void operator()() const noexcept { source_->request_stop(); }
199  
    };
195  
    };
200  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
196  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
201  
    std::optional<stop_callback_t> parent_stop_callback_;
197  
    std::optional<stop_callback_t> parent_stop_callback_;
202  

198  

203  
    coro continuation_;
199  
    coro continuation_;
204  
    executor_ref caller_ex_;
200  
    executor_ref caller_ex_;
205  

201  

206  
    // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
202  
    // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
207  
    std::atomic<bool> has_winner_{false};
203  
    std::atomic<bool> has_winner_{false};
208  

204  

209  
    explicit when_any_core(std::size_t count) noexcept
205  
    explicit when_any_core(std::size_t count) noexcept
210  
        : remaining_count_(count)
206  
        : remaining_count_(count)
211  
    {
207  
    {
212  
    }
208  
    }
213  

209  

214  
    /** Atomically claim winner status; exactly one task succeeds. */
210  
    /** Atomically claim winner status; exactly one task succeeds. */
215  
    bool try_win(std::size_t index) noexcept
211  
    bool try_win(std::size_t index) noexcept
216  
    {
212  
    {
217  
        bool expected = false;
213  
        bool expected = false;
218  
        if(has_winner_.compare_exchange_strong(
214  
        if(has_winner_.compare_exchange_strong(
219  
            expected, true, std::memory_order_acq_rel))
215  
            expected, true, std::memory_order_acq_rel))
220  
        {
216  
        {
221  
            winner_index_ = index;
217  
            winner_index_ = index;
222  
            stop_source_.request_stop();
218  
            stop_source_.request_stop();
223  
            return true;
219  
            return true;
224  
        }
220  
        }
225  
        return false;
221  
        return false;
226  
    }
222  
    }
227  

223  

228  
    /** @pre try_win() returned true. */
224  
    /** @pre try_win() returned true. */
229  
    void set_winner_exception(std::exception_ptr ep) noexcept
225  
    void set_winner_exception(std::exception_ptr ep) noexcept
230  
    {
226  
    {
231  
        winner_exception_ = ep;
227  
        winner_exception_ = ep;
232  
    }
228  
    }
233  

229  

234  
    // Runners signal completion directly via final_suspend; no member function needed.
230  
    // Runners signal completion directly via final_suspend; no member function needed.
235  
};
231  
};
236  

232  

237  
/** Shared state for heterogeneous when_any operation.
233  
/** Shared state for heterogeneous when_any operation.
238  

234  

239  
    Coordinates winner selection, result storage, and completion tracking
235  
    Coordinates winner selection, result storage, and completion tracking
240  
    for all child tasks in a when_any operation. Uses composition with
236  
    for all child tasks in a when_any operation. Uses composition with
241  
    when_any_core for shared functionality.
237  
    when_any_core for shared functionality.
242  

238  

243  
    @par Lifetime
239  
    @par Lifetime
244  
    Allocated on the parent coroutine's frame, outlives all runners.
240  
    Allocated on the parent coroutine's frame, outlives all runners.
245  

241  

246  
    @tparam T0 First task's result type.
242  
    @tparam T0 First task's result type.
247  
    @tparam Ts Remaining tasks' result types.
243  
    @tparam Ts Remaining tasks' result types.
248  
*/
244  
*/
249  
template<typename T0, typename... Ts>
245  
template<typename T0, typename... Ts>
250  
struct when_any_state
246  
struct when_any_state
251  
{
247  
{
252  
    static constexpr std::size_t task_count = 1 + sizeof...(Ts);
248  
    static constexpr std::size_t task_count = 1 + sizeof...(Ts);
253  
    using variant_type = unique_variant_t<T0, Ts...>;
249  
    using variant_type = unique_variant_t<T0, Ts...>;
254  

250  

255  
    when_any_core core_;
251  
    when_any_core core_;
256  
    std::optional<variant_type> result_;
252  
    std::optional<variant_type> result_;
257  
    std::array<coro, task_count> runner_handles_{};
253  
    std::array<coro, task_count> runner_handles_{};
258  

254  

259  
    when_any_state()
255  
    when_any_state()
260  
        : core_(task_count)
256  
        : core_(task_count)
261  
    {
257  
    {
262  
    }
258  
    }
263  

259  

264  
    // Runners self-destruct in final_suspend. No destruction needed here.
260  
    // Runners self-destruct in final_suspend. No destruction needed here.
265  

261  

266  
    /** @pre core_.try_win() returned true.
262  
    /** @pre core_.try_win() returned true.
267  
        @note Uses in_place_type (not index) because variant is deduplicated.
263  
        @note Uses in_place_type (not index) because variant is deduplicated.
268  
    */
264  
    */
269  
    template<typename T>
265  
    template<typename T>
270  
    void set_winner_result(T value)
266  
    void set_winner_result(T value)
271  
        noexcept(std::is_nothrow_move_constructible_v<T>)
267  
        noexcept(std::is_nothrow_move_constructible_v<T>)
272  
    {
268  
    {
273  
        result_.emplace(std::in_place_type<T>, std::move(value));
269  
        result_.emplace(std::in_place_type<T>, std::move(value));
274  
    }
270  
    }
275  

271  

276  
    /** @pre core_.try_win() returned true. */
272  
    /** @pre core_.try_win() returned true. */
277  
    void set_winner_void() noexcept
273  
    void set_winner_void() noexcept
278  
    {
274  
    {
279  
        result_.emplace(std::in_place_type<std::monostate>, std::monostate{});
275  
        result_.emplace(std::in_place_type<std::monostate>, std::monostate{});
280  
    }
276  
    }
281  
};
277  
};
282  

278  

283  
/** Wrapper coroutine that runs a single child task for when_any.
279  
/** Wrapper coroutine that runs a single child task for when_any.
284  

280  

285  
    Propagates executor/stop_token to the child, attempts to claim winner
281  
    Propagates executor/stop_token to the child, attempts to claim winner
286  
    status on completion, and signals completion for cleanup coordination.
282  
    status on completion, and signals completion for cleanup coordination.
287  

283  

288  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
284  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
289  
*/
285  
*/
290  
template<typename StateType>
286  
template<typename StateType>
291  
struct when_any_runner
287  
struct when_any_runner
292  
{
288  
{
293  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
289  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
294  
    {
290  
    {
295  
        StateType* state_ = nullptr;
291  
        StateType* state_ = nullptr;
296  
        std::size_t index_ = 0;
292  
        std::size_t index_ = 0;
297  
        executor_ref ex_;
293  
        executor_ref ex_;
298  
        std::stop_token stop_token_;
294  
        std::stop_token stop_token_;
299  

295  

300  
        when_any_runner get_return_object() noexcept
296  
        when_any_runner get_return_object() noexcept
301  
        {
297  
        {
302  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
298  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
303  
        }
299  
        }
304  

300  

305  
        // Starts suspended; launcher sets up state/ex/token then resumes
301  
        // Starts suspended; launcher sets up state/ex/token then resumes
306  
        std::suspend_always initial_suspend() noexcept
302  
        std::suspend_always initial_suspend() noexcept
307  
        {
303  
        {
308  
            return {};
304  
            return {};
309  
        }
305  
        }
310  

306  

311  
        auto final_suspend() noexcept
307  
        auto final_suspend() noexcept
312  
        {
308  
        {
313  
            struct awaiter
309  
            struct awaiter
314  
            {
310  
            {
315  
                promise_type* p_;
311  
                promise_type* p_;
316  
                bool await_ready() const noexcept { return false; }
312  
                bool await_ready() const noexcept { return false; }
317  
                void await_suspend(coro h) noexcept
313  
                void await_suspend(coro h) noexcept
318  
                {
314  
                {
319  
                    // Extract everything needed for signaling before
315  
                    // Extract everything needed for signaling before
320  
                    // self-destruction. Inline dispatch may destroy
316  
                    // self-destruction. Inline dispatch may destroy
321  
                    // state, so we can't access members after.
317  
                    // state, so we can't access members after.
322  
                    auto& core = p_->state_->core_;
318  
                    auto& core = p_->state_->core_;
323  
                    auto* counter = &core.remaining_count_;
319  
                    auto* counter = &core.remaining_count_;
324  
                    auto caller_ex = core.caller_ex_;
320  
                    auto caller_ex = core.caller_ex_;
325  
                    auto cont = core.continuation_;
321  
                    auto cont = core.continuation_;
326  

322  

327  
                    // Self-destruct first - state no longer destroys runners
323  
                    // Self-destruct first - state no longer destroys runners
328  
                    h.destroy();
324  
                    h.destroy();
329  

325  

330  
                    // Signal completion. If last, dispatch parent.
326  
                    // Signal completion. If last, dispatch parent.
331  
                    // Uses only local copies - safe even if state
327  
                    // Uses only local copies - safe even if state
332  
                    // is destroyed during inline dispatch.
328  
                    // is destroyed during inline dispatch.
333  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
329  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
334  
                    if(remaining == 1)
330  
                    if(remaining == 1)
335  
                        caller_ex.dispatch(cont);
331  
                        caller_ex.dispatch(cont);
336  
                }
332  
                }
337  
                void await_resume() const noexcept {}
333  
                void await_resume() const noexcept {}
338  
            };
334  
            };
339  
            return awaiter{this};
335  
            return awaiter{this};
340  
        }
336  
        }
341  

337  

342  
        void return_void() noexcept {}
338  
        void return_void() noexcept {}
343  

339  

344  
        // Exceptions are valid completions in when_any (unlike when_all)
340  
        // Exceptions are valid completions in when_any (unlike when_all)
345  
        void unhandled_exception()
341  
        void unhandled_exception()
346  
        {
342  
        {
347  
            if(state_->core_.try_win(index_))
343  
            if(state_->core_.try_win(index_))
348  
                state_->core_.set_winner_exception(std::current_exception());
344  
                state_->core_.set_winner_exception(std::current_exception());
349  
        }
345  
        }
350  

346  

351  
        /** Injects executor and stop token into child awaitables. */
347  
        /** Injects executor and stop token into child awaitables. */
352  
        template<class Awaitable>
348  
        template<class Awaitable>
353  
        struct transform_awaiter
349  
        struct transform_awaiter
354  
        {
350  
        {
355  
            std::decay_t<Awaitable> a_;
351  
            std::decay_t<Awaitable> a_;
356  
            promise_type* p_;
352  
            promise_type* p_;
357  

353  

358  
            bool await_ready() { return a_.await_ready(); }
354  
            bool await_ready() { return a_.await_ready(); }
359  
            auto await_resume() { return a_.await_resume(); }
355  
            auto await_resume() { return a_.await_resume(); }
360  

356  

361  
            template<class Promise>
357  
            template<class Promise>
362  
            auto await_suspend(std::coroutine_handle<Promise> h)
358  
            auto await_suspend(std::coroutine_handle<Promise> h)
363  
            {
359  
            {
364  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
360  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
365  
            }
361  
            }
366  
        };
362  
        };
367  

363  

368  
        template<class Awaitable>
364  
        template<class Awaitable>
369  
        auto await_transform(Awaitable&& a)
365  
        auto await_transform(Awaitable&& a)
370  
        {
366  
        {
371  
            using A = std::decay_t<Awaitable>;
367  
            using A = std::decay_t<Awaitable>;
372  
            if constexpr (IoAwaitable<A>)
368  
            if constexpr (IoAwaitable<A>)
373  
            {
369  
            {
374  
                return transform_awaiter<Awaitable>{
370  
                return transform_awaiter<Awaitable>{
375  
                    std::forward<Awaitable>(a), this};
371  
                    std::forward<Awaitable>(a), this};
376  
            }
372  
            }
377  
            else
373  
            else
378  
            {
374  
            {
379  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
375  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
380  
            }
376  
            }
381  
        }
377  
        }
382  
    };
378  
    };
383  

379  

384  
    std::coroutine_handle<promise_type> h_;
380  
    std::coroutine_handle<promise_type> h_;
385  

381  

386  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
382  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
387  
        : h_(h)
383  
        : h_(h)
388  
    {
384  
    {
389  
    }
385  
    }
390  

386  

391  
    // Enable move for all clang versions - some versions need it
387  
    // Enable move for all clang versions - some versions need it
392  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
388  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
393  

389  

394  
    // Non-copyable
390  
    // Non-copyable
395  
    when_any_runner(when_any_runner const&) = delete;
391  
    when_any_runner(when_any_runner const&) = delete;
396  
    when_any_runner& operator=(when_any_runner const&) = delete;
392  
    when_any_runner& operator=(when_any_runner const&) = delete;
397  
    when_any_runner& operator=(when_any_runner&&) = delete;
393  
    when_any_runner& operator=(when_any_runner&&) = delete;
398  

394  

399  
    auto release() noexcept
395  
    auto release() noexcept
400  
    {
396  
    {
401  
        return std::exchange(h_, nullptr);
397  
        return std::exchange(h_, nullptr);
402  
    }
398  
    }
403  
};
399  
};
404  

400  

405  
/** Wraps a child awaitable, attempts to claim winner on completion.
401  
/** Wraps a child awaitable, attempts to claim winner on completion.
406  

402  

407  
    Uses requires-expressions to detect state capabilities:
403  
    Uses requires-expressions to detect state capabilities:
408  
    - set_winner_void(): for heterogeneous void tasks (stores monostate)
404  
    - set_winner_void(): for heterogeneous void tasks (stores monostate)
409  
    - set_winner_result(): for non-void tasks
405  
    - set_winner_result(): for non-void tasks
410  
    - Neither: for homogeneous void tasks (no result storage)
406  
    - Neither: for homogeneous void tasks (no result storage)
411  
*/
407  
*/
412  
template<IoAwaitable Awaitable, typename StateType>
408  
template<IoAwaitable Awaitable, typename StateType>
413  
when_any_runner<StateType>
409  
when_any_runner<StateType>
414  
make_when_any_runner(Awaitable inner, StateType* state, std::size_t index)
410  
make_when_any_runner(Awaitable inner, StateType* state, std::size_t index)
415  
{
411  
{
416  
    using T = awaitable_result_t<Awaitable>;
412  
    using T = awaitable_result_t<Awaitable>;
417  
    if constexpr (std::is_void_v<T>)
413  
    if constexpr (std::is_void_v<T>)
418  
    {
414  
    {
419  
        co_await std::move(inner);
415  
        co_await std::move(inner);
420  
        if(state->core_.try_win(index))
416  
        if(state->core_.try_win(index))
421  
        {
417  
        {
422  
            // Heterogeneous void tasks store monostate in the variant
418  
            // Heterogeneous void tasks store monostate in the variant
423  
            if constexpr (requires { state->set_winner_void(); })
419  
            if constexpr (requires { state->set_winner_void(); })
424  
                state->set_winner_void();
420  
                state->set_winner_void();
425  
            // Homogeneous void tasks have no result to store
421  
            // Homogeneous void tasks have no result to store
426  
        }
422  
        }
427  
    }
423  
    }
428  
    else
424  
    else
429  
    {
425  
    {
430  
        auto result = co_await std::move(inner);
426  
        auto result = co_await std::move(inner);
431  
        if(state->core_.try_win(index))
427  
        if(state->core_.try_win(index))
432  
        {
428  
        {
433  
            // Defensive: move should not throw (already moved once), but we
429  
            // Defensive: move should not throw (already moved once), but we
434  
            // catch just in case since an uncaught exception would be devastating.
430  
            // catch just in case since an uncaught exception would be devastating.
435  
            try
431  
            try
436  
            {
432  
            {
437  
                state->set_winner_result(std::move(result));
433  
                state->set_winner_result(std::move(result));
438  
            }
434  
            }
439  
            catch(...)
435  
            catch(...)
440  
            {
436  
            {
441  
                state->core_.set_winner_exception(std::current_exception());
437  
                state->core_.set_winner_exception(std::current_exception());
442  
            }
438  
            }
443  
        }
439  
        }
444  
    }
440  
    }
445  
}
441  
}
446  

442  

447  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
443  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
448  
template<IoAwaitable... Awaitables>
444  
template<IoAwaitable... Awaitables>
449  
class when_any_launcher
445  
class when_any_launcher
450  
{
446  
{
451  
    using state_type = when_any_state<awaitable_result_t<Awaitables>...>;
447  
    using state_type = when_any_state<awaitable_result_t<Awaitables>...>;
452  

448  

453  
    std::tuple<Awaitables...>* tasks_;
449  
    std::tuple<Awaitables...>* tasks_;
454  
    state_type* state_;
450  
    state_type* state_;
455  

451  

456  
public:
452  
public:
457  
    when_any_launcher(
453  
    when_any_launcher(
458  
        std::tuple<Awaitables...>* tasks,
454  
        std::tuple<Awaitables...>* tasks,
459  
        state_type* state)
455  
        state_type* state)
460  
        : tasks_(tasks)
456  
        : tasks_(tasks)
461  
        , state_(state)
457  
        , state_(state)
462  
    {
458  
    {
463  
    }
459  
    }
464  

460  

465  
    bool await_ready() const noexcept
461  
    bool await_ready() const noexcept
466  
    {
462  
    {
467  
        return sizeof...(Awaitables) == 0;
463  
        return sizeof...(Awaitables) == 0;
468  
    }
464  
    }
469  

465  

470  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
466  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
471  
        destroys this object before await_suspend returns. Must not reference
467  
        destroys this object before await_suspend returns. Must not reference
472  
        `this` after the final launch_one call.
468  
        `this` after the final launch_one call.
473  
    */
469  
    */
474  
    template<Executor Ex>
470  
    template<Executor Ex>
475  
    coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
471  
    coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
476  
    {
472  
    {
477  
        state_->core_.continuation_ = continuation;
473  
        state_->core_.continuation_ = continuation;
478  
        state_->core_.caller_ex_ = caller_ex;
474  
        state_->core_.caller_ex_ = caller_ex;
479  

475  

480  
        if(parent_token.stop_possible())
476  
        if(parent_token.stop_possible())
481  
        {
477  
        {
482  
            state_->core_.parent_stop_callback_.emplace(
478  
            state_->core_.parent_stop_callback_.emplace(
483  
                parent_token,
479  
                parent_token,
484  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
480  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
485  

481  

486  
            if(parent_token.stop_requested())
482  
            if(parent_token.stop_requested())
487  
                state_->core_.stop_source_.request_stop();
483  
                state_->core_.stop_source_.request_stop();
488  
        }
484  
        }
489  

485  

490  
        auto token = state_->core_.stop_source_.get_token();
486  
        auto token = state_->core_.stop_source_.get_token();
491  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
487  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
492  
            (..., launch_one<Is>(caller_ex, token));
488  
            (..., launch_one<Is>(caller_ex, token));
493  
        }(std::index_sequence_for<Awaitables...>{});
489  
        }(std::index_sequence_for<Awaitables...>{});
494  

490  

495  
        return std::noop_coroutine();
491  
        return std::noop_coroutine();
496  
    }
492  
    }
497  

493  

498  
    void await_resume() const noexcept
494  
    void await_resume() const noexcept
499  
    {
495  
    {
500  
    }
496  
    }
501  

497  

502  
private:
498  
private:
503  
    /** @pre Ex::dispatch() and coro::resume() must not throw (handle may leak). */
499  
    /** @pre Ex::dispatch() and coro::resume() must not throw (handle may leak). */
504  
    template<std::size_t I, Executor Ex>
500  
    template<std::size_t I, Executor Ex>
505  
    void launch_one(Ex const& caller_ex, std::stop_token token)
501  
    void launch_one(Ex const& caller_ex, std::stop_token token)
506  
    {
502  
    {
507  
        auto runner = make_when_any_runner(
503  
        auto runner = make_when_any_runner(
508  
            std::move(std::get<I>(*tasks_)), state_, I);
504  
            std::move(std::get<I>(*tasks_)), state_, I);
509  

505  

510  
        auto h = runner.release();
506  
        auto h = runner.release();
511  
        h.promise().state_ = state_;
507  
        h.promise().state_ = state_;
512  
        h.promise().index_ = I;
508  
        h.promise().index_ = I;
513  
        h.promise().ex_ = caller_ex;
509  
        h.promise().ex_ = caller_ex;
514  
        h.promise().stop_token_ = token;
510  
        h.promise().stop_token_ = token;
515  

511  

516  
        coro ch{h};
512  
        coro ch{h};
517  
        state_->runner_handles_[I] = ch;
513  
        state_->runner_handles_[I] = ch;
518  
        caller_ex.dispatch(ch);
514  
        caller_ex.dispatch(ch);
519  
    }
515  
    }
520  
};
516  
};
521  

517  

522  
} // namespace detail
518  
} // namespace detail
523  

519  

524  
/** Wait for the first awaitable to complete.
520  
/** Wait for the first awaitable to complete.
525  

521  

526  
    Races multiple heterogeneous awaitables concurrently and returns when the
522  
    Races multiple heterogeneous awaitables concurrently and returns when the
527  
    first one completes. The result includes the winner's index and a
523  
    first one completes. The result includes the winner's index and a
528  
    deduplicated variant containing the result value.
524  
    deduplicated variant containing the result value.
529  

525  

530  
    @par Suspends
526  
    @par Suspends
531  
    The calling coroutine suspends when co_await is invoked. All awaitables
527  
    The calling coroutine suspends when co_await is invoked. All awaitables
532  
    are launched concurrently and execute in parallel. The coroutine resumes
528  
    are launched concurrently and execute in parallel. The coroutine resumes
533  
    only after all awaitables have completed, even though the winner is
529  
    only after all awaitables have completed, even though the winner is
534  
    determined by the first to finish.
530  
    determined by the first to finish.
535  

531  

536  
    @par Completion Conditions
532  
    @par Completion Conditions
537  
    @li Winner is determined when the first awaitable completes (success or exception)
533  
    @li Winner is determined when the first awaitable completes (success or exception)
538  
    @li Only one task can claim winner status via atomic compare-exchange
534  
    @li Only one task can claim winner status via atomic compare-exchange
539  
    @li Once a winner exists, stop is requested for all remaining siblings
535  
    @li Once a winner exists, stop is requested for all remaining siblings
540  
    @li Parent coroutine resumes only after all siblings acknowledge completion
536  
    @li Parent coroutine resumes only after all siblings acknowledge completion
541  
    @li The winner's result is returned; if the winner threw, the exception is rethrown
537  
    @li The winner's result is returned; if the winner threw, the exception is rethrown
542  

538  

543  
    @par Cancellation Semantics
539  
    @par Cancellation Semantics
544  
    Cancellation is supported via stop_token propagated through the
540  
    Cancellation is supported via stop_token propagated through the
545  
    IoAwaitable protocol:
541  
    IoAwaitable protocol:
546  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
542  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
547  
    @li When the parent's stop token is activated, the stop is forwarded to all children
543  
    @li When the parent's stop token is activated, the stop is forwarded to all children
548  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
544  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
549  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
545  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
550  
    @li Stop requests are cooperative; tasks must check and respond to them
546  
    @li Stop requests are cooperative; tasks must check and respond to them
551  

547  

552  
    @par Concurrency/Overlap
548  
    @par Concurrency/Overlap
553  
    All awaitables are launched concurrently before any can complete.
549  
    All awaitables are launched concurrently before any can complete.
554  
    The launcher iterates through the arguments, starting each task on the
550  
    The launcher iterates through the arguments, starting each task on the
555  
    caller's executor. Tasks may execute in parallel on multi-threaded
551  
    caller's executor. Tasks may execute in parallel on multi-threaded
556  
    executors or interleave on single-threaded executors. There is no
552  
    executors or interleave on single-threaded executors. There is no
557  
    guaranteed ordering of task completion.
553  
    guaranteed ordering of task completion.
558  

554  

559  
    @par Notable Error Conditions
555  
    @par Notable Error Conditions
560  
    @li Winner exception: if the winning task threw, that exception is rethrown
556  
    @li Winner exception: if the winning task threw, that exception is rethrown
561  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
557  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
562  
    @li Cancellation: tasks may complete via cancellation without throwing
558  
    @li Cancellation: tasks may complete via cancellation without throwing
563  

559  

564  
    @par Example
560  
    @par Example
565  
    @code
561  
    @code
566  
    task<void> example() {
562  
    task<void> example() {
567  
        auto [index, result] = co_await when_any(
563  
        auto [index, result] = co_await when_any(
568  
            fetch_from_primary(),   // task<Response>
564  
            fetch_from_primary(),   // task<Response>
569  
            fetch_from_backup()     // task<Response>
565  
            fetch_from_backup()     // task<Response>
570  
        );
566  
        );
571  
        // index is 0 or 1, result holds the winner's Response
567  
        // index is 0 or 1, result holds the winner's Response
572  
        auto response = std::get<Response>(result);
568  
        auto response = std::get<Response>(result);
573  
    }
569  
    }
574  
    @endcode
570  
    @endcode
575  

571  

576  
    @par Example with Heterogeneous Types
572  
    @par Example with Heterogeneous Types
577  
    @code
573  
    @code
578  
    task<void> mixed_types() {
574  
    task<void> mixed_types() {
579  
        auto [index, result] = co_await when_any(
575  
        auto [index, result] = co_await when_any(
580  
            fetch_int(),      // task<int>
576  
            fetch_int(),      // task<int>
581  
            fetch_string()    // task<std::string>
577  
            fetch_string()    // task<std::string>
582  
        );
578  
        );
583  
        if (index == 0)
579  
        if (index == 0)
584  
            std::cout << "Got int: " << std::get<int>(result) << "\n";
580  
            std::cout << "Got int: " << std::get<int>(result) << "\n";
585  
        else
581  
        else
586  
            std::cout << "Got string: " << std::get<std::string>(result) << "\n";
582  
            std::cout << "Got string: " << std::get<std::string>(result) << "\n";
587  
    }
583  
    }
588  
    @endcode
584  
    @endcode
589  

585  

590  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
586  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
591  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
587  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
592  
    @param a0 The first awaitable to race.
588  
    @param a0 The first awaitable to race.
593  
    @param as Additional awaitables to race concurrently.
589  
    @param as Additional awaitables to race concurrently.
594  
    @return A task yielding a pair of (winner_index, result_variant).
590  
    @return A task yielding a pair of (winner_index, result_variant).
595  

591  

596  
    @throws Rethrows the winner's exception if the winning task threw an exception.
592  
    @throws Rethrows the winner's exception if the winning task threw an exception.
597  

593  

598  
    @par Remarks
594  
    @par Remarks
599  
    Awaitables are moved into the coroutine frame; original objects become
595  
    Awaitables are moved into the coroutine frame; original objects become
600  
    empty after the call. When multiple awaitables share the same return type,
596  
    empty after the call. When multiple awaitables share the same return type,
601  
    the variant is deduplicated to contain only unique types. Use the winner
597  
    the variant is deduplicated to contain only unique types. Use the winner
602  
    index to determine which awaitable completed first. Void awaitables
598  
    index to determine which awaitable completed first. Void awaitables
603  
    contribute std::monostate to the variant.
599  
    contribute std::monostate to the variant.
604  

600  

605  
    @see when_all, IoAwaitable
601  
    @see when_all, IoAwaitable
606  
*/
602  
*/
607  
template<IoAwaitable A0, IoAwaitable... As>
603  
template<IoAwaitable A0, IoAwaitable... As>
608  
[[nodiscard]] auto when_any(A0 a0, As... as)
604  
[[nodiscard]] auto when_any(A0 a0, As... as)
609  
    -> task<detail::when_any_result_t<
605  
    -> task<detail::when_any_result_t<
610  
        detail::awaitable_result_t<A0>,
606  
        detail::awaitable_result_t<A0>,
611  
        detail::awaitable_result_t<As>...>>
607  
        detail::awaitable_result_t<As>...>>
612  
{
608  
{
613  
    using result_type = detail::when_any_result_t<
609  
    using result_type = detail::when_any_result_t<
614  
        detail::awaitable_result_t<A0>,
610  
        detail::awaitable_result_t<A0>,
615  
        detail::awaitable_result_t<As>...>;
611  
        detail::awaitable_result_t<As>...>;
616  

612  

617  
    detail::when_any_state<
613  
    detail::when_any_state<
618  
        detail::awaitable_result_t<A0>,
614  
        detail::awaitable_result_t<A0>,
619  
        detail::awaitable_result_t<As>...> state;
615  
        detail::awaitable_result_t<As>...> state;
620  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
616  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
621  

617  

622  
    co_await detail::when_any_launcher<A0, As...>(&awaitable_tuple, &state);
618  
    co_await detail::when_any_launcher<A0, As...>(&awaitable_tuple, &state);
623  

619  

624  
    if(state.core_.winner_exception_)
620  
    if(state.core_.winner_exception_)
625  
        std::rethrow_exception(state.core_.winner_exception_);
621  
        std::rethrow_exception(state.core_.winner_exception_);
626  

622  

627  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
623  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
628  
}
624  
}
629  

625  

630  
/** Concept for ranges of full I/O awaitables.
626  
/** Concept for ranges of full I/O awaitables.
631  

627  

632  
    A range satisfies `IoAwaitableRange` if it is a sized input range
628  
    A range satisfies `IoAwaitableRange` if it is a sized input range
633  
    whose value type satisfies @ref IoAwaitable. This enables when_any
629  
    whose value type satisfies @ref IoAwaitable. This enables when_any
634  
    to accept any container or view of awaitables, not just std::vector.
630  
    to accept any container or view of awaitables, not just std::vector.
635  

631  

636  
    @tparam R The range type.
632  
    @tparam R The range type.
637  

633  

638  
    @par Requirements
634  
    @par Requirements
639  
    @li `R` must satisfy `std::ranges::input_range`
635  
    @li `R` must satisfy `std::ranges::input_range`
640  
    @li `R` must satisfy `std::ranges::sized_range`
636  
    @li `R` must satisfy `std::ranges::sized_range`
641  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
637  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
642  

638  

643  
    @par Syntactic Requirements
639  
    @par Syntactic Requirements
644  
    Given `r` of type `R`:
640  
    Given `r` of type `R`:
645  
    @li `std::ranges::begin(r)` is valid
641  
    @li `std::ranges::begin(r)` is valid
646  
    @li `std::ranges::end(r)` is valid
642  
    @li `std::ranges::end(r)` is valid
647  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
643  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
648  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
644  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
649  

645  

650  
    @par Example
646  
    @par Example
651  
    @code
647  
    @code
652  
    template<IoAwaitableRange R>
648  
    template<IoAwaitableRange R>
653  
    task<void> race_all(R&& awaitables) {
649  
    task<void> race_all(R&& awaitables) {
654  
        auto winner = co_await when_any(std::forward<R>(awaitables));
650  
        auto winner = co_await when_any(std::forward<R>(awaitables));
655  
        // Process winner...
651  
        // Process winner...
656  
    }
652  
    }
657  
    @endcode
653  
    @endcode
658  

654  

659  
    @see when_any, IoAwaitable
655  
    @see when_any, IoAwaitable
660  
*/
656  
*/
661  
template<typename R>
657  
template<typename R>
662  
concept IoAwaitableRange =
658  
concept IoAwaitableRange =
663  
    std::ranges::input_range<R> &&
659  
    std::ranges::input_range<R> &&
664  
    std::ranges::sized_range<R> &&
660  
    std::ranges::sized_range<R> &&
665  
    IoAwaitable<std::ranges::range_value_t<R>>;
661  
    IoAwaitable<std::ranges::range_value_t<R>>;
666  

662  

667  
namespace detail {
663  
namespace detail {
668  

664  

669  
/** Shared state for homogeneous when_any (range overload).
665  
/** Shared state for homogeneous when_any (range overload).
670  

666  

671  
    Uses composition with when_any_core for shared functionality.
667  
    Uses composition with when_any_core for shared functionality.
672  
    Simpler than heterogeneous: optional<T> instead of variant, vector
668  
    Simpler than heterogeneous: optional<T> instead of variant, vector
673  
    instead of array for runner handles.
669  
    instead of array for runner handles.
674  
*/
670  
*/
675  
template<typename T>
671  
template<typename T>
676  
struct when_any_homogeneous_state
672  
struct when_any_homogeneous_state
677  
{
673  
{
678  
    when_any_core core_;
674  
    when_any_core core_;
679  
    std::optional<T> result_;
675  
    std::optional<T> result_;
680  
    std::vector<coro> runner_handles_;
676  
    std::vector<coro> runner_handles_;
681  

677  

682  
    explicit when_any_homogeneous_state(std::size_t count)
678  
    explicit when_any_homogeneous_state(std::size_t count)
683  
        : core_(count)
679  
        : core_(count)
684  
        , runner_handles_(count)
680  
        , runner_handles_(count)
685  
    {
681  
    {
686  
    }
682  
    }
687  

683  

688  
    // Runners self-destruct in final_suspend. No destruction needed here.
684  
    // Runners self-destruct in final_suspend. No destruction needed here.
689  

685  

690  
    /** @pre core_.try_win() returned true. */
686  
    /** @pre core_.try_win() returned true. */
691  
    void set_winner_result(T value)
687  
    void set_winner_result(T value)
692  
        noexcept(std::is_nothrow_move_constructible_v<T>)
688  
        noexcept(std::is_nothrow_move_constructible_v<T>)
693  
    {
689  
    {
694  
        result_.emplace(std::move(value));
690  
        result_.emplace(std::move(value));
695  
    }
691  
    }
696  
};
692  
};
697  

693  

698  
/** Specialization for void tasks (no result storage needed). */
694  
/** Specialization for void tasks (no result storage needed). */
699  
template<>
695  
template<>
700  
struct when_any_homogeneous_state<void>
696  
struct when_any_homogeneous_state<void>
701  
{
697  
{
702  
    when_any_core core_;
698  
    when_any_core core_;
703  
    std::vector<coro> runner_handles_;
699  
    std::vector<coro> runner_handles_;
704  

700  

705  
    explicit when_any_homogeneous_state(std::size_t count)
701  
    explicit when_any_homogeneous_state(std::size_t count)
706  
        : core_(count)
702  
        : core_(count)
707  
        , runner_handles_(count)
703  
        , runner_handles_(count)
708  
    {
704  
    {
709  
    }
705  
    }
710  

706  

711  
    // Runners self-destruct in final_suspend. No destruction needed here.
707  
    // Runners self-destruct in final_suspend. No destruction needed here.
712  

708  

713  
    // No set_winner_result - void tasks have no result to store
709  
    // No set_winner_result - void tasks have no result to store
714  
};
710  
};
715  

711  

716  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
712  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
717  
template<IoAwaitableRange Range>
713  
template<IoAwaitableRange Range>
718  
class when_any_homogeneous_launcher
714  
class when_any_homogeneous_launcher
719  
{
715  
{
720  
    using Awaitable = std::ranges::range_value_t<Range>;
716  
    using Awaitable = std::ranges::range_value_t<Range>;
721  
    using T = awaitable_result_t<Awaitable>;
717  
    using T = awaitable_result_t<Awaitable>;
722  

718  

723  
    Range* range_;
719  
    Range* range_;
724  
    when_any_homogeneous_state<T>* state_;
720  
    when_any_homogeneous_state<T>* state_;
725  

721  

726  
public:
722  
public:
727  
    when_any_homogeneous_launcher(
723  
    when_any_homogeneous_launcher(
728  
        Range* range,
724  
        Range* range,
729  
        when_any_homogeneous_state<T>* state)
725  
        when_any_homogeneous_state<T>* state)
730  
        : range_(range)
726  
        : range_(range)
731  
        , state_(state)
727  
        , state_(state)
732  
    {
728  
    {
733  
    }
729  
    }
734  

730  

735  
    bool await_ready() const noexcept
731  
    bool await_ready() const noexcept
736  
    {
732  
    {
737  
        return std::ranges::empty(*range_);
733  
        return std::ranges::empty(*range_);
738  
    }
734  
    }
739  

735  

740  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
736  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
741  
        destroys this object before await_suspend returns. Must not reference
737  
        destroys this object before await_suspend returns. Must not reference
742  
        `this` after dispatching begins.
738  
        `this` after dispatching begins.
743  

739  

744  
        Two-phase approach:
740  
        Two-phase approach:
745  
        1. Create all runners (safe - no dispatch yet)
741  
        1. Create all runners (safe - no dispatch yet)
746  
        2. Dispatch all runners (any may complete synchronously)
742  
        2. Dispatch all runners (any may complete synchronously)
747  
    */
743  
    */
748  
    template<Executor Ex>
744  
    template<Executor Ex>
749  
    coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
745  
    coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
750  
    {
746  
    {
751  
        state_->core_.continuation_ = continuation;
747  
        state_->core_.continuation_ = continuation;
752  
        state_->core_.caller_ex_ = caller_ex;
748  
        state_->core_.caller_ex_ = caller_ex;
753  

749  

754  
        if(parent_token.stop_possible())
750  
        if(parent_token.stop_possible())
755  
        {
751  
        {
756  
            state_->core_.parent_stop_callback_.emplace(
752  
            state_->core_.parent_stop_callback_.emplace(
757  
                parent_token,
753  
                parent_token,
758  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
754  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
759  

755  

760  
            if(parent_token.stop_requested())
756  
            if(parent_token.stop_requested())
761  
                state_->core_.stop_source_.request_stop();
757  
                state_->core_.stop_source_.request_stop();
762  
        }
758  
        }
763  

759  

764  
        auto token = state_->core_.stop_source_.get_token();
760  
        auto token = state_->core_.stop_source_.get_token();
765  

761  

766  
        // Phase 1: Create all runners without dispatching.
762  
        // Phase 1: Create all runners without dispatching.
767  
        // This iterates over *range_ safely because no runners execute yet.
763  
        // This iterates over *range_ safely because no runners execute yet.
768  
        std::size_t index = 0;
764  
        std::size_t index = 0;
769  
        for(auto&& a : *range_)
765  
        for(auto&& a : *range_)
770  
        {
766  
        {
771  
            auto runner = make_when_any_runner(
767  
            auto runner = make_when_any_runner(
772  
                std::move(a), state_, index);
768  
                std::move(a), state_, index);
773  

769  

774  
            auto h = runner.release();
770  
            auto h = runner.release();
775  
            h.promise().state_ = state_;
771  
            h.promise().state_ = state_;
776  
            h.promise().index_ = index;
772  
            h.promise().index_ = index;
777  
            h.promise().ex_ = caller_ex;
773  
            h.promise().ex_ = caller_ex;
778  
            h.promise().stop_token_ = token;
774  
            h.promise().stop_token_ = token;
779  

775  

780  
            state_->runner_handles_[index] = coro{h};
776  
            state_->runner_handles_[index] = coro{h};
781  
            ++index;
777  
            ++index;
782  
        }
778  
        }
783  

779  

784  
        // Phase 2: Dispatch all runners. Any may complete synchronously.
780  
        // Phase 2: Dispatch all runners. Any may complete synchronously.
785  
        // After last dispatch, state_ and this may be destroyed.
781  
        // After last dispatch, state_ and this may be destroyed.
786  
        // Use raw pointer/count captured before dispatching.
782  
        // Use raw pointer/count captured before dispatching.
787  
        coro* handles = state_->runner_handles_.data();
783  
        coro* handles = state_->runner_handles_.data();
788  
        std::size_t count = state_->runner_handles_.size();
784  
        std::size_t count = state_->runner_handles_.size();
789  
        for(std::size_t i = 0; i < count; ++i)
785  
        for(std::size_t i = 0; i < count; ++i)
790  
            caller_ex.dispatch(handles[i]);
786  
            caller_ex.dispatch(handles[i]);
791  

787  

792  
        return std::noop_coroutine();
788  
        return std::noop_coroutine();
793  
    }
789  
    }
794  

790  

795  
    void await_resume() const noexcept
791  
    void await_resume() const noexcept
796  
    {
792  
    {
797  
    }
793  
    }
798  
};
794  
};
799  

795  

800  
} // namespace detail
796  
} // namespace detail
801  

797  

802  
/** Wait for the first awaitable to complete (range overload).
798  
/** Wait for the first awaitable to complete (range overload).
803  

799  

804  
    Races a range of awaitables with the same result type. Accepts any
800  
    Races a range of awaitables with the same result type. Accepts any
805  
    sized input range of IoAwaitable types, enabling use with arrays,
801  
    sized input range of IoAwaitable types, enabling use with arrays,
806  
    spans, or custom containers.
802  
    spans, or custom containers.
807  

803  

808  
    @par Suspends
804  
    @par Suspends
809  
    The calling coroutine suspends when co_await is invoked. All awaitables
805  
    The calling coroutine suspends when co_await is invoked. All awaitables
810  
    in the range are launched concurrently and execute in parallel. The
806  
    in the range are launched concurrently and execute in parallel. The
811  
    coroutine resumes only after all awaitables have completed, even though
807  
    coroutine resumes only after all awaitables have completed, even though
812  
    the winner is determined by the first to finish.
808  
    the winner is determined by the first to finish.
813  

809  

814  
    @par Completion Conditions
810  
    @par Completion Conditions
815  
    @li Winner is determined when the first awaitable completes (success or exception)
811  
    @li Winner is determined when the first awaitable completes (success or exception)
816  
    @li Only one task can claim winner status via atomic compare-exchange
812  
    @li Only one task can claim winner status via atomic compare-exchange
817  
    @li Once a winner exists, stop is requested for all remaining siblings
813  
    @li Once a winner exists, stop is requested for all remaining siblings
818  
    @li Parent coroutine resumes only after all siblings acknowledge completion
814  
    @li Parent coroutine resumes only after all siblings acknowledge completion
819  
    @li The winner's index and result are returned; if the winner threw, the exception is rethrown
815  
    @li The winner's index and result are returned; if the winner threw, the exception is rethrown
820  

816  

821  
    @par Cancellation Semantics
817  
    @par Cancellation Semantics
822  
    Cancellation is supported via stop_token propagated through the
818  
    Cancellation is supported via stop_token propagated through the
823  
    IoAwaitable protocol:
819  
    IoAwaitable protocol:
824  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
820  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
825  
    @li When the parent's stop token is activated, the stop is forwarded to all children
821  
    @li When the parent's stop token is activated, the stop is forwarded to all children
826  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
822  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
827  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
823  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
828  
    @li Stop requests are cooperative; tasks must check and respond to them
824  
    @li Stop requests are cooperative; tasks must check and respond to them
829  

825  

830  
    @par Concurrency/Overlap
826  
    @par Concurrency/Overlap
831  
    All awaitables are launched concurrently before any can complete.
827  
    All awaitables are launched concurrently before any can complete.
832  
    The launcher iterates through the range, starting each task on the
828  
    The launcher iterates through the range, starting each task on the
833  
    caller's executor. Tasks may execute in parallel on multi-threaded
829  
    caller's executor. Tasks may execute in parallel on multi-threaded
834  
    executors or interleave on single-threaded executors. There is no
830  
    executors or interleave on single-threaded executors. There is no
835  
    guaranteed ordering of task completion.
831  
    guaranteed ordering of task completion.
836  

832  

837  
    @par Notable Error Conditions
833  
    @par Notable Error Conditions
838  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
834  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
839  
    @li Winner exception: if the winning task threw, that exception is rethrown
835  
    @li Winner exception: if the winning task threw, that exception is rethrown
840  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
836  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
841  
    @li Cancellation: tasks may complete via cancellation without throwing
837  
    @li Cancellation: tasks may complete via cancellation without throwing
842  

838  

843  
    @par Example
839  
    @par Example
844  
    @code
840  
    @code
845  
    task<void> example() {
841  
    task<void> example() {
846  
        std::array<task<Response>, 3> requests = {
842  
        std::array<task<Response>, 3> requests = {
847  
            fetch_from_server(0),
843  
            fetch_from_server(0),
848  
            fetch_from_server(1),
844  
            fetch_from_server(1),
849  
            fetch_from_server(2)
845  
            fetch_from_server(2)
850  
        };
846  
        };
851  

847  

852  
        auto [index, response] = co_await when_any(std::move(requests));
848  
        auto [index, response] = co_await when_any(std::move(requests));
853  
    }
849  
    }
854  
    @endcode
850  
    @endcode
855  

851  

856  
    @par Example with Vector
852  
    @par Example with Vector
857  
    @code
853  
    @code
858  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
854  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
859  
        std::vector<task<Response>> requests;
855  
        std::vector<task<Response>> requests;
860  
        for (auto const& server : servers)
856  
        for (auto const& server : servers)
861  
            requests.push_back(fetch_from(server));
857  
            requests.push_back(fetch_from(server));
862  

858  

863  
        auto [index, response] = co_await when_any(std::move(requests));
859  
        auto [index, response] = co_await when_any(std::move(requests));
864  
        co_return response;
860  
        co_return response;
865  
    }
861  
    }
866  
    @endcode
862  
    @endcode
867  

863  

868  
    @tparam R Range type satisfying IoAwaitableRange.
864  
    @tparam R Range type satisfying IoAwaitableRange.
869  
    @param awaitables Range of awaitables to race concurrently (must not be empty).
865  
    @param awaitables Range of awaitables to race concurrently (must not be empty).
870  
    @return A task yielding a pair of (winner_index, result).
866  
    @return A task yielding a pair of (winner_index, result).
871  

867  

872  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
868  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
873  
    @throws Rethrows the winner's exception if the winning task threw an exception.
869  
    @throws Rethrows the winner's exception if the winning task threw an exception.
874  

870  

875  
    @par Remarks
871  
    @par Remarks
876  
    Elements are moved from the range; for lvalue ranges, the original
872  
    Elements are moved from the range; for lvalue ranges, the original
877  
    container will have moved-from elements after this call. The range
873  
    container will have moved-from elements after this call. The range
878  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
874  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
879  
    the variadic overload, no variant wrapper is needed since all tasks
875  
    the variadic overload, no variant wrapper is needed since all tasks
880  
    share the same return type.
876  
    share the same return type.
881  

877  

882  
    @see when_any, IoAwaitableRange
878  
    @see when_any, IoAwaitableRange
883  
*/
879  
*/
884  
template<IoAwaitableRange R>
880  
template<IoAwaitableRange R>
885  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
881  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
886  
[[nodiscard]] auto when_any(R&& awaitables)
882  
[[nodiscard]] auto when_any(R&& awaitables)
887  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
883  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
888  
{
884  
{
889  
    using Awaitable = std::ranges::range_value_t<R>;
885  
    using Awaitable = std::ranges::range_value_t<R>;
890  
    using T = detail::awaitable_result_t<Awaitable>;
886  
    using T = detail::awaitable_result_t<Awaitable>;
891  
    using result_type = std::pair<std::size_t, T>;
887  
    using result_type = std::pair<std::size_t, T>;
892  
    using OwnedRange = std::remove_cvref_t<R>;
888  
    using OwnedRange = std::remove_cvref_t<R>;
893  

889  

894  
    auto count = std::ranges::size(awaitables);
890  
    auto count = std::ranges::size(awaitables);
895  
    if(count == 0)
891  
    if(count == 0)
896  
        throw std::invalid_argument("when_any requires at least one awaitable");
892  
        throw std::invalid_argument("when_any requires at least one awaitable");
897  

893  

898  
    // Move/copy range onto coroutine frame to ensure lifetime
894  
    // Move/copy range onto coroutine frame to ensure lifetime
899  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
895  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
900  

896  

901  
    detail::when_any_homogeneous_state<T> state(count);
897  
    detail::when_any_homogeneous_state<T> state(count);
902  

898  

903  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
899  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
904  

900  

905  
    if(state.core_.winner_exception_)
901  
    if(state.core_.winner_exception_)
906  
        std::rethrow_exception(state.core_.winner_exception_);
902  
        std::rethrow_exception(state.core_.winner_exception_);
907  

903  

908  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
904  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
909  
}
905  
}
910  

906  

911  
/** Wait for the first awaitable to complete (void range overload).
907  
/** Wait for the first awaitable to complete (void range overload).
912  

908  

913  
    Races a range of void-returning awaitables. Since void awaitables have
909  
    Races a range of void-returning awaitables. Since void awaitables have
914  
    no result value, only the winner's index is returned.
910  
    no result value, only the winner's index is returned.
915  

911  

916  
    @par Suspends
912  
    @par Suspends
917  
    The calling coroutine suspends when co_await is invoked. All awaitables
913  
    The calling coroutine suspends when co_await is invoked. All awaitables
918  
    in the range are launched concurrently and execute in parallel. The
914  
    in the range are launched concurrently and execute in parallel. The
919  
    coroutine resumes only after all awaitables have completed, even though
915  
    coroutine resumes only after all awaitables have completed, even though
920  
    the winner is determined by the first to finish.
916  
    the winner is determined by the first to finish.
921  

917  

922  
    @par Completion Conditions
918  
    @par Completion Conditions
923  
    @li Winner is determined when the first awaitable completes (success or exception)
919  
    @li Winner is determined when the first awaitable completes (success or exception)
924  
    @li Only one task can claim winner status via atomic compare-exchange
920  
    @li Only one task can claim winner status via atomic compare-exchange
925  
    @li Once a winner exists, stop is requested for all remaining siblings
921  
    @li Once a winner exists, stop is requested for all remaining siblings
926  
    @li Parent coroutine resumes only after all siblings acknowledge completion
922  
    @li Parent coroutine resumes only after all siblings acknowledge completion
927  
    @li The winner's index is returned; if the winner threw, the exception is rethrown
923  
    @li The winner's index is returned; if the winner threw, the exception is rethrown
928  

924  

929  
    @par Cancellation Semantics
925  
    @par Cancellation Semantics
930  
    Cancellation is supported via stop_token propagated through the
926  
    Cancellation is supported via stop_token propagated through the
931  
    IoAwaitable protocol:
927  
    IoAwaitable protocol:
932  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
928  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
933  
    @li When the parent's stop token is activated, the stop is forwarded to all children
929  
    @li When the parent's stop token is activated, the stop is forwarded to all children
934  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
930  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
935  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
931  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
936  
    @li Stop requests are cooperative; tasks must check and respond to them
932  
    @li Stop requests are cooperative; tasks must check and respond to them
937  

933  

938  
    @par Concurrency/Overlap
934  
    @par Concurrency/Overlap
939  
    All awaitables are launched concurrently before any can complete.
935  
    All awaitables are launched concurrently before any can complete.
940  
    The launcher iterates through the range, starting each task on the
936  
    The launcher iterates through the range, starting each task on the
941  
    caller's executor. Tasks may execute in parallel on multi-threaded
937  
    caller's executor. Tasks may execute in parallel on multi-threaded
942  
    executors or interleave on single-threaded executors. There is no
938  
    executors or interleave on single-threaded executors. There is no
943  
    guaranteed ordering of task completion.
939  
    guaranteed ordering of task completion.
944  

940  

945  
    @par Notable Error Conditions
941  
    @par Notable Error Conditions
946  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
942  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
947  
    @li Winner exception: if the winning task threw, that exception is rethrown
943  
    @li Winner exception: if the winning task threw, that exception is rethrown
948  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
944  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
949  
    @li Cancellation: tasks may complete via cancellation without throwing
945  
    @li Cancellation: tasks may complete via cancellation without throwing
950  

946  

951  
    @par Example
947  
    @par Example
952  
    @code
948  
    @code
953  
    task<void> example() {
949  
    task<void> example() {
954  
        std::vector<task<void>> tasks;
950  
        std::vector<task<void>> tasks;
955  
        for (int i = 0; i < 5; ++i)
951  
        for (int i = 0; i < 5; ++i)
956  
            tasks.push_back(background_work(i));
952  
            tasks.push_back(background_work(i));
957  

953  

958  
        std::size_t winner = co_await when_any(std::move(tasks));
954  
        std::size_t winner = co_await when_any(std::move(tasks));
959  
        // winner is the index of the first task to complete
955  
        // winner is the index of the first task to complete
960  
    }
956  
    }
961  
    @endcode
957  
    @endcode
962  

958  

963  
    @par Example with Timeout
959  
    @par Example with Timeout
964  
    @code
960  
    @code
965  
    task<void> with_timeout() {
961  
    task<void> with_timeout() {
966  
        std::vector<task<void>> tasks;
962  
        std::vector<task<void>> tasks;
967  
        tasks.push_back(long_running_operation());
963  
        tasks.push_back(long_running_operation());
968  
        tasks.push_back(delay(std::chrono::seconds(5)));
964  
        tasks.push_back(delay(std::chrono::seconds(5)));
969  

965  

970  
        std::size_t winner = co_await when_any(std::move(tasks));
966  
        std::size_t winner = co_await when_any(std::move(tasks));
971  
        if (winner == 1) {
967  
        if (winner == 1) {
972  
            // Timeout occurred
968  
            // Timeout occurred
973  
        }
969  
        }
974  
    }
970  
    }
975  
    @endcode
971  
    @endcode
976  

972  

977  
    @tparam R Range type satisfying IoAwaitableRange with void result.
973  
    @tparam R Range type satisfying IoAwaitableRange with void result.
978  
    @param awaitables Range of void awaitables to race concurrently (must not be empty).
974  
    @param awaitables Range of void awaitables to race concurrently (must not be empty).
979  
    @return A task yielding the winner's index (zero-based).
975  
    @return A task yielding the winner's index (zero-based).
980  

976  

981  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
977  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
982  
    @throws Rethrows the winner's exception if the winning task threw an exception.
978  
    @throws Rethrows the winner's exception if the winning task threw an exception.
983  

979  

984  
    @par Remarks
980  
    @par Remarks
985  
    Elements are moved from the range; for lvalue ranges, the original
981  
    Elements are moved from the range; for lvalue ranges, the original
986  
    container will have moved-from elements after this call. The range
982  
    container will have moved-from elements after this call. The range
987  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
983  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
988  
    the non-void overload, no result storage is needed since void tasks
984  
    the non-void overload, no result storage is needed since void tasks
989  
    produce no value.
985  
    produce no value.
990  

986  

991  
    @see when_any, IoAwaitableRange
987  
    @see when_any, IoAwaitableRange
992  
*/
988  
*/
993  
template<IoAwaitableRange R>
989  
template<IoAwaitableRange R>
994  
    requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
990  
    requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
995  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
991  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
996  
{
992  
{
997  
    using OwnedRange = std::remove_cvref_t<R>;
993  
    using OwnedRange = std::remove_cvref_t<R>;
998  

994  

999  
    auto count = std::ranges::size(awaitables);
995  
    auto count = std::ranges::size(awaitables);
1000  
    if(count == 0)
996  
    if(count == 0)
1001  
        throw std::invalid_argument("when_any requires at least one awaitable");
997  
        throw std::invalid_argument("when_any requires at least one awaitable");
1002  

998  

1003  
    // Move/copy range onto coroutine frame to ensure lifetime
999  
    // Move/copy range onto coroutine frame to ensure lifetime
1004  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
1000  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
1005  

1001  

1006  
    detail::when_any_homogeneous_state<void> state(count);
1002  
    detail::when_any_homogeneous_state<void> state(count);
1007  

1003  

1008  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
1004  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
1009  

1005  

1010  
    if(state.core_.winner_exception_)
1006  
    if(state.core_.winner_exception_)
1011  
        std::rethrow_exception(state.core_.winner_exception_);
1007  
        std::rethrow_exception(state.core_.winner_exception_);
1012  

1008  

1013  
    co_return state.core_.winner_index_;
1009  
    co_return state.core_.winner_index_;
1014  
}
1010  
}
1015  

1011  

1016  
} // namespace capy
1012  
} // namespace capy
1017  
} // namespace boost
1013  
} // namespace boost
1018  

1014  

1019  
#endif
1015  
#endif