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  
                return a_.await_suspend(h, &p_->env_);
356  
                return a_.await_suspend(h, &p_->env_);
357  
            }
357  
            }
358  
        };
358  
        };
359  

359  

360  
        template<class Awaitable>
360  
        template<class Awaitable>
361  
        auto await_transform(Awaitable&& a)
361  
        auto await_transform(Awaitable&& a)
362  
        {
362  
        {
363  
            using A = std::decay_t<Awaitable>;
363  
            using A = std::decay_t<Awaitable>;
364  
            if constexpr (IoAwaitable<A>)
364  
            if constexpr (IoAwaitable<A>)
365  
            {
365  
            {
366  
                return transform_awaiter<Awaitable>{
366  
                return transform_awaiter<Awaitable>{
367  
                    std::forward<Awaitable>(a), this};
367  
                    std::forward<Awaitable>(a), this};
368  
            }
368  
            }
369  
            else
369  
            else
370  
            {
370  
            {
371  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
371  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
372  
            }
372  
            }
373  
        }
373  
        }
374  
    };
374  
    };
375  

375  

376  
    std::coroutine_handle<promise_type> h_;
376  
    std::coroutine_handle<promise_type> h_;
377  

377  

378  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
378  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
379  
        : h_(h)
379  
        : h_(h)
380  
    {
380  
    {
381  
    }
381  
    }
382  

382  

383  
    // Enable move for all clang versions - some versions need it
383  
    // Enable move for all clang versions - some versions need it
384  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
384  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
385  

385  

386  
    // Non-copyable
386  
    // Non-copyable
387  
    when_any_runner(when_any_runner const&) = delete;
387  
    when_any_runner(when_any_runner const&) = delete;
388  
    when_any_runner& operator=(when_any_runner const&) = delete;
388  
    when_any_runner& operator=(when_any_runner const&) = delete;
389  
    when_any_runner& operator=(when_any_runner&&) = delete;
389  
    when_any_runner& operator=(when_any_runner&&) = delete;
390  

390  

391  
    auto release() noexcept
391  
    auto release() noexcept
392  
    {
392  
    {
393  
        return std::exchange(h_, nullptr);
393  
        return std::exchange(h_, nullptr);
394  
    }
394  
    }
395  
};
395  
};
396  

396  

397  
/** Wraps a child awaitable, attempts to claim winner on completion.
397  
/** Wraps a child awaitable, attempts to claim winner on completion.
398  

398  

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

438  

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

444  

445  
    std::tuple<Awaitables...>* tasks_;
445  
    std::tuple<Awaitables...>* tasks_;
446  
    state_type* state_;
446  
    state_type* state_;
447  

447  

448  
public:
448  
public:
449  
    when_any_launcher(
449  
    when_any_launcher(
450  
        std::tuple<Awaitables...>* tasks,
450  
        std::tuple<Awaitables...>* tasks,
451  
        state_type* state)
451  
        state_type* state)
452  
        : tasks_(tasks)
452  
        : tasks_(tasks)
453  
        , state_(state)
453  
        , state_(state)
454  
    {
454  
    {
455  
    }
455  
    }
456  

456  

457  
    bool await_ready() const noexcept
457  
    bool await_ready() const noexcept
458  
    {
458  
    {
459  
        return sizeof...(Awaitables) == 0;
459  
        return sizeof...(Awaitables) == 0;
460  
    }
460  
    }
461  

461  

462  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
462  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
463  
        destroys this object before await_suspend returns. Must not reference
463  
        destroys this object before await_suspend returns. Must not reference
464  
        `this` after the final launch_one call.
464  
        `this` after the final launch_one call.
465  
    */
465  
    */
466  
    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)
467  
    {
467  
    {
468  
        state_->core_.continuation_ = continuation;
468  
        state_->core_.continuation_ = continuation;
469  
        state_->core_.caller_env_ = caller_env;
469  
        state_->core_.caller_env_ = caller_env;
470  

470  

471  
        if(caller_env->stop_token.stop_possible())
471  
        if(caller_env->stop_token.stop_possible())
472  
        {
472  
        {
473  
            state_->core_.parent_stop_callback_.emplace(
473  
            state_->core_.parent_stop_callback_.emplace(
474  
                caller_env->stop_token,
474  
                caller_env->stop_token,
475  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
475  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
476  

476  

477  
            if(caller_env->stop_token.stop_requested())
477  
            if(caller_env->stop_token.stop_requested())
478  
                state_->core_.stop_source_.request_stop();
478  
                state_->core_.stop_source_.request_stop();
479  
        }
479  
        }
480  

480  

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

485  

486  
        return std::noop_coroutine();
486  
        return std::noop_coroutine();
487  
    }
487  
    }
488  

488  

489  
    void await_resume() const noexcept
489  
    void await_resume() const noexcept
490  
    {
490  
    {
491  
    }
491  
    }
492  

492  

493  
private:
493  
private:
494  
    /** @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). */
495  
    template<std::size_t I>
495  
    template<std::size_t I>
496  
    void launch_one(executor_ref caller_ex, std::stop_token token)
496  
    void launch_one(executor_ref caller_ex, std::stop_token token)
497  
    {
497  
    {
498  
        auto runner = make_when_any_runner(
498  
        auto runner = make_when_any_runner(
499  
            std::move(std::get<I>(*tasks_)), state_, I);
499  
            std::move(std::get<I>(*tasks_)), state_, I);
500  

500  

501  
        auto h = runner.release();
501  
        auto h = runner.release();
502  
        h.promise().state_ = state_;
502  
        h.promise().state_ = state_;
503  
        h.promise().index_ = I;
503  
        h.promise().index_ = I;
504  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->allocator};
504  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->allocator};
505  

505  

506  
        std::coroutine_handle<> ch{h};
506  
        std::coroutine_handle<> ch{h};
507  
        state_->runner_handles_[I] = ch;
507  
        state_->runner_handles_[I] = ch;
508  
        caller_ex.post(ch);
508  
        caller_ex.post(ch);
509  
    }
509  
    }
