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 <coroutine>
16  
#include <coroutine>
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/ex/io_env.hpp>
19  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/task.hpp>
20  
#include <boost/capy/task.hpp>
21  

21  

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

34  

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

38  

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

44  

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

48  

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

52  

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

57  

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

64  

65 -
   TYPE DEDUPLICATION:
65 +
   POSITIONAL VARIANT:
66  
   -------------------
66  
   -------------------
67 -
   std::variant requires unique alternative types. Since when_any can race
67 +
   The variadic overload returns a std::variant with one alternative per
68 -
   tasks with identical return types (e.g., three task<int>), we must
68 +
   input task, preserving positional correspondence. Use .index() on
69 -
   deduplicate types before constructing the variant.
69 +
   the variant to identify which task won.
70  

70  

71  
   Example: when_any(task<int>, task<string>, task<int>)
71  
   Example: when_any(task<int>, task<string>, task<int>)
72  
     - Raw types after void->monostate: int, string, int
72  
     - Raw types after void->monostate: int, string, int
73 -
     - Deduplicated variant: std::variant<int, string>
73 +
     - Result variant: std::variant<int, string, int>
74 -
     - Return: pair<size_t, variant<int, string>>
74 +
     - variant.index() tells you which task won (0, 1, or 2)
75 -

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

75  

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

80  

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

84  

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

91  

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

101  

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

107  

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

114  

