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  
   TYPE DEDUPLICATION:
66  
   -------------------
66  
   -------------------
67  
   std::variant requires unique alternative types. Since when_any can race
67  
   std::variant requires unique alternative types. Since when_any can race
68  
   tasks with identical return types (e.g., three task<int>), we must
68  
   tasks with identical return types (e.g., three task<int>), we must
69  
   deduplicate types before constructing the variant.
69  
   deduplicate types before constructing the variant.
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  
     - Deduplicated variant: std::variant<int, string>
74  
     - Return: pair<size_t, variant<int, string>>
74  
     - Return: pair<size_t, variant<int, string>>
75  

75  

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

78  

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

83  

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

87  

88  
   1. Winner thread writes result_/winner_exception_ (non-atomic)
88  
   1. Winner thread writes result_/winner_exception_ (non-atomic)
89  
   2. Winner thread calls signal_completion() → fetch_sub(acq_rel) on remaining_count_
89  
   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()
90  
   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
91  
      → fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
92  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
92  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
93  
   5. Parent coroutine resumes and reads result_/winner_exception_
93  
   5. Parent coroutine resumes and reads result_/winner_exception_
94  

94  

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

104  

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

110  

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

117  

118  
namespace boost {
118  
namespace boost {
119  
namespace capy {
119  
namespace capy {
120  

120  

121  
namespace detail {
121  
namespace detail {
122  

122  

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

124  

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

128  

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

133  

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

138  

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

147  

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

150  

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

156  

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

163  

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

169  

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

174  

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

176  

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

180  

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

190  

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

199  

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

202  

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

205  

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

210  

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

224  

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

230  

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

233  

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

235  

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

239  

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

242  

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

251  

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

255  

256  
    when_any_state()
256  
    when_any_state()
257  
        : core_(task_count)
257  
        : core_(task_count)
258  
    {
258  
    {
259  
    }
259  
    }
260  

260  

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

262  

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

272  

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

279  

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

281  

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

284  

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

295  

296  
        when_any_runner get_return_object() noexcept
296  
        when_any_runner get_return_object() noexcept
297  
        {
297  
        {
298  
            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));
299  
        }
299  
        }
300  

300  

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

306  

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

320  

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

322  

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

333  

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

335  

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

342  

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

349  

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

352  

353  
            template<class Promise>
353  
            template<class Promise>
354  
            auto await_suspend(std::coroutine_handle<Promise> h)
354  
            auto await_suspend(std::coroutine_handle<Promise> h)
355  
            {
355  
            {
356  
#ifdef _MSC_VER
356  
#ifdef _MSC_VER
357  
                using R = decltype(a_.await_suspend(h, &p_->env_));
357  
                using R = decltype(a_.await_suspend(h, &p_->env_));
358  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
358  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
359  
                    a_.await_suspend(h, &p_->env_).resume();
359  
                    a_.await_suspend(h, &p_->env_).resume();
360  
                else
360  
                else
361  
                    return a_.await_suspend(h, &p_->env_);
361  
                    return a_.await_suspend(h, &p_->env_);
362  
#else
362  
#else
363  
                return a_.await_suspend(h, &p_->env_);
363  
                return a_.await_suspend(h, &p_->env_);
364  
#endif
364  
#endif
365  
            }
365  
            }
366  
        };
366  
        };
367  

367  

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

383  

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

385  

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

390  

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

393  

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

398  

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

404  

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

406  

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

446  

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

452  

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

455  

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

464  

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

469  

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

478  

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

484  

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

488  

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

493  

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

496  

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

500  

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

508  

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

513  

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

519  

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

521  

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

523  

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

527  

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

533  

534  
    @par Completion Conditions
534  
    @par Completion Conditions
535  
    @li Winner is determined when the first awaitable completes (success or exception)
535  
    @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
536  
    @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
537  
    @li Once a winner exists, stop is requested for all remaining siblings
538  
    @li Parent coroutine resumes only after all siblings acknowledge completion
538  
    @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
539  
    @li The winner's result is returned; if the winner threw, the exception is rethrown
540  

540  

541  
    @par Cancellation Semantics
541  
    @par Cancellation Semantics
542  
    Cancellation is supported via stop_token propagated through the
542  
    Cancellation is supported via stop_token propagated through the
543  
    IoAwaitable protocol:
543  
    IoAwaitable protocol:
544  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
544  
    @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
545  
    @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
546  
    @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
547  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
548  
    @li Stop requests are cooperative; tasks must check and respond to them
548  
    @li Stop requests are cooperative; tasks must check and respond to them
549  

549  

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

556  

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

561  

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

573  

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

587  

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

593  

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

595  

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

602  

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

614  

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

619  

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

621  

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

624  

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

627  

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

629  

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

633  

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

635  

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

640  

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

647  

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

656  

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

664  

665  
namespace detail {
665  
namespace detail {
666  

666  

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

668  

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

679  

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

685  

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

687  

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

695  

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

702  

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

708  

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

710  

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

713  

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

720  

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

723  

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

732  

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

737  

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

741  

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

750  

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

756  

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

760  

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

762  

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

770  

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

775  

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

779  

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

787  

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

790  

791  
    void await_resume() const noexcept
791  
    void await_resume() const noexcept
792  
    {
792  
    {
793  
    }
793  
    }
794  
};
794  
};
795  

795  

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

797  

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

799  

800  
    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
801  
    sized input range of IoAwaitable types, enabling use with arrays,
801  
    sized input range of IoAwaitable types, enabling use with arrays,
802  
    spans, or custom containers.
802  
    spans, or custom containers.
803  

803  

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

809  

810  
    @par Completion Conditions
810  
    @par Completion Conditions
811  
    @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)
812  
    @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
813  
    @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
814  
    @li Parent coroutine resumes only after all siblings acknowledge completion
814  
    @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
815  
    @li The winner's index and result are returned; if the winner threw, the exception is rethrown
816  

816  

817  
    @par Cancellation Semantics
817  
    @par Cancellation Semantics
818  
    Cancellation is supported via stop_token propagated through the
818  
    Cancellation is supported via stop_token propagated through the
819  
    IoAwaitable protocol:
819  
    IoAwaitable protocol:
820  
    @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
821  
    @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
822  
    @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
823  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
823  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
824  
    @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
825  

825  

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

832  

833  
    @par Notable Error Conditions
833  
    @par Notable Error Conditions
834  
    @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)