510  
};
510  
};
511  

511  

512  
} // namespace detail
512  
} // namespace detail
513  

513  

514  
/** Wait for the first awaitable to complete.
514  
/** Wait for the first awaitable to complete.
515  

515  

516  
    Races multiple heterogeneous awaitables concurrently and returns when the
516  
    Races multiple heterogeneous awaitables concurrently and returns when the
517  
    first one completes. The result includes the winner's index and a
517  
    first one completes. The result includes the winner's index and a
518  
    deduplicated variant containing the result value.
518  
    deduplicated variant containing the result value.
519  

519  

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

525  

526  
    @par Completion Conditions
526  
    @par Completion Conditions
527  
    @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)
528  
    @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
529  
    @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
530  
    @li Parent coroutine resumes only after all siblings acknowledge completion
530  
    @li Parent coroutine resumes only after all siblings acknowledge completion
531  
    @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
532  

532  

533  
    @par Cancellation Semantics
533  
    @par Cancellation Semantics
534  
    Cancellation is supported via stop_token propagated through the
534  
    Cancellation is supported via stop_token propagated through the
535  
    IoAwaitable protocol:
535  
    IoAwaitable protocol:
536  
    @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
537  
    @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
538  
    @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
539  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
539  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
540  
    @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
541  

541  

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

548  

549  
    @par Notable Error Conditions
549  
    @par Notable Error Conditions
550  
    @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
551  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
551  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
552  
    @li Cancellation: tasks may complete via cancellation without throwing
552  
    @li Cancellation: tasks may complete via cancellation without throwing
553  

553  

554  
    @par Example
554  
    @par Example
555  
    @code
555  
    @code
556  
    task<void> example() {
556  
    task<void> example() {
557  
        auto [index, result] = co_await when_any(
557  
        auto [index, result] = co_await when_any(
558  
            fetch_from_primary(),   // task<Response>
558  
            fetch_from_primary(),   // task<Response>
559  
            fetch_from_backup()     // task<Response>
559  
            fetch_from_backup()     // task<Response>
560  
        );
560  
        );
561  
        // index is 0 or 1, result holds the winner's Response
561  
        // index is 0 or 1, result holds the winner's Response
562  
        auto response = std::get<Response>(result);
562  
        auto response = std::get<Response>(result);
563  
    }
563  
    }
564  
    @endcode
564  
    @endcode
565  

565  

566  
    @par Example with Heterogeneous Types
566  
    @par Example with Heterogeneous Types
567  
    @code
567  
    @code
568  
    task<void> mixed_types() {
568  
    task<void> mixed_types() {
569  
        auto [index, result] = co_await when_any(
569  
        auto [index, result] = co_await when_any(
570  
            fetch_int(),      // task<int>
570  
            fetch_int(),      // task<int>
571  
            fetch_string()    // task<std::string>
571  
            fetch_string()    // task<std::string>
572  
        );
572  
        );
573  
        if (index == 0)
573  
        if (index == 0)
574  
            std::cout << "Got int: " << std::get<int>(result) << "\n";
574  
            std::cout << "Got int: " << std::get<int>(result) << "\n";
575  
        else
575  
        else
576  
            std::cout << "Got string: " << std::get<std::string>(result) << "\n";
576  
            std::cout << "Got string: " << std::get<std::string>(result) << "\n";
577  
    }
577  
    }
578  
    @endcode
578  
    @endcode
579  

579  

580  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
580  
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
581  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
581  
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
582  
    @param a0 The first awaitable to race.
582  
    @param a0 The first awaitable to race.
583  
    @param as Additional awaitables to race concurrently.
583  
    @param as Additional awaitables to race concurrently.
584  
    @return A task yielding a pair of (winner_index, result_variant).
584  
    @return A task yielding a pair of (winner_index, result_variant).
585  

585  

586  
    @throws Rethrows the winner's exception if the winning task threw an exception.
586  
    @throws Rethrows the winner's exception if the winning task threw an exception.
587  

587  

588  
    @par Remarks
588  
    @par Remarks
589  
    Awaitables are moved into the coroutine frame; original objects become
589  
    Awaitables are moved into the coroutine frame; original objects become
590  
    empty after the call. When multiple awaitables share the same return type,
590  
    empty after the call. When multiple awaitables share the same return type,
591  
    the variant is deduplicated to contain only unique types. Use the winner
591  
    the variant is deduplicated to contain only unique types. Use the winner
592  
    index to determine which awaitable completed first. Void awaitables
592  
    index to determine which awaitable completed first. Void awaitables
593  
    contribute std::monostate to the variant.
593  
    contribute std::monostate to the variant.
594  

594  

595  
    @see when_all, IoAwaitable
595  
    @see when_all, IoAwaitable
596  
*/
596  
*/
597  
template<IoAwaitable A0, IoAwaitable... As>
597  
template<IoAwaitable A0, IoAwaitable... As>
598  
[[nodiscard]] auto when_any(A0 a0, As... as)
598  
[[nodiscard]] auto when_any(A0 a0, As... as)
599  
    -> task<detail::when_any_result_t<