118  
namespace boost {
115  
namespace boost {
119  
namespace capy {
116  
namespace capy {
120  

117  

121  
namespace detail {
118  
namespace detail {
122  

119  

123  
/** Convert void to monostate for variant storage.
120  
/** Convert void to monostate for variant storage.
124  

121  

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

125  

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

130  

134 -
// Type deduplication: std::variant requires unique alternative types.
131 +
// Result variant: one alternative per task, preserving positional
135 -
// Fold left over the type list, appending each type only if not already present.
132 +
// correspondence. Use .index() to identify which task won.
136 -
template<typename Variant, typename T>
133 +
// void results become monostate.
137 -
struct variant_append_if_unique;
 
138 -

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

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

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

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

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

 
170 -
// Result: (winner_index, deduplicated_variant). Use index to disambiguate
 
171 -
// when multiple tasks share the same return type.
 
172  
template<typename T0, typename... Ts>
134  
template<typename T0, typename... Ts>
173 -
using when_any_result_t = std::pair<std::size_t, unique_variant_t<T0, Ts...>>;
135 +
using when_any_variant_t = std::variant<void_to_monostate_t<T0>, void_to_monostate_t<Ts>...>;
174  

136  

175  
/** Core shared state for when_any operations.
137  
/** Core shared state for when_any operations.
176  

138  

177  
    Contains all members and methods common to both heterogeneous (variadic)
139  
    Contains all members and methods common to both heterogeneous (variadic)
178  
    and homogeneous (range) when_any implementations. State classes embed
140  
    and homogeneous (range) when_any implementations. State classes embed
179  
    this via composition to avoid CRTP destructor ordering issues.
141  
    this via composition to avoid CRTP destructor ordering issues.
180  

142  

181  
    @par Thread Safety
143  
    @par Thread Safety
182  
    Atomic operations protect winner selection and completion count.
144  
    Atomic operations protect winner selection and completion count.
183  
*/
145  
*/
184  
struct when_any_core
146  
struct when_any_core
185  
{
147  
{
186  
    std::atomic<std::size_t> remaining_count_;
148  
    std::atomic<std::size_t> remaining_count_;
187  
    std::size_t winner_index_{0};
149  
    std::size_t winner_index_{0};
188  
    std::exception_ptr winner_exception_;
150  
    std::exception_ptr winner_exception_;
189  
    std::stop_source stop_source_;
151  
    std::stop_source stop_source_;
190  

152  

191  
    // Bridges parent's stop token to our stop_source
153  
    // Bridges parent's stop token to our stop_source
192  
    struct stop_callback_fn
154  
    struct stop_callback_fn
193  
    {
155  
    {
194  
        std::stop_source* source_;
156  
        std::stop_source* source_;
195  
        void operator()() const noexcept { source_->request_stop(); }
157  
        void operator()() const noexcept { source_->request_stop(); }
196  
    };
158  
    };
197  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
159  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
198  
    std::optional<stop_callback_t> parent_stop_callback_;
160  
    std::optional<stop_callback_t> parent_stop_callback_;
199  

161  

200  
    std::coroutine_handle<> continuation_;
162  
    std::coroutine_handle<> continuation_;
201  
    io_env const* caller_env_ = nullptr;
163  
    io_env const* caller_env_ = nullptr;
202  

164  

203  
    // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
165  
    // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
204  
    std::atomic<bool> has_winner_{false};
166  
    std::atomic<bool> has_winner_{false};
205  

167  

206  
    explicit when_any_core(std::size_t count) noexcept
168  
    explicit when_any_core(std::size_t count) noexcept
207  
        : remaining_count_(count)
169  
        : remaining_count_(count)
208  
    {
170  
    {
209  
    }
171  
    }
210  

172  

211  
    /** Atomically claim winner status; exactly one task succeeds. */
173  
    /** Atomically claim winner status; exactly one task succeeds. */
212  
    bool try_win(std::size_t index) noexcept
174  
    bool try_win(std::size_t index) noexcept
213  
    {
175  
    {
214  
        bool expected = false;
176  
        bool expected = false;
215  
        if(has_winner_.compare_exchange_strong(
177  
        if(has_winner_.compare_exchange_strong(
216  
            expected, true, std::memory_order_acq_rel))
178  
            expected, true, std::memory_order_acq_rel))
217  
        {
179  
        {
218  
            winner_index_ = index;
180  
            winner_index_ = index;
219  
            stop_source_.request_stop();
181  
            stop_source_.request_stop();
220  
            return true;
182  
            return true;
221  
        }
183  
        }
222  
        return false;
184  
        return false;
223  
    }
185  
    }
224  

186  

225  
    /** @pre try_win() returned true. */
187  
    /** @pre try_win() returned true. */
226  
    void set_winner_exception(std::exception_ptr ep) noexcept
188  
    void set_winner_exception(std::exception_ptr ep) noexcept
227  
    {
189  
    {
228  
        winner_exception_ = ep;
190  
        winner_exception_ = ep;
229  
    }
191  
    }
230  

192  

231  
    // Runners signal completion directly via final_suspend; no member function needed.
193  
    // Runners signal completion directly via final_suspend; no member function needed.
232  
};
194  
};
233  

195  

234  
/** Shared state for heterogeneous when_any operation.
196  
/** Shared state for heterogeneous when_any operation.
235  

197  

236  
    Coordinates winner selection, result storage, and completion tracking
198  
    Coordinates winner selection, result storage, and completion tracking
237  
    for all child tasks in a when_any operation. Uses composition with
199  
    for all child tasks in a when_any operation. Uses composition with
238  
    when_any_core for shared functionality.
200  
    when_any_core for shared functionality.
239  

201  

240  
    @par Lifetime
202  
    @par Lifetime
241  
    Allocated on the parent coroutine's frame, outlives all runners.
203  
    Allocated on the parent coroutine's frame, outlives all runners.
242  

204  

243  
    @tparam T0 First task's result type.
205  
    @tparam T0 First task's result type.
244  
    @tparam Ts Remaining tasks' result types.
206  
    @tparam Ts Remaining tasks' result types.
245  
*/
207  
*/
246  
template<typename T0, typename... Ts>
208  
template<typename T0, typename... Ts>
247  
struct when_any_state
209  
struct when_any_state
248  
{
210  
{
249  
    static constexpr std::size_t task_count = 1 + sizeof...(Ts);
211  
    static constexpr std::size_t task_count = 1 + sizeof...(Ts);
250 -
    using variant_type = unique_variant_t<T0, Ts...>;
212 +
    using variant_type = when_any_variant_t<T0, Ts...>;
251  

213  

252  
    when_any_core core_;
214  
    when_any_core core_;
253  
    std::optional<variant_type> result_;
215  
    std::optional<variant_type> result_;
254  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
216  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
255  

217  

256  
    when_any_state()
218  
    when_any_state()
257  
        : core_(task_count)
219  
        : core_(task_count)
258  
    {
220  
    {
259  
    }
221  
    }
260  

222  

261  
    // Runners self-destruct in final_suspend. No destruction needed here.
223  
    // Runners self-destruct in final_suspend. No destruction needed here.
262  

224  

263  
    /** @pre core_.try_win() returned true.
225  
    /** @pre core_.try_win() returned true.
264 -
        @note Uses in_place_type (not index) because variant is deduplicated.
226 +
        @note Uses in_place_index (not type) for positional variant access.
265  
    */
227  
    */
266 -
    template<typename T>
228 +
    template<std::size_t I, typename T>
267  
    void set_winner_result(T value)
229  
    void set_winner_result(T value)
268  
        noexcept(std::is_nothrow_move_constructible_v<T>)
230  
        noexcept(std::is_nothrow_move_constructible_v<T>)
269  
    {
231  
    {
270 -
        result_.emplace(std::in_place_type<T>, std::move(value));
232 +
        result_.emplace(std::in_place_index<I>, std::move(value));
271  
    }
233  
    }
272  

234  

273  
    /** @pre core_.try_win() returned true. */
235  
    /** @pre core_.try_win() returned true. */
 
236 +
    template<std::size_t I>
274  
    void set_winner_void() noexcept
237  
    void set_winner_void() noexcept
275  
    {
238  
    {
276 -
        result_.emplace(std::in_place_type<std::monostate>, std::monostate{});
239 +
        result_.emplace(std::in_place_index<I>, std::monostate{});
277  
    }
240  
    }
278  
};
241  
};
279  

242  

280  
/** Wrapper coroutine that runs a single child task for when_any.
243  
/** Wrapper coroutine that runs a single child task for when_any.
281  

244  

282  
    Propagates executor/stop_token to the child, attempts to claim winner
245  
    Propagates executor/stop_token to the child, attempts to claim winner
283  
    status on completion, and signals completion for cleanup coordination.
246  
    status on completion, and signals completion for cleanup coordination.
284  

247  

285  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
248  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
286  
*/
249  
*/
287  
template<typename StateType>
250  
template<typename StateType>
288  
struct when_any_runner
251  
struct when_any_runner
289  
{
252  
{
290  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
253  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
291  
    {
254  
    {
292  
        StateType* state_ = nullptr;
255  
        StateType* state_ = nullptr;
293  
        std::size_t index_ = 0;
256  
        std::size_t index_ = 0;
294  
        io_env env_;
257  
        io_env env_;
295  

258  

296  
        when_any_runner get_return_object() noexcept
259  
        when_any_runner get_return_object() noexcept
297  
        {
260  
        {
298  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
261  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
299  
        }
262  
        }
300  

263  

301  
        // Starts suspended; launcher sets up state/ex/token then resumes
264  
        // Starts suspended; launcher sets up state/ex/token then resumes
302  
        std::suspend_always initial_suspend() noexcept
265  
        std::suspend_always initial_suspend() noexcept
303  
        {
266  
        {
304  
            return {};
267  
            return {};
305  
        }
268  
        }
306  

269  

307  
        auto final_suspend() noexcept
270  
        auto final_suspend() noexcept
308  
        {
271  
        {
309  
            struct awaiter
272  
            struct awaiter
310  
            {
273  
            {
311  
                promise_type* p_;
274  
                promise_type* p_;
312  
                bool await_ready() const noexcept { return false; }
275  
                bool await_ready() const noexcept { return false; }
313  
                std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept
276  
                std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept
314  
                {
277  
                {
315  
                    // Extract everything needed before self-destruction.
278  
                    // Extract everything needed before self-destruction.
316  
                    auto& core = p_->state_->core_;
279  
                    auto& core = p_->state_->core_;
317  
                    auto* counter = &core.remaining_count_;
280  
                    auto* counter = &core.remaining_count_;
318  
                    auto* caller_env = core.caller_env_;
281  
                    auto* caller_env = core.caller_env_;
319  
                    auto cont = core.continuation_;
282  
                    auto cont = core.continuation_;
320  

283  

321  
                    h.destroy();
284  
                    h.destroy();
322  

285  

323  
                    // If last runner, dispatch parent for symmetric transfer.
286  
                    // If last runner, dispatch parent for symmetric transfer.
324  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
287  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
325  
                    if(remaining == 1)
288  
                    if(remaining == 1)
326  
                        return caller_env->executor.dispatch(cont);
289  
                        return caller_env->executor.dispatch(cont);
327  
                    return std::noop_coroutine();
290  
                    return std::noop_coroutine();
328  
                }
291  
                }
329  
                void await_resume() const noexcept {}
292  
                void await_resume() const noexcept {}
330  
            };
293  
            };
331  
            return awaiter{this};
294  
            return awaiter{this};
332  
        }
295  
        }
333  

296  

334  
        void return_void() noexcept {}
297  
        void return_void() noexcept {}
335  

298  

336  
        // Exceptions are valid completions in when_any (unlike when_all)
299  
        // Exceptions are valid completions in when_any (unlike when_all)
337  
        void unhandled_exception()
300  
        void unhandled_exception()
338  
        {
301  
        {
339  
            if(state_->core_.try_win(index_))
302  
            if(state_->core_.try_win(index_))
340  
                state_->core_.set_winner_exception(std::current_exception());
303  
                state_->core_.set_winner_exception(std::current_exception());
341  
        }
304  
        }
342  

305  

343  
        /** Injects executor and stop token into child awaitables. */
306  
        /** Injects executor and stop token into child awaitables. */
344  
        template<class Awaitable>
307  
        template<class Awaitable>
345  
        struct transform_awaiter
308  
        struct transform_awaiter
346  
        {
309  
        {
347  
            std::decay_t<Awaitable> a_;
310  
            std::decay_t<Awaitable> a_;
348  
            promise_type* p_;
311  
            promise_type* p_;
349  

312  

350  
            bool await_ready() { return a_.await_ready(); }
313  
            bool await_ready() { return a_.await_ready(); }
351  
            auto await_resume() { return a_.await_resume(); }
314  
            auto await_resume() { return a_.await_resume(); }
352  

315  

353  
            template<class Promise>
316  
            template<class Promise>
354  
            auto await_suspend(std::coroutine_handle<Promise> h)
317  
            auto await_suspend(std::coroutine_handle<Promise> h)
355  
            {
318  
            {
356  
#ifdef _MSC_VER
319  
#ifdef _MSC_VER
357  
                using R = decltype(a_.await_suspend(h, &p_->env_));
320  
                using R = decltype(a_.await_suspend(h, &p_->env_));
358  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
321  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
359  
                    a_.await_suspend(h, &p_->env_).resume();
322  
                    a_.await_suspend(h, &p_->env_).resume();
360  
                else
323  
                else
361  
                    return a_.await_suspend(h, &p_->env_);
324  
                    return a_.await_suspend(h, &p_->env_);
362  
#else
325  
#else
363  
                return a_.await_suspend(h, &p_->env_);
326  
                return a_.await_suspend(h, &p_->env_);
364  
#endif
327  
#endif
365  
            }
328  
            }
366  
        };
329  
        };
367  

330  

368  
        template<class Awaitable>
331  
        template<class Awaitable>
369  
        auto await_transform(Awaitable&& a)
332  
        auto await_transform(Awaitable&& a)
370  
        {
333  
        {
371  
            using A = std::decay_t<Awaitable>;
334  
            using A = std::decay_t<Awaitable>;
372  
            if constexpr (IoAwaitable<A>)
335  
            if constexpr (IoAwaitable<A>)
373  
            {
336  
            {
374  
                return transform_awaiter<Awaitable>{
337  
                return transform_awaiter<Awaitable>{
375  
                    std::forward<Awaitable>(a), this};
338  
                    std::forward<Awaitable>(a), this};
376  
            }
339  
            }
377  
            else
340  
            else
378  
            {
341  
            {
379  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
342  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
380  
            }
343  
            }
381  
        }
344  
        }
382  
    };
345  
    };
383  

346  

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

348  

386  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
349  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
387  
        : h_(h)
350  
        : h_(h)
388  
    {
351  
    {
389  
    }
352  
    }
390  

353  

391  
    // Enable move for all clang versions - some versions need it
354  
    // 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)) {}
355  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
393  

356  

394  
    // Non-copyable
357  
    // Non-copyable
395  
    when_any_runner(when_any_runner const&) = delete;
358  
    when_any_runner(when_any_runner const&) = delete;
396  
    when_any_runner& operator=(when_any_runner const&) = delete;
359  
    when_any_runner& operator=(when_any_runner const&) = delete;
397  
    when_any_runner& operator=(when_any_runner&&) = delete;
360  
    when_any_runner& operator=(when_any_runner&&) = delete;
398  

361  

399  
    auto release() noexcept
362  
    auto release() noexcept
400  
    {
363  
    {
401  
        return std::exchange(h_, nullptr);
364  
        return std::exchange(h_, nullptr);
402  
    }
365  
    }
403  
};
366  
};
404  

367  

405 -
/** Wraps a child awaitable, attempts to claim winner on completion.
368 +
/** Indexed overload for heterogeneous when_any (compile-time index).
 
369 +

 
370 +
    Uses compile-time index I for variant construction via in_place_index.
 
371 +
    Called from when_any_launcher::launch_one<I>().
 
372 +
*/
 
373 +
template<std::size_t I, IoAwaitable Awaitable, typename StateType>
 
374 +
when_any_runner<StateType>
 
375 +
make_when_any_runner(Awaitable inner, StateType* state)
 
376 +
{
 
377 +
    using T = awaitable_result_t<Awaitable>;
 
378 +
    if constexpr (std::is_void_v<T>)
 
379 +
    {
 
380 +
        co_await std::move(inner);
 
381 +
        if(state->core_.try_win(I))
 
382 +
            state->template set_winner_void<I>();
 
383 +
    }
 
384 +
    else
 
385 +
    {
 
386 +
        auto result = co_await std::move(inner);
 
387 +
        if(state->core_.try_win(I))
 
388 +
        {
 
389 +
            try
 
390 +
            {
 
391 +
                state->template set_winner_result<I>(std::move(result));
 
392 +
            }
 
393 +
            catch(...)
 
394 +
            {
 
395 +
                state->core_.set_winner_exception(std::current_exception());
 
396 +
            }
 
397 +
        }
 
398 +
    }
 
399 +
}
 
400 +

 
401 +
/** Runtime-index overload for homogeneous when_any (range path).
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 -
            // Heterogeneous void tasks store monostate in the variant
 
422  
        {
417  
        {
423  
            if constexpr (requires { state->set_winner_void(); })
418  
            if constexpr (requires { state->set_winner_void(); })
424 -
            // Homogeneous void tasks have no result to store
 
425  
                state->set_winner_void();
419  
                state->set_winner_void();
426  
        }
420  
        }
427  
    }
421  
    }
428  
    else
422  
    else
429  
    {
423  
    {
430  
        auto result = co_await std::move(inner);
424  
        auto result = co_await std::move(inner);
431  
        if(state->core_.try_win(index))
425  
        if(state->core_.try_win(index))
432 -
            // Defensive: move should not throw (already moved once), but we
 
433 -
            // catch just in case since an uncaught exception would be devastating.
 
434  
        {
426  
        {
435  
            try
427  
            try
436  
            {
428  
            {
437  
                state->set_winner_result(std::move(result));
429  
                state->set_winner_result(std::move(result));
438  
            }
430  
            }
439  
            catch(...)
431  
            catch(...)
440  
            {
432  
            {
441  
                state->core_.set_winner_exception(std::current_exception());
433  
                state->core_.set_winner_exception(std::current_exception());
442  
            }
434  
            }
443  
        }
435  
        }
444  
    }
436  
    }
445  
}
437  
}
446  

438  

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

444  

453  
    std::tuple<Awaitables...>* tasks_;
445  
    std::tuple<Awaitables...>* tasks_;
454  
    state_type* state_;
446  
    state_type* state_;
455  

447  

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

456  

465  
    bool await_ready() const noexcept
457  
    bool await_ready() const noexcept
466  
    {
458  
    {
467  
        return sizeof...(Awaitables) == 0;
459  
        return sizeof...(Awaitables) == 0;
468  
    }
460  
    }
469  

461  

470  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
462  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
471  
        destroys this object before await_suspend returns. Must not reference
463  
        destroys this object before await_suspend returns. Must not reference
472  
        `this` after the final launch_one call.
464  
        `this` after the final launch_one call.
473  
    */
465  
    */
474  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
466  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
475  
    {
467  
    {
476  
        state_->core_.continuation_ = continuation;
468  
        state_->core_.continuation_ = continuation;
477  
        state_->core_.caller_env_ = caller_env;
469  
        state_->core_.caller_env_ = caller_env;
478  

470  

479  
        if(caller_env->stop_token.stop_possible())
471  
        if(caller_env->stop_token.stop_possible())
480  
        {
472  
        {
481  
            state_->core_.parent_stop_callback_.emplace(
473  
            state_->core_.parent_stop_callback_.emplace(
482  
                caller_env->stop_token,
474  
                caller_env->stop_token,
483  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
475  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
484  

476  

485  
            if(caller_env->stop_token.stop_requested())
477  
            if(caller_env->stop_token.stop_requested())
486  
                state_->core_.stop_source_.request_stop();
478  
                state_->core_.stop_source_.request_stop();
487  
        }
479  
        }
488  

480  

489  
        auto token = state_->core_.stop_source_.get_token();
481  
        auto token = state_->core_.stop_source_.get_token();
490  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
482  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
491  
            (..., launch_one<Is>(caller_env->executor, token));
483  
            (..., launch_one<Is>(caller_env->executor, token));
492  
        }(std::index_sequence_for<Awaitables...>{});
484  
        }(std::index_sequence_for<Awaitables...>{});
493  

485  

494  
        return std::noop_coroutine();
486  
        return std::noop_coroutine();
495  
    }
487  
    }
496  

488  

497  
    void await_resume() const noexcept
489  
    void await_resume() const noexcept
498  
    {
490  
    {
499  
    }
491  
    }
500  

492  

501  
private:
493  
private:
502  
    /** @pre Ex::dispatch() and std::coroutine_handle<>::resume() must not throw (handle may leak). */
494  
    /** @pre Ex::dispatch() and std::coroutine_handle<>::resume() must not throw (handle may leak). */
503  
    template<std::size_t I>
495  
    template<std::size_t I>
504  
    void launch_one(executor_ref caller_ex, std::stop_token token)
496  
    void launch_one(executor_ref caller_ex, std::stop_token token)
505  
    {
497  
    {
506 -
        auto runner = make_when_any_runner(
498 +
        auto runner = make_when_any_runner<I>(
507 -
            std::move(std::get<I>(*tasks_)), state_, I);
499 +
            std::move(std::get<I>(*tasks_)), state_);
508  

500  

509  
        auto h = runner.release();
501  
        auto h = runner.release();
510  
        h.promise().state_ = state_;
502  
        h.promise().state_ = state_;
511  
        h.promise().index_ = I;
503  
        h.promise().index_ = I;
512  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
504  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
513  

505  

514  
        std::coroutine_handle<> ch{h};
506  
        std::coroutine_handle<> ch{h};
515  
        state_->runner_handles_[I] = ch;
507  
        state_->runner_handles_[I] = ch;
516  
        caller_ex.post(ch);
508  
        caller_ex.post(ch);
517  
    }
509  
    }
518  
};
510  
};
519  

511  

520  
} // namespace detail
512  
} // namespace detail
521  

513  

522  
/** Wait for the first awaitable to complete.
514  
/** Wait for the first awaitable to complete.
523  

515  

524  
    Races multiple heterogeneous awaitables concurrently and returns when the
516  
    Races multiple heterogeneous awaitables concurrently and returns when the
525 -
    first one completes. The result includes the winner's index and a
517 +
    first one completes. The result is a variant with one alternative per
526 -
    deduplicated variant containing the result value.
518 +
    input task, preserving positional correspondence.
527  

519  

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

525  

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

532  

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

541  

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

548  

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

553  

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

 
574 -
    @par Example with Heterogeneous Types
 
575 -
    @code
 
576 -
    task<void> mixed_types() {
 
577 -
        auto [index, result] = co_await when_any(
 
578  
            fetch_int(),      // task<int>
558  
            fetch_int(),      // task<int>
579  
            fetch_string()    // task<std::string>
559  
            fetch_string()    // task<std::string>
580  
        );
560  
        );
581 -
        if (index == 0)
561 +
        // result.index() is 0 or 1
582 -
            std::cout << "Got int: " << std::get<int>(result) << "\n";
562 +
        if (result.index() == 0)
 
563 +
            std::cout << "Got int: " << std::get<0>(result) << "\n";
583  
        else
564  
        else
584 -
            std::cout << "Got string: " << std::get<std::string>(result) << "\n";
565 +
            std::cout << "Got string: " << std::get<1>(result) << "\n";
585  
    }
566  
    }
586  
    @endcode
567  
    @endcode
587  

568  

588  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
569  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
589  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
570  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
590  
    @param a0 The first awaitable to race.
571  
    @param a0 The first awaitable to race.
591  
    @param as Additional awaitables to race concurrently.
572  
    @param as Additional awaitables to race concurrently.
592 -
    @return A task yielding a pair of (winner_index, result_variant).
573 +
    @return A task yielding a variant with one alternative per awaitable.
 
574 +
        Use .index() to identify the winner. Void awaitables contribute
 
575 +
        std::monostate.
593  

576  

594  
    @throws Rethrows the winner's exception if the winning task threw an exception.
577  
    @throws Rethrows the winner's exception if the winning task threw an exception.
595  

578  

596  
    @par Remarks
579  
    @par Remarks
597  
    Awaitables are moved into the coroutine frame; original objects become
580  
    Awaitables are moved into the coroutine frame; original objects become
598 -
    empty after the call. When multiple awaitables share the same return type,
581 +
    empty after the call. The variant preserves one alternative per input
599 -
    the variant is deduplicated to contain only unique types. Use the winner
582 +
    task. Use .index() to determine which awaitable completed first.
600 -
    index to determine which awaitable completed first. Void awaitables
583 +
    Void awaitables contribute std::monostate to the variant.
601 -
    contribute std::monostate to the variant.
 
602  

584  

603  
    @see when_all, IoAwaitable
585  
    @see when_all, IoAwaitable
604  
*/
586  
*/
605  
template<IoAwaitable A0, IoAwaitable... As>
587  
template<IoAwaitable A0, IoAwaitable... As>
606  
[[nodiscard]] auto when_any(A0 a0, As... as)
588  
[[nodiscard]] auto when_any(A0 a0, As... as)
607 -
    -> task<detail::when_any_result_t<
589 +
    -> task<detail::when_any_variant_t<
608  
        detail::awaitable_result_t<A0>,
590  
        detail::awaitable_result_t<A0>,
609  
        detail::awaitable_result_t<As>...>>
591  
        detail::awaitable_result_t<As>...>>
610 -
    using result_type = detail::when_any_result_t<
 
611 -
        detail::awaitable_result_t<A0>,
 
612 -
        detail::awaitable_result_t<As>...>;
 
613 -

 
614  
{
592  
{
615  
    detail::when_any_state<
593  
    detail::when_any_state<
616  
        detail::awaitable_result_t<A0>,
594  
        detail::awaitable_result_t<A0>,
617  
        detail::awaitable_result_t<As>...> state;
595  
        detail::awaitable_result_t<As>...> state;
618  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
596  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
619  

597  

620  
    co_await detail::when_any_launcher<A0, As...>(&awaitable_tuple, &state);
598  
    co_await detail::when_any_launcher<A0, As...>(&awaitable_tuple, &state);
621  

599  

622  
    if(state.core_.winner_exception_)
600  
    if(state.core_.winner_exception_)
623  
        std::rethrow_exception(state.core_.winner_exception_);
601  
        std::rethrow_exception(state.core_.winner_exception_);
624  

602  

625 -
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
603 +
    co_return std::move(*state.result_);
626  
}
604  
}
627  

605  

628  
/** Concept for ranges of full I/O awaitables.
606  
/** Concept for ranges of full I/O awaitables.
629  

607  

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

611  

634  
    @tparam R The range type.
612  
    @tparam R The range type.
635  

613  

636  
    @par Requirements
614  
    @par Requirements
637  
    @li `R` must satisfy `std::ranges::input_range`
615  
    @li `R` must satisfy `std::ranges::input_range`
638  
    @li `R` must satisfy `std::ranges::sized_range`
616  
    @li `R` must satisfy `std::ranges::sized_range`
639  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
617  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
640  

618  

641  
    @par Syntactic Requirements
619  
    @par Syntactic Requirements
642  
    Given `r` of type `R`:
620  
    Given `r` of type `R`:
643  
    @li `std::ranges::begin(r)` is valid
621  
    @li `std::ranges::begin(r)` is valid
644  
    @li `std::ranges::end(r)` is valid
622  
    @li `std::ranges::end(r)` is valid
645  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
623  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
646  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
624  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
647  

625  

648  
    @par Example
626  
    @par Example
649  
    @code
627  
    @code
650  
    template<IoAwaitableRange R>
628  
    template<IoAwaitableRange R>
651  
    task<void> race_all(R&& awaitables) {
629  
    task<void> race_all(R&& awaitables) {
652  
        auto winner = co_await when_any(std::forward<R>(awaitables));
630  
        auto winner = co_await when_any(std::forward<R>(awaitables));
653  
        // Process winner...
631  
        // Process winner...
654  
    }
632  
    }
655  
    @endcode
633  
    @endcode
656  

634  

657  
    @see when_any, IoAwaitable
635  
    @see when_any, IoAwaitable
658  
*/
636  
*/
659  
template<typename R>
637  
template<typename R>
660  
concept IoAwaitableRange =
638  
concept IoAwaitableRange =
661  
    std::ranges::input_range<R> &&
639  
    std::ranges::input_range<R> &&
662  
    std::ranges::sized_range<R> &&
640  
    std::ranges::sized_range<R> &&
663  
    IoAwaitable<std::ranges::range_value_t<R>>;
641  
    IoAwaitable<std::ranges::range_value_t<R>>;
664  

642  

665  
namespace detail {
643  
namespace detail {
666  

644  

667  
/** Shared state for homogeneous when_any (range overload).
645  
/** Shared state for homogeneous when_any (range overload).
668  

646  

669  
    Uses composition with when_any_core for shared functionality.
647  
    Uses composition with when_any_core for shared functionality.
670  
    Simpler than heterogeneous: optional<T> instead of variant, vector
648  
    Simpler than heterogeneous: optional<T> instead of variant, vector
671  
    instead of array for runner handles.
649  
    instead of array for runner handles.
672  
*/
650  
*/
673  
template<typename T>
651  
template<typename T>
674  
struct when_any_homogeneous_state
652  
struct when_any_homogeneous_state
675  
{
653  
{
676  
    when_any_core core_;
654  
    when_any_core core_;
677  
    std::optional<T> result_;
655  
    std::optional<T> result_;
678  
    std::vector<std::coroutine_handle<>> runner_handles_;
656  
    std::vector<std::coroutine_handle<>> runner_handles_;
679  

657  

680  
    explicit when_any_homogeneous_state(std::size_t count)
658  
    explicit when_any_homogeneous_state(std::size_t count)
681  
        : core_(count)
659  
        : core_(count)
682  
        , runner_handles_(count)
660  
        , runner_handles_(count)
683  
    {
661  
    {
684  
    }
662  
    }
685  

663  

686  
    // Runners self-destruct in final_suspend. No destruction needed here.
664  
    // Runners self-destruct in final_suspend. No destruction needed here.
687  

665  

688  
    /** @pre core_.try_win() returned true. */
666  
    /** @pre core_.try_win() returned true. */
689  
    void set_winner_result(T value)
667  
    void set_winner_result(T value)
690  
        noexcept(std::is_nothrow_move_constructible_v<T>)
668  
        noexcept(std::is_nothrow_move_constructible_v<T>)
691  
    {
669  
    {
692  
        result_.emplace(std::move(value));
670  
        result_.emplace(std::move(value));
693  
    }
671  
    }
694  
};
672  
};
695  

673  

696  
/** Specialization for void tasks (no result storage needed). */
674  
/** Specialization for void tasks (no result storage needed). */
697  
template<>
675  
template<>
698  
struct when_any_homogeneous_state<void>
676  
struct when_any_homogeneous_state<void>
699  
{
677  
{
700  
    when_any_core core_;
678  
    when_any_core core_;
701  
    std::vector<std::coroutine_handle<>> runner_handles_;
679  
    std::vector<std::coroutine_handle<>> runner_handles_;
702  

680  

703  
    explicit when_any_homogeneous_state(std::size_t count)
681  
    explicit when_any_homogeneous_state(std::size_t count)
704  
        : core_(count)
682  
        : core_(count)
705  
        , runner_handles_(count)
683  
        , runner_handles_(count)
706  
    {
684  
    {
707  
    }
685  
    }
708  

686  

709  
    // Runners self-destruct in final_suspend. No destruction needed here.
687  
    // Runners self-destruct in final_suspend. No destruction needed here.
710  

688  

711  
    // No set_winner_result - void tasks have no result to store
689  
    // No set_winner_result - void tasks have no result to store
712  
};
690  
};
713  

691  

714  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
692  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
715  
template<IoAwaitableRange Range>
693  
template<IoAwaitableRange Range>
716  
class when_any_homogeneous_launcher
694  
class when_any_homogeneous_launcher
717  
{
695  
{
718  
    using Awaitable = std::ranges::range_value_t<Range>;
696  
    using Awaitable = std::ranges::range_value_t<Range>;
719  
    using T = awaitable_result_t<Awaitable>;
697  
    using T = awaitable_result_t<Awaitable>;
720  

698  

721  
    Range* range_;
699  
    Range* range_;
722  
    when_any_homogeneous_state<T>* state_;
700  
    when_any_homogeneous_state<T>* state_;
723  

701  

724  
public:
702  
public:
725  
    when_any_homogeneous_launcher(
703  
    when_any_homogeneous_launcher(
726  
        Range* range,
704  
        Range* range,
727  
        when_any_homogeneous_state<T>* state)
705  
        when_any_homogeneous_state<T>* state)
728  
        : range_(range)
706  
        : range_(range)
729  
        , state_(state)
707  
        , state_(state)
730  
    {
708  
    {
731  
    }
709  
    }
732  

710  

733  
    bool await_ready() const noexcept
711  
    bool await_ready() const noexcept
734  
    {
712  
    {
735  
        return std::ranges::empty(*range_);
713  
        return std::ranges::empty(*range_);
736  
    }
714  
    }
737  

715  

738  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
716  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
739  
        destroys this object before await_suspend returns. Must not reference
717  
        destroys this object before await_suspend returns. Must not reference
740  
        `this` after dispatching begins.
718  
        `this` after dispatching begins.
741  

719  

742  
        Two-phase approach:
720  
        Two-phase approach:
743  
        1. Create all runners (safe - no dispatch yet)
721  
        1. Create all runners (safe - no dispatch yet)
744  
        2. Dispatch all runners (any may complete synchronously)
722  
        2. Dispatch all runners (any may complete synchronously)
745  
    */
723  
    */
746  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
724  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
747  
    {
725  
    {
748  
        state_->core_.continuation_ = continuation;
726  
        state_->core_.continuation_ = continuation;
749  
        state_->core_.caller_env_ = caller_env;
727  
        state_->core_.caller_env_ = caller_env;
750  

728  

751  
        if(caller_env->stop_token.stop_possible())
729  
        if(caller_env->stop_token.stop_possible())
752  
        {
730  
        {
753  
            state_->core_.parent_stop_callback_.emplace(
731  
            state_->core_.parent_stop_callback_.emplace(
754  
                caller_env->stop_token,
732  
                caller_env->stop_token,
755  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
733  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
756  

734  

757  
            if(caller_env->stop_token.stop_requested())
735  
            if(caller_env->stop_token.stop_requested())
758  
                state_->core_.stop_source_.request_stop();
736  
                state_->core_.stop_source_.request_stop();
759  
        }
737  
        }
760  

738  

761  
        auto token = state_->core_.stop_source_.get_token();
739  
        auto token = state_->core_.stop_source_.get_token();
762  

740  

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

748  

771  
            auto h = runner.release();
749  
            auto h = runner.release();
772  
            h.promise().state_ = state_;
750  
            h.promise().state_ = state_;
773  
            h.promise().index_ = index;
751  
            h.promise().index_ = index;
774  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
752  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
775  

753  

776  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
754  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
777  
            ++index;
755  
            ++index;
778  
        }
756  
        }
779  

757  

780  
        // Phase 2: Post all runners. Any may complete synchronously.
758  
        // Phase 2: Post all runners. Any may complete synchronously.
781  
        // After last post, state_ and this may be destroyed.
759  
        // After last post, state_ and this may be destroyed.
782  
        // Use raw pointer/count captured before posting.
760  
        // Use raw pointer/count captured before posting.
783  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
761  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
784  
        std::size_t count = state_->runner_handles_.size();
762  
        std::size_t count = state_->runner_handles_.size();
785  
        for(std::size_t i = 0; i < count; ++i)
763  
        for(std::size_t i = 0; i < count; ++i)
786  
            caller_env->executor.post(handles[i]);
764  
            caller_env->executor.post(handles[i]);
787  

765  

788  
        return std::noop_coroutine();
766  
        return std::noop_coroutine();
789  
    }
767  
    }
790  

768  

791  
    void await_resume() const noexcept
769  
    void await_resume() const noexcept
792  
    {
770  
    {
793  
    }
771  
    }
794  
};
772  
};
795  

773  

796  
} // namespace detail
774  
} // namespace detail
797  

775  

798  
/** Wait for the first awaitable to complete (range overload).
776  
/** Wait for the first awaitable to complete (range overload).
799  

777  

800  
    Races a range of awaitables with the same result type. Accepts any
778  
    Races a range of awaitables with the same result type. Accepts any
801  
    sized input range of IoAwaitable types, enabling use with arrays,
779  
    sized input range of IoAwaitable types, enabling use with arrays,
802  
    spans, or custom containers.
780  
    spans, or custom containers.
803  

781  

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

787  

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

794  

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

803  

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

810  

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

816  

839  
    @par Example
817  
    @par Example
840  
    @code
818  
    @code
841  
    task<void> example() {
819  
    task<void> example() {
842  
        std::array<task<Response>, 3> requests = {
820  
        std::array<task<Response>, 3> requests = {
843  
            fetch_from_server(0),
821  
            fetch_from_server(0),
844  
            fetch_from_server(1),
822  
            fetch_from_server(1),
845  
            fetch_from_server(2)
823  
            fetch_from_server(2)
846  
        };
824  
        };
847  

825  

848  
        auto [index, response] = co_await when_any(std::move(requests));
826  
        auto [index, response] = co_await when_any(std::move(requests));
849  
    }
827  
    }
850  
    @endcode
828  
    @endcode
851  

829  

852  
    @par Example with Vector
830  
    @par Example with Vector
853  
    @code
831  
    @code
854  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
832  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
855  
        std::vector<task<Response>> requests;
833  
        std::vector<task<Response>> requests;
856  
        for (auto const& server : servers)
834  
        for (auto const& server : servers)
857  
            requests.push_back(fetch_from(server));
835  
            requests.push_back(fetch_from(server));
858  

836  

859  
        auto [index, response] = co_await when_any(std::move(requests));
837  
        auto [index, response] = co_await when_any(std::move(requests));
860  
        co_return response;
838  
        co_return response;
861  
    }
839  
    }
862  
    @endcode
840  
    @endcode
863  

841  

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

845  

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

848  

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

855  

878  
    @see when_any, IoAwaitableRange
856  
    @see when_any, IoAwaitableRange
879  
*/
857  
*/
880  
template<IoAwaitableRange R>
858  
template<IoAwaitableRange R>
881  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
859  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
882  
[[nodiscard]] auto when_any(R&& awaitables)
860  
[[nodiscard]] auto when_any(R&& awaitables)
883  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
861  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
884  
{
862  
{
885  
    using Awaitable = std::ranges::range_value_t<R>;
863  
    using Awaitable = std::ranges::range_value_t<R>;
886  
    using T = detail::awaitable_result_t<Awaitable>;
864  
    using T = detail::awaitable_result_t<Awaitable>;
887  
    using result_type = std::pair<std::size_t, T>;
865  
    using result_type = std::pair<std::size_t, T>;
888  
    using OwnedRange = std::remove_cvref_t<R>;
866  
    using OwnedRange = std::remove_cvref_t<R>;
889  

867  

890  
    auto count = std::ranges::size(awaitables);
868  
    auto count = std::ranges::size(awaitables);
891  
    if(count == 0)
869  
    if(count == 0)
892  
        throw std::invalid_argument("when_any requires at least one awaitable");
870  
        throw std::invalid_argument("when_any requires at least one awaitable");
893  

871  

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

874  

897  
    detail::when_any_homogeneous_state<T> state(count);
875  
    detail::when_any_homogeneous_state<T> state(count);
898  

876  

899  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
877  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
900  

878  

901  
    if(state.core_.winner_exception_)
879  
    if(state.core_.winner_exception_)
902  
        std::rethrow_exception(state.core_.winner_exception_);
880  
        std::rethrow_exception(state.core_.winner_exception_);
903  

881  

904  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
882  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
905  
}
883  
}
906  

884  

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

886  

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

889  

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

895  

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

902  

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

911  

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

918  

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

924  

947  
    @par Example
925  
    @par Example
948  
    @code
926  
    @code
949  
    task<void> example() {
927  
    task<void> example() {
950  
        std::vector<task<void>> tasks;
928  
        std::vector<task<void>> tasks;
951  
        for (int i = 0; i < 5; ++i)
929  
        for (int i = 0; i < 5; ++i)
952  
            tasks.push_back(background_work(i));
930  
            tasks.push_back(background_work(i));
953  

931  

954  
        std::size_t winner = co_await when_any(std::move(tasks));
932  
        std::size_t winner = co_await when_any(std::move(tasks));
955  
        // winner is the index of the first task to complete
933  
        // winner is the index of the first task to complete
956  
    }
934  
    }
957  
    @endcode
935  
    @endcode
958  

936  

959  
    @par Example with Timeout
937  
    @par Example with Timeout
960  
    @code
938  
    @code
961  
    task<void> with_timeout() {
939  
    task<void> with_timeout() {
962  
        std::vector<task<void>> tasks;
940  
        std::vector<task<void>> tasks;
963  
        tasks.push_back(long_running_operation());
941  
        tasks.push_back(long_running_operation());
964  
        tasks.push_back(delay(std::chrono::seconds(5)));
942  
        tasks.push_back(delay(std::chrono::seconds(5)));
965  

943  

966  
        std::size_t winner = co_await when_any(std::move(tasks));
944  
        std::size_t winner = co_await when_any(std::move(tasks));
967  
        if (winner == 1) {
945  
        if (winner == 1) {
968  
            // Timeout occurred
946  
            // Timeout occurred
969  
        }
947  
        }
970  
    }
948  
    }
971  
    @endcode
949  
    @endcode
972  

950  

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

954  

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

957  

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

964  

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

972  

995  
    auto count = std::ranges::size(awaitables);
973  
    auto count = std::ranges::size(awaitables);
996  
    if(count == 0)
974  
    if(count == 0)
997  
        throw std::invalid_argument("when_any requires at least one awaitable");
975  
        throw std::invalid_argument("when_any requires at least one awaitable");
998  

976  

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

979  

1002  
    detail::when_any_homogeneous_state<void> state(count);
980  
    detail::when_any_homogeneous_state<void> state(count);
1003  

981  

1004  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
982  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
1005  

983  

1006  
    if(state.core_.winner_exception_)
984  
    if(state.core_.winner_exception_)
1007  
        std::rethrow_exception(state.core_.winner_exception_);
985  
        std::rethrow_exception(state.core_.winner_exception_);
1008  

986  

1009  
    co_return state.core_.winner_index_;
987  
    co_return state.core_.winner_index_;
1010  
}
988  
}
1011  

989  

1012  
} // namespace capy
990  
} // namespace capy
1013  
} // namespace boost
991  
} // namespace boost
1014  

992  

1015  
#endif
993  
#endif