835  
    @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
836  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
836  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
837  
    @li Cancellation: tasks may complete via cancellation without throwing
837  
    @li Cancellation: tasks may complete via cancellation without throwing
838  

838  

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

847  

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

851  

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

858  

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

863  

864  
    @tparam R Range type satisfying IoAwaitableRange.
864  
    @tparam R Range type satisfying IoAwaitableRange.
865  
    @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).
866  
    @return A task yielding a pair of (winner_index, result).
866  
    @return A task yielding a pair of (winner_index, result).
867  

867  

868  
    @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).
869  
    @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.
870  

870  

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

877  

878  
    @see when_any, IoAwaitableRange
878  
    @see when_any, IoAwaitableRange
879  
*/
879  
*/
880  
template<IoAwaitableRange R>
880  
template<IoAwaitableRange R>
881  
    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>>>)
882  
[[nodiscard]] auto when_any(R&& awaitables)
882  
[[nodiscard]] auto when_any(R&& awaitables)
883  
    -> 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>>>>
884  
{
884  
{
885  
    using Awaitable = std::ranges::range_value_t<R>;
885  
    using Awaitable = std::ranges::range_value_t<R>;
886  
    using T = detail::awaitable_result_t<Awaitable>;
886  
    using T = detail::awaitable_result_t<Awaitable>;
887  
    using result_type = std::pair<std::size_t, T>;
887  
    using result_type = std::pair<std::size_t, T>;
888  
    using OwnedRange = std::remove_cvref_t<R>;
888  
    using OwnedRange = std::remove_cvref_t<R>;
889  

889  

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

893  

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

896  

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

898  

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

900  

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

903  

904  
    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_)};
905  
}
905  
}
906  

906  

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

908  

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

911  

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

917  

918  
    @par Completion Conditions
918  
    @par Completion Conditions
919  
    @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)
920  
    @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
921  
    @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
922  
    @li Parent coroutine resumes only after all siblings acknowledge completion
922  
    @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
923  
    @li The winner's index is returned; if the winner threw, the exception is rethrown
924  

924  

925  
    @par Cancellation Semantics
925  
    @par Cancellation Semantics
926  
    Cancellation is supported via stop_token propagated through the
926  
    Cancellation is supported via stop_token propagated through the
927  
    IoAwaitable protocol:
927  
    IoAwaitable protocol:
928  
    @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
929  
    @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
930  
    @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
931  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
931  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
932  
    @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
933  

933  

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

940  

941  
    @par Notable Error Conditions
941  
    @par Notable Error Conditions
942  
    @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)
943  
    @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
944  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
944  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
945  
    @li Cancellation: tasks may complete via cancellation without throwing
945  
    @li Cancellation: tasks may complete via cancellation without throwing
946  

946  

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

953  

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

958  

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

965  

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

972  

973  
    @tparam R Range type satisfying IoAwaitableRange with void result.
973  
    @tparam R Range type satisfying IoAwaitableRange with void result.
974  
    @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).
975  
    @return A task yielding the winner's index (zero-based).
975  
    @return A task yielding the winner's index (zero-based).
976  

976  

977  
    @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).
978  
    @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.
979  

979  

980  
    @par Remarks
980  
    @par Remarks
981  
    Elements are moved from the range; for lvalue ranges, the original
981  
    Elements are moved from the range; for lvalue ranges, the original
982  
    container will have moved-from elements after this call. The range
982  
    container will have moved-from elements after this call. The range
983  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
983  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
984  
    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
985  
    produce no value.
985  
    produce no value.
986  

986  

987  
    @see when_any, IoAwaitableRange
987  
    @see when_any, IoAwaitableRange
988  
*/
988  
*/
989  
template<IoAwaitableRange R>
989  
template<IoAwaitableRange R>
990  
    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>>>
991  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
991  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
992  
{
992  
{
993  
    using OwnedRange = std::remove_cvref_t<R>;
993  
    using OwnedRange = std::remove_cvref_t<R>;
994  

994  

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

998  

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

1001  

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

1003  

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

1005  

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

1008  

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

1011  

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

1014  

1015  
#endif
1015  
#endif