599  
    -> task<detail::when_any_result_t<
600  
        detail::awaitable_result_t<A0>,
600  
        detail::awaitable_result_t<A0>,
601  
        detail::awaitable_result_t<As>...>>
601  
        detail::awaitable_result_t<As>...>>
602  
{
602  
{
603  
    using result_type = detail::when_any_result_t<
603  
    using result_type = detail::when_any_result_t<
604  
        detail::awaitable_result_t<A0>,
604  
        detail::awaitable_result_t<A0>,
605  
        detail::awaitable_result_t<As>...>;
605  
        detail::awaitable_result_t<As>...>;
606  

606  

607  
    detail::when_any_state<
607  
    detail::when_any_state<
608  
        detail::awaitable_result_t<A0>,
608  
        detail::awaitable_result_t<A0>,
609  
        detail::awaitable_result_t<As>...> state;
609  
        detail::awaitable_result_t<As>...> state;
610  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
610  
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
611  

611  

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

613  

614  
    if(state.core_.winner_exception_)
614  
    if(state.core_.winner_exception_)
615  
        std::rethrow_exception(state.core_.winner_exception_);
615  
        std::rethrow_exception(state.core_.winner_exception_);
616  

616  

617  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
617  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
618  
}
618  
}
619  

619  

620  
/** Concept for ranges of full I/O awaitables.
620  
/** Concept for ranges of full I/O awaitables.
621  

621  

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

625  

626  
    @tparam R The range type.
626  
    @tparam R The range type.
627  

627  

628  
    @par Requirements
628  
    @par Requirements
629  
    @li `R` must satisfy `std::ranges::input_range`
629  
    @li `R` must satisfy `std::ranges::input_range`
630  
    @li `R` must satisfy `std::ranges::sized_range`
630  
    @li `R` must satisfy `std::ranges::sized_range`
631  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
631  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
632  

632  

633  
    @par Syntactic Requirements
633  
    @par Syntactic Requirements
634  
    Given `r` of type `R`:
634  
    Given `r` of type `R`:
635  
    @li `std::ranges::begin(r)` is valid
635  
    @li `std::ranges::begin(r)` is valid
636  
    @li `std::ranges::end(r)` is valid
636  
    @li `std::ranges::end(r)` is valid
637  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
637  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
638  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
638  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
639  

639  

640  
    @par Example
640  
    @par Example
641  
    @code
641  
    @code
642  
    template<IoAwaitableRange R>
642  
    template<IoAwaitableRange R>
643  
    task<void> race_all(R&& awaitables) {
643  
    task<void> race_all(R&& awaitables) {
644  
        auto winner = co_await when_any(std::forward<R>(awaitables));
644  
        auto winner = co_await when_any(std::forward<R>(awaitables));
645  
        // Process winner...
645  
        // Process winner...
646  
    }
646  
    }
647  
    @endcode
647  
    @endcode
648  

648  

649  
    @see when_any, IoAwaitable
649  
    @see when_any, IoAwaitable
650  
*/
650  
*/
651  
template<typename R>
651  
template<typename R>
652  
concept IoAwaitableRange =
652  
concept IoAwaitableRange =
653  
    std::ranges::input_range<R> &&
653  
    std::ranges::input_range<R> &&
654  
    std::ranges::sized_range<R> &&
654  
    std::ranges::sized_range<R> &&
655  
    IoAwaitable<std::ranges::range_value_t<R>>;
655  
    IoAwaitable<std::ranges::range_value_t<R>>;
656  

656  

657  
namespace detail {
657  
namespace detail {
658  

658  

659  
/** Shared state for homogeneous when_any (range overload).
659  
/** Shared state for homogeneous when_any (range overload).
660  

660  

661  
    Uses composition with when_any_core for shared functionality.
661  
    Uses composition with when_any_core for shared functionality.
662  
    Simpler than heterogeneous: optional<T> instead of variant, vector
662  
    Simpler than heterogeneous: optional<T> instead of variant, vector
663  
    instead of array for runner handles.
663  
    instead of array for runner handles.
664  
*/
664  
*/
665  
template<typename T>
665  
template<typename T>
666  
struct when_any_homogeneous_state
666  
struct when_any_homogeneous_state
667  
{
667  
{
668  
    when_any_core core_;
668  
    when_any_core core_;
669  
    std::optional<T> result_;
669  
    std::optional<T> result_;
670  
    std::vector<std::coroutine_handle<>> runner_handles_;
670  
    std::vector<std::coroutine_handle<>> runner_handles_;
671  

671  

672  
    explicit when_any_homogeneous_state(std::size_t count)
672  
    explicit when_any_homogeneous_state(std::size_t count)
673  
        : core_(count)
673  
        : core_(count)
674  
        , runner_handles_(count)
674  
        , runner_handles_(count)
675  
    {
675  
    {
676  
    }
676  
    }
677  

677  

678  
    // Runners self-destruct in final_suspend. No destruction needed here.
678  
    // Runners self-destruct in final_suspend. No destruction needed here.
679  

679  

680  
    /** @pre core_.try_win() returned true. */
680  
    /** @pre core_.try_win() returned true. */
681  
    void set_winner_result(T value)
681  
    void set_winner_result(T value)
682  
        noexcept(std::is_nothrow_move_constructible_v<T>)
682  
        noexcept(std::is_nothrow_move_constructible_v<T>)
683  
    {
683  
    {
684  
        result_.emplace(std::move(value));
684  
        result_.emplace(std::move(value));
685  
    }
685  
    }
686  
};
686  
};
687  

687  

688  
/** Specialization for void tasks (no result storage needed). */
688  
/** Specialization for void tasks (no result storage needed). */
689  
template<>
689  
template<>
690  
struct when_any_homogeneous_state<void>
690  
struct when_any_homogeneous_state<void>
691  
{
691  
{
692  
    when_any_core core_;
692  
    when_any_core core_;
693  
    std::vector<std::coroutine_handle<>> runner_handles_;
693  
    std::vector<std::coroutine_handle<>> runner_handles_;
694  

694  

695  
    explicit when_any_homogeneous_state(std::size_t count)
695  
    explicit when_any_homogeneous_state(std::size_t count)
696  
        : core_(count)
696  
        : core_(count)
697  
        , runner_handles_(count)
697  
        , runner_handles_(count)
698  
    {
698  
    {
699  
    }
699  
    }
700  

700  

701  
    // Runners self-destruct in final_suspend. No destruction needed here.
701  
    // Runners self-destruct in final_suspend. No destruction needed here.
702  

702  

703  
    // No set_winner_result - void tasks have no result to store
703  
    // No set_winner_result - void tasks have no result to store
704  
};
704  
};
705  

705  

706  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
706  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
707  
template<IoAwaitableRange Range>
707  
template<IoAwaitableRange Range>
708  
class when_any_homogeneous_launcher
708  
class when_any_homogeneous_launcher
709  
{
709  
{
710  
    using Awaitable = std::ranges::range_value_t<Range>;
710  
    using Awaitable = std::ranges::range_value_t<Range>;
711  
    using T = awaitable_result_t<Awaitable>;
711  
    using T = awaitable_result_t<Awaitable>;
712  

712  

713  
    Range* range_;
713  
    Range* range_;
714  
    when_any_homogeneous_state<T>* state_;
714  
    when_any_homogeneous_state<T>* state_;
715  

715  

716  
public:
716  
public:
717  
    when_any_homogeneous_launcher(
717  
    when_any_homogeneous_launcher(
718  
        Range* range,
718  
        Range* range,
719  
        when_any_homogeneous_state<T>* state)
719  
        when_any_homogeneous_state<T>* state)
720  
        : range_(range)
720  
        : range_(range)
721  
        , state_(state)
721  
        , state_(state)
722  
    {
722  
    {
723  
    }
723  
    }
724  

724  

725  
    bool await_ready() const noexcept
725  
    bool await_ready() const noexcept
726  
    {
726  
    {
727  
        return std::ranges::empty(*range_);
727  
        return std::ranges::empty(*range_);
728  
    }
728  
    }
729  

729  

730  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
730  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
731  
        destroys this object before await_suspend returns. Must not reference
731  
        destroys this object before await_suspend returns. Must not reference
732  
        `this` after dispatching begins.
732  
        `this` after dispatching begins.
733  

733  

734  
        Two-phase approach:
734  
        Two-phase approach:
735  
        1. Create all runners (safe - no dispatch yet)
735  
        1. Create all runners (safe - no dispatch yet)
736  
        2. Dispatch all runners (any may complete synchronously)
736  
        2. Dispatch all runners (any may complete synchronously)
737  
    */
737  
    */
738  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
738  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
739  
    {
739  
    {
740  
        state_->core_.continuation_ = continuation;
740  
        state_->core_.continuation_ = continuation;
741  
        state_->core_.caller_env_ = caller_env;
741  
        state_->core_.caller_env_ = caller_env;
742  

742  

743  
        if(caller_env->stop_token.stop_possible())
743  
        if(caller_env->stop_token.stop_possible())
744  
        {
744  
        {
745  
            state_->core_.parent_stop_callback_.emplace(
745  
            state_->core_.parent_stop_callback_.emplace(
746  
                caller_env->stop_token,
746  
                caller_env->stop_token,
747  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
747  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
748  

748  

749  
            if(caller_env->stop_token.stop_requested())
749  
            if(caller_env->stop_token.stop_requested())
750  
                state_->core_.stop_source_.request_stop();
750  
                state_->core_.stop_source_.request_stop();
751  
        }
751  
        }
752  

752  

753  
        auto token = state_->core_.stop_source_.get_token();
753  
        auto token = state_->core_.stop_source_.get_token();
754  

754  

755  
        // Phase 1: Create all runners without dispatching.
755  
        // Phase 1: Create all runners without dispatching.
756  
        // This iterates over *range_ safely because no runners execute yet.
756  
        // This iterates over *range_ safely because no runners execute yet.
757  
        std::size_t index = 0;
757  
        std::size_t index = 0;
758  
        for(auto&& a : *range_)
758  
        for(auto&& a : *range_)
759  
        {
759  
        {
760  
            auto runner = make_when_any_runner(
760  
            auto runner = make_when_any_runner(
761  
                std::move(a), state_, index);
761  
                std::move(a), state_, index);
762  

762  

763  
            auto h = runner.release();
763  
            auto h = runner.release();
764  
            h.promise().state_ = state_;
764  
            h.promise().state_ = state_;
765  
            h.promise().index_ = index;
765  
            h.promise().index_ = index;
766  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->allocator};
766  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->allocator};
767  

767  

768  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
768  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
769  
            ++index;
769  
            ++index;
770  
        }
770  
        }
771  

771  

772  
        // Phase 2: Post all runners. Any may complete synchronously.
772  
        // Phase 2: Post all runners. Any may complete synchronously.
773  
        // After last post, state_ and this may be destroyed.
773  
        // After last post, state_ and this may be destroyed.
774  
        // Use raw pointer/count captured before posting.
774  
        // Use raw pointer/count captured before posting.
775  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
775  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
776  
        std::size_t count = state_->runner_handles_.size();
776  
        std::size_t count = state_->runner_handles_.size();
777  
        for(std::size_t i = 0; i < count; ++i)
777  
        for(std::size_t i = 0; i < count; ++i)
778  
            caller_env->executor.post(handles[i]);
778  
            caller_env->executor.post(handles[i]);
779  

779  

780  
        return std::noop_coroutine();
780  
        return std::noop_coroutine();
781  
    }
781  
    }
782  

782  

783  
    void await_resume() const noexcept
783  
    void await_resume() const noexcept
784  
    {
784  
    {
785  
    }
785  
    }
786  
};
786  
};
787  

787  

788  
} // namespace detail
788  
} // namespace detail
789  

789  

790  
/** Wait for the first awaitable to complete (range overload).
790  
/** Wait for the first awaitable to complete (range overload).
791  

791  

792  
    Races a range of awaitables with the same result type. Accepts any
792  
    Races a range of awaitables with the same result type. Accepts any
793  
    sized input range of IoAwaitable types, enabling use with arrays,
793  
    sized input range of IoAwaitable types, enabling use with arrays,
794  
    spans, or custom containers.
794  
    spans, or custom containers.
795  

795  

796  
    @par Suspends
796  
    @par Suspends
797  
    The calling coroutine suspends when co_await is invoked. All awaitables
797  
    The calling coroutine suspends when co_await is invoked. All awaitables
798  
    in the range are launched concurrently and execute in parallel. The
798  
    in the range are launched concurrently and execute in parallel. The
799  
    coroutine resumes only after all awaitables have completed, even though
799  
    coroutine resumes only after all awaitables have completed, even though
800  
    the winner is determined by the first to finish.
800  
    the winner is determined by the first to finish.
801  

801  

802  
    @par Completion Conditions
802  
    @par Completion Conditions
803  
    @li Winner is determined when the first awaitable completes (success or exception)
803  
    @li Winner is determined when the first awaitable completes (success or exception)
804  
    @li Only one task can claim winner status via atomic compare-exchange
804  
    @li Only one task can claim winner status via atomic compare-exchange
805  
    @li Once a winner exists, stop is requested for all remaining siblings
805  
    @li Once a winner exists, stop is requested for all remaining siblings
806  
    @li Parent coroutine resumes only after all siblings acknowledge completion
806  
    @li Parent coroutine resumes only after all siblings acknowledge completion
807  
    @li The winner's index and result are returned; if the winner threw, the exception is rethrown
807  
    @li The winner's index and result are returned; if the winner threw, the exception is rethrown
808  

808  

809  
    @par Cancellation Semantics
809  
    @par Cancellation Semantics
810  
    Cancellation is supported via stop_token propagated through the
810  
    Cancellation is supported via stop_token propagated through the
811  
    IoAwaitable protocol:
811  
    IoAwaitable protocol:
812  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
812  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
813  
    @li When the parent's stop token is activated, the stop is forwarded to all children
813  
    @li When the parent's stop token is activated, the stop is forwarded to all children
814  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
814  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
815  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
815  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
816  
    @li Stop requests are cooperative; tasks must check and respond to them
816  
    @li Stop requests are cooperative; tasks must check and respond to them
817  

817  

818  
    @par Concurrency/Overlap
818  
    @par Concurrency/Overlap
819  
    All awaitables are launched concurrently before any can complete.
819  
    All awaitables are launched concurrently before any can complete.
820  
    The launcher iterates through the range, starting each task on the
820  
    The launcher iterates through the range, starting each task on the
821  
    caller's executor. Tasks may execute in parallel on multi-threaded
821  
    caller's executor. Tasks may execute in parallel on multi-threaded
822  
    executors or interleave on single-threaded executors. There is no
822  
    executors or interleave on single-threaded executors. There is no
823  
    guaranteed ordering of task completion.
823  
    guaranteed ordering of task completion.
824  

824  

825  
    @par Notable Error Conditions
825  
    @par Notable Error Conditions
826  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
826  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
827  
    @li Winner exception: if the winning task threw, that exception is rethrown
827  
    @li Winner exception: if the winning task threw, that exception is rethrown
828  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
828  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
829  
    @li Cancellation: tasks may complete via cancellation without throwing
829  
    @li Cancellation: tasks may complete via cancellation without throwing
830  

830  

831  
    @par Example
831  
    @par Example
832  
    @code
832  
    @code
833  
    task<void> example() {
833  
    task<void> example() {
834  
        std::array<task<Response>, 3> requests = {
834  
        std::array<task<Response>, 3> requests = {
835  
            fetch_from_server(0),
835  
            fetch_from_server(0),
836  
            fetch_from_server(1),
836  
            fetch_from_server(1),
837  
            fetch_from_server(2)
837  
            fetch_from_server(2)
838  
        };
838  
        };
839  

839  

840  
        auto [index, response] = co_await when_any(std::move(requests));
840  
        auto [index, response] = co_await when_any(std::move(requests));
841  
    }
841  
    }
842  
    @endcode
842  
    @endcode
843  

843  

844  
    @par Example with Vector
844  
    @par Example with Vector
845  
    @code
845  
    @code
846  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
846  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
847  
        std::vector<task<Response>> requests;
847  
        std::vector<task<Response>> requests;
848  
        for (auto const& server : servers)
848  
        for (auto const& server : servers)
849  
            requests.push_back(fetch_from(server));
849  
            requests.push_back(fetch_from(server));
850  

850  

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

855  

856  
    @tparam R Range type satisfying IoAwaitableRange.
856  
    @tparam R Range type satisfying IoAwaitableRange.
857  
    @param awaitables Range of awaitables to race concurrently (must not be empty).
857  
    @param awaitables Range of awaitables to race concurrently (must not be empty).
858  
    @return A task yielding a pair of (winner_index, result).
858  
    @return A task yielding a pair of (winner_index, result).
859  

859  

860  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
860  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
861  
    @throws Rethrows the winner's exception if the winning task threw an exception.
861  
    @throws Rethrows the winner's exception if the winning task threw an exception.
862  

862  

863  
    @par Remarks
863  
    @par Remarks
864  
    Elements are moved from the range; for lvalue ranges, the original
864  
    Elements are moved from the range; for lvalue ranges, the original
865  
    container will have moved-from elements after this call. The range
865  
    container will have moved-from elements after this call. The range
866  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
866  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
867  
    the variadic overload, no variant wrapper is needed since all tasks
867  
    the variadic overload, no variant wrapper is needed since all tasks
868  
    share the same return type.
868  
    share the same return type.
869  

869  

870  
    @see when_any, IoAwaitableRange
870  
    @see when_any, IoAwaitableRange
871  
*/
871  
*/
872  
template<IoAwaitableRange R>
872  
template<IoAwaitableRange R>
873  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
873  
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
874  
[[nodiscard]] auto when_any(R&& awaitables)
874  
[[nodiscard]] auto when_any(R&& awaitables)
875  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
875  
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
876  
{
876  
{
877  
    using Awaitable = std::ranges::range_value_t<R>;
877  
    using Awaitable = std::ranges::range_value_t<R>;
878  
    using T = detail::awaitable_result_t<Awaitable>;
878  
    using T = detail::awaitable_result_t<Awaitable>;
879  
    using result_type = std::pair<std::size_t, T>;
879  
    using result_type = std::pair<std::size_t, T>;
880  
    using OwnedRange = std::remove_cvref_t<R>;
880  
    using OwnedRange = std::remove_cvref_t<R>;
881  

881  

882  
    auto count = std::ranges::size(awaitables);
882  
    auto count = std::ranges::size(awaitables);
883  
    if(count == 0)
883  
    if(count == 0)
884  
        throw std::invalid_argument("when_any requires at least one awaitable");
884  
        throw std::invalid_argument("when_any requires at least one awaitable");
885  

885  

886  
    // Move/copy range onto coroutine frame to ensure lifetime
886  
    // Move/copy range onto coroutine frame to ensure lifetime
887  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
887  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
888  

888  

889  
    detail::when_any_homogeneous_state<T> state(count);
889  
    detail::when_any_homogeneous_state<T> state(count);
890  

890  

891  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
891  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
892  

892  

893  
    if(state.core_.winner_exception_)
893  
    if(state.core_.winner_exception_)
894  
        std::rethrow_exception(state.core_.winner_exception_);
894  
        std::rethrow_exception(state.core_.winner_exception_);
895  

895  

896  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
896  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
897  
}
897  
}
898  

898  

899  
/** Wait for the first awaitable to complete (void range overload).
899  
/** Wait for the first awaitable to complete (void range overload).
900  

900  

901  
    Races a range of void-returning awaitables. Since void awaitables have
901  
    Races a range of void-returning awaitables. Since void awaitables have
902  
    no result value, only the winner's index is returned.
902  
    no result value, only the winner's index is returned.
903  

903  

904  
    @par Suspends
904  
    @par Suspends
905  
    The calling coroutine suspends when co_await is invoked. All awaitables
905  
    The calling coroutine suspends when co_await is invoked. All awaitables
906  
    in the range are launched concurrently and execute in parallel. The
906  
    in the range are launched concurrently and execute in parallel. The
907  
    coroutine resumes only after all awaitables have completed, even though
907  
    coroutine resumes only after all awaitables have completed, even though
908  
    the winner is determined by the first to finish.
908  
    the winner is determined by the first to finish.
909  

909  

910  
    @par Completion Conditions
910  
    @par Completion Conditions
911  
    @li Winner is determined when the first awaitable completes (success or exception)
911  
    @li Winner is determined when the first awaitable completes (success or exception)
912  
    @li Only one task can claim winner status via atomic compare-exchange
912  
    @li Only one task can claim winner status via atomic compare-exchange
913  
    @li Once a winner exists, stop is requested for all remaining siblings
913  
    @li Once a winner exists, stop is requested for all remaining siblings
914  
    @li Parent coroutine resumes only after all siblings acknowledge completion
914  
    @li Parent coroutine resumes only after all siblings acknowledge completion
915  
    @li The winner's index is returned; if the winner threw, the exception is rethrown
915  
    @li The winner's index is returned; if the winner threw, the exception is rethrown
916  

916  

917  
    @par Cancellation Semantics
917  
    @par Cancellation Semantics
918  
    Cancellation is supported via stop_token propagated through the
918  
    Cancellation is supported via stop_token propagated through the
919  
    IoAwaitable protocol:
919  
    IoAwaitable protocol:
920  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
920  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
921  
    @li When the parent's stop token is activated, the stop is forwarded to all children
921  
    @li When the parent's stop token is activated, the stop is forwarded to all children
922  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
922  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
923  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
923  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
924  
    @li Stop requests are cooperative; tasks must check and respond to them
924  
    @li Stop requests are cooperative; tasks must check and respond to them
925  

925  

926  
    @par Concurrency/Overlap
926  
    @par Concurrency/Overlap
927  
    All awaitables are launched concurrently before any can complete.
927  
    All awaitables are launched concurrently before any can complete.
928  
    The launcher iterates through the range, starting each task on the
928  
    The launcher iterates through the range, starting each task on the
929  
    caller's executor. Tasks may execute in parallel on multi-threaded
929  
    caller's executor. Tasks may execute in parallel on multi-threaded
930  
    executors or interleave on single-threaded executors. There is no
930  
    executors or interleave on single-threaded executors. There is no
931  
    guaranteed ordering of task completion.
931  
    guaranteed ordering of task completion.
932  

932  

933  
    @par Notable Error Conditions
933  
    @par Notable Error Conditions
934  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
934  
    @li Empty range: throws std::invalid_argument immediately (not via co_return)
935  
    @li Winner exception: if the winning task threw, that exception is rethrown
935  
    @li Winner exception: if the winning task threw, that exception is rethrown
936  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
936  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
937  
    @li Cancellation: tasks may complete via cancellation without throwing
937  
    @li Cancellation: tasks may complete via cancellation without throwing
938  

938  

939  
    @par Example
939  
    @par Example
940  
    @code
940  
    @code
941  
    task<void> example() {
941  
    task<void> example() {
942  
        std::vector<task<void>> tasks;
942  
        std::vector<task<void>> tasks;
943  
        for (int i = 0; i < 5; ++i)
943  
        for (int i = 0; i < 5; ++i)
944  
            tasks.push_back(background_work(i));
944  
            tasks.push_back(background_work(i));
945  

945  

946  
        std::size_t winner = co_await when_any(std::move(tasks));
946  
        std::size_t winner = co_await when_any(std::move(tasks));
947  
        // winner is the index of the first task to complete
947  
        // winner is the index of the first task to complete
948  
    }
948  
    }
949  
    @endcode
949  
    @endcode
950  

950  

951  
    @par Example with Timeout
951  
    @par Example with Timeout
952  
    @code
952  
    @code
953  
    task<void> with_timeout() {
953  
    task<void> with_timeout() {
954  
        std::vector<task<void>> tasks;
954  
        std::vector<task<void>> tasks;
955  
        tasks.push_back(long_running_operation());
955  
        tasks.push_back(long_running_operation());
956  
        tasks.push_back(delay(std::chrono::seconds(5)));
956  
        tasks.push_back(delay(std::chrono::seconds(5)));
957  

957  

958  
        std::size_t winner = co_await when_any(std::move(tasks));
958  
        std::size_t winner = co_await when_any(std::move(tasks));
959  
        if (winner == 1) {
959  
        if (winner == 1) {
960  
            // Timeout occurred
960  
            // Timeout occurred
961  
        }
961  
        }
962  
    }
962  
    }
963  
    @endcode
963  
    @endcode
964  

964  

965  
    @tparam R Range type satisfying IoAwaitableRange with void result.
965  
    @tparam R Range type satisfying IoAwaitableRange with void result.
966  
    @param awaitables Range of void awaitables to race concurrently (must not be empty).
966  
    @param awaitables Range of void awaitables to race concurrently (must not be empty).
967  
    @return A task yielding the winner's index (zero-based).
967  
    @return A task yielding the winner's index (zero-based).
968  

968  

969  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
969  
    @throws std::invalid_argument if range is empty (thrown before coroutine suspends).
970  
    @throws Rethrows the winner's exception if the winning task threw an exception.
970  
    @throws Rethrows the winner's exception if the winning task threw an exception.
971  

971  

972  
    @par Remarks
972  
    @par Remarks
973  
    Elements are moved from the range; for lvalue ranges, the original
973  
    Elements are moved from the range; for lvalue ranges, the original
974  
    container will have moved-from elements after this call. The range
974  
    container will have moved-from elements after this call. The range
975  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
975  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
976  
    the non-void overload, no result storage is needed since void tasks
976  
    the non-void overload, no result storage is needed since void tasks
977  
    produce no value.
977  
    produce no value.
978  

978  

979  
    @see when_any, IoAwaitableRange
979  
    @see when_any, IoAwaitableRange
980  
*/
980  
*/
981  
template<IoAwaitableRange R>
981  
template<IoAwaitableRange R>
982  
    requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
982  
    requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
983  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
983  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
984  
{
984  
{
985  
    using OwnedRange = std::remove_cvref_t<R>;
985  
    using OwnedRange = std::remove_cvref_t<R>;
986  

986  

987  
    auto count = std::ranges::size(awaitables);
987  
    auto count = std::ranges::size(awaitables);
988  
    if(count == 0)
988  
    if(count == 0)
989  
        throw std::invalid_argument("when_any requires at least one awaitable");
989  
        throw std::invalid_argument("when_any requires at least one awaitable");
990  

990  

991  
    // Move/copy range onto coroutine frame to ensure lifetime
991  
    // Move/copy range onto coroutine frame to ensure lifetime
992  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
992  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
993  

993  

994  
    detail::when_any_homogeneous_state<void> state(count);
994  
    detail::when_any_homogeneous_state<void> state(count);
995  

995  

996  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
996  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
997  

997  

998  
    if(state.core_.winner_exception_)
998  
    if(state.core_.winner_exception_)
999  
        std::rethrow_exception(state.core_.winner_exception_);
999  
        std::rethrow_exception(state.core_.winner_exception_);
1000  

1000  

1001  
    co_return state.core_.winner_index_;
1001  
    co_return state.core_.winner_index_;
1002  
}
1002  
}
1003  

1003  

1004  
} // namespace capy
1004  
} // namespace capy
1005  
} // namespace boost
1005  
} // namespace boost
1006  

1006  

1007  
#endif
1007  
#endif