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  
   POSITIONAL VARIANT:
65  
   POSITIONAL VARIANT:
66  
   -------------------
66  
   -------------------
67  
   The variadic overload returns a std::variant with one alternative per
67  
   The variadic overload returns a std::variant with one alternative per
68  
   input task, preserving positional correspondence. Use .index() on
68  
   input task, preserving positional correspondence. Use .index() on
69  
   the variant to identify which task won.
69  
   the variant to identify which task won.
70  

70  

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

75  

76  
   VOID HANDLING:
76  
   VOID HANDLING:
77  
   --------------
77  
   --------------
78  
   void tasks contribute std::monostate to the variant.
78  
   void tasks contribute std::monostate to the variant.
79  
   All-void tasks result in: variant<monostate, monostate, monostate>
79  
   All-void tasks result in: variant<monostate, monostate, monostate>
80  

80  

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

84  

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

91  

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

101  

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

107  

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

114  

115  
namespace boost {
115  
namespace boost {
116  
namespace capy {
116  
namespace capy {
117  

117  

 
118 +
namespace detail {
 
119 +

118  
/** Convert void to monostate for variant storage.
120  
/** Convert void to monostate for variant storage.
119  

121  

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

125  

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

130  

129 -
namespace detail {
131 +
// Result variant: one alternative per task, preserving positional
 
132 +
// correspondence. Use .index() to identify which task won.
 
133 +
// void results become monostate.
 
134 +
template<typename T0, typename... Ts>
 
135 +
using when_any_variant_t = std::variant<void_to_monostate_t<T0>, void_to_monostate_t<Ts>...>;
130  

136  

131  
/** Core shared state for when_any operations.
137  
/** Core shared state for when_any operations.
132  

138  

133  
    Contains all members and methods common to both heterogeneous (variadic)
139  
    Contains all members and methods common to both heterogeneous (variadic)
134  
    and homogeneous (range) when_any implementations. State classes embed
140  
    and homogeneous (range) when_any implementations. State classes embed
135  
    this via composition to avoid CRTP destructor ordering issues.
141  
    this via composition to avoid CRTP destructor ordering issues.
136  

142  

137  
    @par Thread Safety
143  
    @par Thread Safety
138  
    Atomic operations protect winner selection and completion count.
144  
    Atomic operations protect winner selection and completion count.
139  
*/
145  
*/
140  
struct when_any_core
146  
struct when_any_core
141  
{
147  
{
142  
    std::atomic<std::size_t> remaining_count_;
148  
    std::atomic<std::size_t> remaining_count_;
143  
    std::size_t winner_index_{0};
149  
    std::size_t winner_index_{0};
144  
    std::exception_ptr winner_exception_;
150  
    std::exception_ptr winner_exception_;
145  
    std::stop_source stop_source_;
151  
    std::stop_source stop_source_;
146  

152  

147  
    // Bridges parent's stop token to our stop_source
153  
    // Bridges parent's stop token to our stop_source
148  
    struct stop_callback_fn
154  
    struct stop_callback_fn
149  
    {
155  
    {
150  
        std::stop_source* source_;
156  
        std::stop_source* source_;
151  
        void operator()() const noexcept { source_->request_stop(); }
157  
        void operator()() const noexcept { source_->request_stop(); }
152  
    };
158  
    };
153  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
159  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
154  
    std::optional<stop_callback_t> parent_stop_callback_;
160  
    std::optional<stop_callback_t> parent_stop_callback_;
155  

161  

156  
    std::coroutine_handle<> continuation_;
162  
    std::coroutine_handle<> continuation_;
157  
    io_env const* caller_env_ = nullptr;
163  
    io_env const* caller_env_ = nullptr;
158  

164  

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

167  

162  
    explicit when_any_core(std::size_t count) noexcept
168  
    explicit when_any_core(std::size_t count) noexcept
163  
        : remaining_count_(count)
169  
        : remaining_count_(count)
164  
    {
170  
    {
165  
    }
171  
    }
166  

172  

167  
    /** Atomically claim winner status; exactly one task succeeds. */
173  
    /** Atomically claim winner status; exactly one task succeeds. */
168  
    bool try_win(std::size_t index) noexcept
174  
    bool try_win(std::size_t index) noexcept
169  
    {
175  
    {
170  
        bool expected = false;
176  
        bool expected = false;
171  
        if(has_winner_.compare_exchange_strong(
177  
        if(has_winner_.compare_exchange_strong(
172  
            expected, true, std::memory_order_acq_rel))
178  
            expected, true, std::memory_order_acq_rel))
173  
        {
179  
        {
174  
            winner_index_ = index;
180  
            winner_index_ = index;
175  
            stop_source_.request_stop();
181  
            stop_source_.request_stop();
176  
            return true;
182  
            return true;
177  
        }
183  
        }
178  
        return false;
184  
        return false;
179  
    }
185  
    }
180  

186  

181  
    /** @pre try_win() returned true. */
187  
    /** @pre try_win() returned true. */
182  
    void set_winner_exception(std::exception_ptr ep) noexcept
188  
    void set_winner_exception(std::exception_ptr ep) noexcept
183  
    {
189  
    {
184  
        winner_exception_ = ep;
190  
        winner_exception_ = ep;
185  
    }
191  
    }
186  

192  

187  
    // Runners signal completion directly via final_suspend; no member function needed.
193  
    // Runners signal completion directly via final_suspend; no member function needed.
188  
};
194  
};
189  

195  

190  
/** Shared state for heterogeneous when_any operation.
196  
/** Shared state for heterogeneous when_any operation.
191  

197  

192  
    Coordinates winner selection, result storage, and completion tracking
198  
    Coordinates winner selection, result storage, and completion tracking
193  
    for all child tasks in a when_any operation. Uses composition with
199  
    for all child tasks in a when_any operation. Uses composition with
194  
    when_any_core for shared functionality.
200  
    when_any_core for shared functionality.
195  

201  

196  
    @par Lifetime
202  
    @par Lifetime
197  
    Allocated on the parent coroutine's frame, outlives all runners.
203  
    Allocated on the parent coroutine's frame, outlives all runners.
198  

204  

199 -
    @tparam Ts Task result types.
205 +
    @tparam T0 First task's result type.
 
206 +
    @tparam Ts Remaining tasks' result types.
200  
*/
207  
*/
201 -
template<typename... Ts>
208 +
template<typename T0, typename... Ts>
202  
struct when_any_state
209  
struct when_any_state
203  
{
210  
{
204 -
    static constexpr std::size_t task_count = sizeof...(Ts);
211 +
    static constexpr std::size_t task_count = 1 + sizeof...(Ts);
205 -
    using variant_type = std::variant<void_to_monostate_t<Ts>...>;
212 +
    using variant_type = when_any_variant_t<T0, Ts...>;
206  

213  

207  
    when_any_core core_;
214  
    when_any_core core_;
208  
    std::optional<variant_type> result_;
215  
    std::optional<variant_type> result_;
209  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
216  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
210  

217  

211  
    when_any_state()
218  
    when_any_state()
212  
        : core_(task_count)
219  
        : core_(task_count)
213  
    {
220  
    {
214  
    }
221  
    }
215  

222  

216  
    // Runners self-destruct in final_suspend. No destruction needed here.
223  
    // Runners self-destruct in final_suspend. No destruction needed here.
217  

224  

218  
    /** @pre core_.try_win() returned true.
225  
    /** @pre core_.try_win() returned true.
219  
        @note Uses in_place_index (not type) for positional variant access.
226  
        @note Uses in_place_index (not type) for positional variant access.
220  
    */
227  
    */
221  
    template<std::size_t I, typename T>
228  
    template<std::size_t I, typename T>
222  
    void set_winner_result(T value)
229  
    void set_winner_result(T value)
223  
        noexcept(std::is_nothrow_move_constructible_v<T>)
230  
        noexcept(std::is_nothrow_move_constructible_v<T>)
224  
    {
231  
    {
225  
        result_.emplace(std::in_place_index<I>, std::move(value));
232  
        result_.emplace(std::in_place_index<I>, std::move(value));
226  
    }
233  
    }
227  

234  

228  
    /** @pre core_.try_win() returned true. */
235  
    /** @pre core_.try_win() returned true. */
229  
    template<std::size_t I>
236  
    template<std::size_t I>
230  
    void set_winner_void() noexcept
237  
    void set_winner_void() noexcept
231  
    {
238  
    {
232  
        result_.emplace(std::in_place_index<I>, std::monostate{});
239  
        result_.emplace(std::in_place_index<I>, std::monostate{});
233  
    }
240  
    }
234  
};
241  
};
235  

242  

236  
/** Wrapper coroutine that runs a single child task for when_any.
243  
/** Wrapper coroutine that runs a single child task for when_any.
237  

244  

238  
    Propagates executor/stop_token to the child, attempts to claim winner
245  
    Propagates executor/stop_token to the child, attempts to claim winner
239  
    status on completion, and signals completion for cleanup coordination.
246  
    status on completion, and signals completion for cleanup coordination.
240  

247  

241  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
248  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
242  
*/
249  
*/
243  
template<typename StateType>
250  
template<typename StateType>
244  
struct when_any_runner
251  
struct when_any_runner
245  
{
252  
{
246  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
253  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
247  
    {
254  
    {
248  
        StateType* state_ = nullptr;
255  
        StateType* state_ = nullptr;
249  
        std::size_t index_ = 0;
256  
        std::size_t index_ = 0;
250  
        io_env env_;
257  
        io_env env_;
251  

258  

252  
        when_any_runner get_return_object() noexcept
259  
        when_any_runner get_return_object() noexcept
253  
        {
260  
        {
254  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
261  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
255  
        }
262  
        }
256  

263  

257  
        // Starts suspended; launcher sets up state/ex/token then resumes
264  
        // Starts suspended; launcher sets up state/ex/token then resumes
258  
        std::suspend_always initial_suspend() noexcept
265  
        std::suspend_always initial_suspend() noexcept
259  
        {
266  
        {
260  
            return {};
267  
            return {};
261  
        }
268  
        }
262  

269  

263  
        auto final_suspend() noexcept
270  
        auto final_suspend() noexcept
264  
        {
271  
        {
265  
            struct awaiter
272  
            struct awaiter
266  
            {
273  
            {
267  
                promise_type* p_;
274  
                promise_type* p_;
268  
                bool await_ready() const noexcept { return false; }
275  
                bool await_ready() const noexcept { return false; }
269 -
                auto await_suspend(std::coroutine_handle<> h) noexcept
276 +
                std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept
270  
                {
277  
                {
271  
                    // Extract everything needed before self-destruction.
278  
                    // Extract everything needed before self-destruction.
272  
                    auto& core = p_->state_->core_;
279  
                    auto& core = p_->state_->core_;
273  
                    auto* counter = &core.remaining_count_;
280  
                    auto* counter = &core.remaining_count_;
274  
                    auto* caller_env = core.caller_env_;
281  
                    auto* caller_env = core.caller_env_;
275  
                    auto cont = core.continuation_;
282  
                    auto cont = core.continuation_;
276  

283  

277  
                    h.destroy();
284  
                    h.destroy();
278  

285  

279  
                    // If last runner, dispatch parent for symmetric transfer.
286  
                    // If last runner, dispatch parent for symmetric transfer.
280  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
287  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
281  
                    if(remaining == 1)
288  
                    if(remaining == 1)
282 -
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
289 +
                        return caller_env->executor.dispatch(cont);
283 -
                    return detail::symmetric_transfer(std::noop_coroutine());
290 +
                    return std::noop_coroutine();
284  
                }
291  
                }
285  
                void await_resume() const noexcept {}
292  
                void await_resume() const noexcept {}
286  
            };
293  
            };
287  
            return awaiter{this};
294  
            return awaiter{this};
288  
        }
295  
        }
289  

296  

290  
        void return_void() noexcept {}
297  
        void return_void() noexcept {}
291  

298  

292  
        // Exceptions are valid completions in when_any (unlike when_all)
299  
        // Exceptions are valid completions in when_any (unlike when_all)
293  
        void unhandled_exception()
300  
        void unhandled_exception()
294  
        {
301  
        {
295  
            if(state_->core_.try_win(index_))
302  
            if(state_->core_.try_win(index_))
296  
                state_->core_.set_winner_exception(std::current_exception());
303  
                state_->core_.set_winner_exception(std::current_exception());
297  
        }
304  
        }
298  

305  

299  
        /** Injects executor and stop token into child awaitables. */
306  
        /** Injects executor and stop token into child awaitables. */
300  
        template<class Awaitable>
307  
        template<class Awaitable>
301  
        struct transform_awaiter
308  
        struct transform_awaiter
302  
        {
309  
        {
303  
            std::decay_t<Awaitable> a_;
310  
            std::decay_t<Awaitable> a_;
304  
            promise_type* p_;
311  
            promise_type* p_;
305  

312  

306  
            bool await_ready() { return a_.await_ready(); }
313  
            bool await_ready() { return a_.await_ready(); }
307  
            auto await_resume() { return a_.await_resume(); }
314  
            auto await_resume() { return a_.await_resume(); }
308  

315  

309  
            template<class Promise>
316  
            template<class Promise>
310  
            auto await_suspend(std::coroutine_handle<Promise> h)
317  
            auto await_suspend(std::coroutine_handle<Promise> h)
311  
            {
318  
            {
 
319 +
#ifdef _MSC_VER
312  
                using R = decltype(a_.await_suspend(h, &p_->env_));
320  
                using R = decltype(a_.await_suspend(h, &p_->env_));
313  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
321  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
314 -
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
322 +
                    a_.await_suspend(h, &p_->env_).resume();
315  
                else
323  
                else
316  
                    return a_.await_suspend(h, &p_->env_);
324  
                    return a_.await_suspend(h, &p_->env_);
 
325 +
#else
 
326 +
                return a_.await_suspend(h, &p_->env_);
 
327 +
#endif
317  
            }
328  
            }
318  
        };
329  
        };
319  

330  

320  
        template<class Awaitable>
331  
        template<class Awaitable>
321  
        auto await_transform(Awaitable&& a)
332  
        auto await_transform(Awaitable&& a)
322  
        {
333  
        {
323  
            using A = std::decay_t<Awaitable>;
334  
            using A = std::decay_t<Awaitable>;
324  
            if constexpr (IoAwaitable<A>)
335  
            if constexpr (IoAwaitable<A>)
325  
            {
336  
            {
326  
                return transform_awaiter<Awaitable>{
337  
                return transform_awaiter<Awaitable>{
327  
                    std::forward<Awaitable>(a), this};
338  
                    std::forward<Awaitable>(a), this};
328  
            }
339  
            }
329  
            else
340  
            else
330  
            {
341  
            {
331  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
342  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
332  
            }
343  
            }
333  
        }
344  
        }
334  
    };
345  
    };
335  

346  

336  
    std::coroutine_handle<promise_type> h_;
347  
    std::coroutine_handle<promise_type> h_;
337  

348  

338  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
349  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
339  
        : h_(h)
350  
        : h_(h)
340  
    {
351  
    {
341  
    }
352  
    }
342  

353  

343  
    // Enable move for all clang versions - some versions need it
354  
    // Enable move for all clang versions - some versions need it
344  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
355  
    when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
345  

356  

346  
    // Non-copyable
357  
    // Non-copyable
347  
    when_any_runner(when_any_runner const&) = delete;
358  
    when_any_runner(when_any_runner const&) = delete;
348  
    when_any_runner& operator=(when_any_runner const&) = delete;
359  
    when_any_runner& operator=(when_any_runner const&) = delete;
349  
    when_any_runner& operator=(when_any_runner&&) = delete;
360  
    when_any_runner& operator=(when_any_runner&&) = delete;
350  

361  

351  
    auto release() noexcept
362  
    auto release() noexcept
352  
    {
363  
    {
353  
        return std::exchange(h_, nullptr);
364  
        return std::exchange(h_, nullptr);
354  
    }
365  
    }
355  
};
366  
};
356  

367  

357  
/** Indexed overload for heterogeneous when_any (compile-time index).
368  
/** Indexed overload for heterogeneous when_any (compile-time index).
358  

369  

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

400  

390  
/** Runtime-index overload for homogeneous when_any (range path).
401  
/** Runtime-index overload for homogeneous when_any (range path).
391  

402  

392  
    Uses requires-expressions to detect state capabilities:
403  
    Uses requires-expressions to detect state capabilities:
393  
    - set_winner_void(): for heterogeneous void tasks (stores monostate)
404  
    - set_winner_void(): for heterogeneous void tasks (stores monostate)
394  
    - set_winner_result(): for non-void tasks
405  
    - set_winner_result(): for non-void tasks
395  
    - Neither: for homogeneous void tasks (no result storage)
406  
    - Neither: for homogeneous void tasks (no result storage)
396  
*/
407  
*/
397  
template<IoAwaitable Awaitable, typename StateType>
408  
template<IoAwaitable Awaitable, typename StateType>
398  
when_any_runner<StateType>
409  
when_any_runner<StateType>
399  
make_when_any_runner(Awaitable inner, StateType* state, std::size_t index)
410  
make_when_any_runner(Awaitable inner, StateType* state, std::size_t index)
400  
{
411  
{
401  
    using T = awaitable_result_t<Awaitable>;
412  
    using T = awaitable_result_t<Awaitable>;
402  
    if constexpr (std::is_void_v<T>)
413  
    if constexpr (std::is_void_v<T>)
403  
    {
414  
    {
404  
        co_await std::move(inner);
415  
        co_await std::move(inner);
405  
        if(state->core_.try_win(index))
416  
        if(state->core_.try_win(index))
406  
        {
417  
        {
407  
            if constexpr (requires { state->set_winner_void(); })
418  
            if constexpr (requires { state->set_winner_void(); })
408  
                state->set_winner_void();
419  
                state->set_winner_void();
409  
        }
420  
        }
410  
    }
421  
    }
411  
    else
422  
    else
412  
    {
423  
    {
413  
        auto result = co_await std::move(inner);
424  
        auto result = co_await std::move(inner);
414  
        if(state->core_.try_win(index))
425  
        if(state->core_.try_win(index))
415  
        {
426  
        {
416  
            try
427  
            try
417  
            {
428  
            {
418  
                state->set_winner_result(std::move(result));
429  
                state->set_winner_result(std::move(result));
419  
            }
430  
            }
420  
            catch(...)
431  
            catch(...)
421  
            {
432  
            {
422  
                state->core_.set_winner_exception(std::current_exception());
433  
                state->core_.set_winner_exception(std::current_exception());
423  
            }
434  
            }
424  
        }
435  
        }
425  
    }
436  
    }
426  
}
437  
}
427  

438  

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

444  

434  
    std::tuple<Awaitables...>* tasks_;
445  
    std::tuple<Awaitables...>* tasks_;
435  
    state_type* state_;
446  
    state_type* state_;
436  

447  

437  
public:
448  
public:
438  
    when_any_launcher(
449  
    when_any_launcher(
439  
        std::tuple<Awaitables...>* tasks,
450  
        std::tuple<Awaitables...>* tasks,
440  
        state_type* state)
451  
        state_type* state)
441  
        : tasks_(tasks)
452  
        : tasks_(tasks)
442  
        , state_(state)
453  
        , state_(state)
443  
    {
454  
    {
444  
    }
455  
    }
445  

456  

446  
    bool await_ready() const noexcept
457  
    bool await_ready() const noexcept
447  
    {
458  
    {
448  
        return sizeof...(Awaitables) == 0;
459  
        return sizeof...(Awaitables) == 0;
449  
    }
460  
    }
450  

461  

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

470  

460  
        if(caller_env->stop_token.stop_possible())
471  
        if(caller_env->stop_token.stop_possible())
461  
        {
472  
        {
462  
            state_->core_.parent_stop_callback_.emplace(
473  
            state_->core_.parent_stop_callback_.emplace(
463  
                caller_env->stop_token,
474  
                caller_env->stop_token,
464  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
475  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
465  

476  

466  
            if(caller_env->stop_token.stop_requested())
477  
            if(caller_env->stop_token.stop_requested())
467  
                state_->core_.stop_source_.request_stop();
478  
                state_->core_.stop_source_.request_stop();
468  
        }
479  
        }
469  

480  

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

485  

475  
        return std::noop_coroutine();
486  
        return std::noop_coroutine();
476  
    }
487  
    }
477  

488  

478  
    void await_resume() const noexcept
489  
    void await_resume() const noexcept
479  
    {
490  
    {
480  
    }
491  
    }
481  

492  

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

500  

490  
        auto h = runner.release();
501  
        auto h = runner.release();
491  
        h.promise().state_ = state_;
502  
        h.promise().state_ = state_;
492  
        h.promise().index_ = I;
503  
        h.promise().index_ = I;
493  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
504  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
494  

505  

495  
        std::coroutine_handle<> ch{h};
506  
        std::coroutine_handle<> ch{h};
496  
        state_->runner_handles_[I] = ch;
507  
        state_->runner_handles_[I] = ch;
497  
        caller_ex.post(ch);
508  
        caller_ex.post(ch);
498  
    }
509  
    }
499  
};
510  
};
500  

511  

501  
} // namespace detail
512  
} // namespace detail
502  

513  

503  
/** Wait for the first awaitable to complete.
514  
/** Wait for the first awaitable to complete.
504  

515  

505  
    Races multiple heterogeneous awaitables concurrently and returns when the
516  
    Races multiple heterogeneous awaitables concurrently and returns when the
506  
    first one completes. The result is a variant with one alternative per
517  
    first one completes. The result is a variant with one alternative per
507  
    input task, preserving positional correspondence.
518  
    input task, preserving positional correspondence.
508  

519  

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

525  

515  
    @par Completion Conditions
526  
    @par Completion Conditions
516  
    @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)
517  
    @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
518  
    @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
519  
    @li Parent coroutine resumes only after all siblings acknowledge completion
530  
    @li Parent coroutine resumes only after all siblings acknowledge completion
520  
    @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
521  

532  

522  
    @par Cancellation Semantics
533  
    @par Cancellation Semantics
523  
    Cancellation is supported via stop_token propagated through the
534  
    Cancellation is supported via stop_token propagated through the
524  
    IoAwaitable protocol:
535  
    IoAwaitable protocol:
525  
    @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
526  
    @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
527  
    @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
528  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
539  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
529  
    @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
530  

541  

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

548  

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

553  

543  
    @par Example
554  
    @par Example
544  
    @code
555  
    @code
545  
    task<void> example() {
556  
    task<void> example() {
546  
        auto result = co_await when_any(
557  
        auto result = co_await when_any(
547  
            fetch_int(),      // task<int>
558  
            fetch_int(),      // task<int>
548  
            fetch_string()    // task<std::string>
559  
            fetch_string()    // task<std::string>
549  
        );
560  
        );
550  
        // result.index() is 0 or 1
561  
        // result.index() is 0 or 1
551  
        if (result.index() == 0)
562  
        if (result.index() == 0)
552  
            std::cout << "Got int: " << std::get<0>(result) << "\n";
563  
            std::cout << "Got int: " << std::get<0>(result) << "\n";
553  
        else
564  
        else
554  
            std::cout << "Got string: " << std::get<1>(result) << "\n";
565  
            std::cout << "Got string: " << std::get<1>(result) << "\n";
555  
    }
566  
    }
556  
    @endcode
567  
    @endcode
557  

568  

558 -
    @param as Awaitables to race concurrently (at least one required; each
569 +
    @tparam A0 First awaitable type (must satisfy IoAwaitable).
559 -
        must satisfy IoAwaitable).
570 +
    @tparam As Remaining awaitable types (must satisfy IoAwaitable).
560 -
    @return A task yielding a std::variant with one alternative per awaitable.
571 +
    @param a0 The first awaitable to race.
 
572 +
    @param as Additional awaitables to race concurrently.
 
573 +
    @return A task yielding a variant with one alternative per awaitable.
561  
        Use .index() to identify the winner. Void awaitables contribute
574  
        Use .index() to identify the winner. Void awaitables contribute
562  
        std::monostate.
575  
        std::monostate.
563  

576  

564  
    @throws Rethrows the winner's exception if the winning task threw an exception.
577  
    @throws Rethrows the winner's exception if the winning task threw an exception.
565  

578  

566  
    @par Remarks
579  
    @par Remarks
567  
    Awaitables are moved into the coroutine frame; original objects become
580  
    Awaitables are moved into the coroutine frame; original objects become
568  
    empty after the call. The variant preserves one alternative per input
581  
    empty after the call. The variant preserves one alternative per input
569  
    task. Use .index() to determine which awaitable completed first.
582  
    task. Use .index() to determine which awaitable completed first.
570  
    Void awaitables contribute std::monostate to the variant.
583  
    Void awaitables contribute std::monostate to the variant.
571  

584  

572  
    @see when_all, IoAwaitable
585  
    @see when_all, IoAwaitable
573  
*/
586  
*/
574 -
template<IoAwaitable... As>
587 +
template<IoAwaitable A0, IoAwaitable... As>
575 -
    requires (sizeof...(As) > 0)
588 +
[[nodiscard]] auto when_any(A0 a0, As... as)
576 -
[[nodiscard]] auto when_any(As... as)
589 +
    -> task<detail::when_any_variant_t<
577 -
    -> task<std::variant<void_to_monostate_t<awaitable_result_t<As>>...>>
590 +
        detail::awaitable_result_t<A0>,
 
591 +
        detail::awaitable_result_t<As>...>>
578  
{
592  
{
579 -
    detail::when_any_state<awaitable_result_t<As>...> state;
593 +
    detail::when_any_state<
580 -
    std::tuple<As...> awaitable_tuple(std::move(as)...);
594 +
        detail::awaitable_result_t<A0>,
 
595 +
        detail::awaitable_result_t<As>...> state;
 
596 +
    std::tuple<A0, As...> awaitable_tuple(std::move(a0), std::move(as)...);
581  

597  

582 -
    co_await detail::when_any_launcher<As...>(&awaitable_tuple, &state);
598 +
    co_await detail::when_any_launcher<A0, As...>(&awaitable_tuple, &state);
583  

599  

584  
    if(state.core_.winner_exception_)
600  
    if(state.core_.winner_exception_)
585  
        std::rethrow_exception(state.core_.winner_exception_);
601  
        std::rethrow_exception(state.core_.winner_exception_);
586  

602  

587  
    co_return std::move(*state.result_);
603  
    co_return std::move(*state.result_);
588  
}
604  
}
589  

605  

590  
/** Concept for ranges of full I/O awaitables.
606  
/** Concept for ranges of full I/O awaitables.
591  

607  

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

611  

596  
    @tparam R The range type.
612  
    @tparam R The range type.
597  

613  

598  
    @par Requirements
614  
    @par Requirements
599  
    @li `R` must satisfy `std::ranges::input_range`
615  
    @li `R` must satisfy `std::ranges::input_range`
600  
    @li `R` must satisfy `std::ranges::sized_range`
616  
    @li `R` must satisfy `std::ranges::sized_range`
601  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
617  
    @li `std::ranges::range_value_t<R>` must satisfy @ref IoAwaitable
602  

618  

603  
    @par Syntactic Requirements
619  
    @par Syntactic Requirements
604  
    Given `r` of type `R`:
620  
    Given `r` of type `R`:
605  
    @li `std::ranges::begin(r)` is valid
621  
    @li `std::ranges::begin(r)` is valid
606  
    @li `std::ranges::end(r)` is valid
622  
    @li `std::ranges::end(r)` is valid
607  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
623  
    @li `std::ranges::size(r)` returns `std::ranges::range_size_t<R>`
608  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
624  
    @li `*std::ranges::begin(r)` satisfies @ref IoAwaitable
609  

625  

610  
    @par Example
626  
    @par Example
611  
    @code
627  
    @code
612  
    template<IoAwaitableRange R>
628  
    template<IoAwaitableRange R>
613  
    task<void> race_all(R&& awaitables) {
629  
    task<void> race_all(R&& awaitables) {
614  
        auto winner = co_await when_any(std::forward<R>(awaitables));
630  
        auto winner = co_await when_any(std::forward<R>(awaitables));
615  
        // Process winner...
631  
        // Process winner...
616  
    }
632  
    }
617  
    @endcode
633  
    @endcode
618  

634  

619  
    @see when_any, IoAwaitable
635  
    @see when_any, IoAwaitable
620  
*/
636  
*/
621  
template<typename R>
637  
template<typename R>
622  
concept IoAwaitableRange =
638  
concept IoAwaitableRange =
623  
    std::ranges::input_range<R> &&
639  
    std::ranges::input_range<R> &&
624  
    std::ranges::sized_range<R> &&
640  
    std::ranges::sized_range<R> &&
625  
    IoAwaitable<std::ranges::range_value_t<R>>;
641  
    IoAwaitable<std::ranges::range_value_t<R>>;
626  

642  

627  
namespace detail {
643  
namespace detail {
628  

644  

629  
/** Shared state for homogeneous when_any (range overload).
645  
/** Shared state for homogeneous when_any (range overload).
630  

646  

631  
    Uses composition with when_any_core for shared functionality.
647  
    Uses composition with when_any_core for shared functionality.
632  
    Simpler than heterogeneous: optional<T> instead of variant, vector
648  
    Simpler than heterogeneous: optional<T> instead of variant, vector
633  
    instead of array for runner handles.
649  
    instead of array for runner handles.
634  
*/
650  
*/
635  
template<typename T>
651  
template<typename T>
636  
struct when_any_homogeneous_state
652  
struct when_any_homogeneous_state
637  
{
653  
{
638  
    when_any_core core_;
654  
    when_any_core core_;
639  
    std::optional<T> result_;
655  
    std::optional<T> result_;
640  
    std::vector<std::coroutine_handle<>> runner_handles_;
656  
    std::vector<std::coroutine_handle<>> runner_handles_;
641  

657  

642  
    explicit when_any_homogeneous_state(std::size_t count)
658  
    explicit when_any_homogeneous_state(std::size_t count)
643  
        : core_(count)
659  
        : core_(count)
644  
        , runner_handles_(count)
660  
        , runner_handles_(count)
645  
    {
661  
    {
646  
    }
662  
    }
647  

663  

648  
    // Runners self-destruct in final_suspend. No destruction needed here.
664  
    // Runners self-destruct in final_suspend. No destruction needed here.
649  

665  

650  
    /** @pre core_.try_win() returned true. */
666  
    /** @pre core_.try_win() returned true. */
651  
    void set_winner_result(T value)
667  
    void set_winner_result(T value)
652  
        noexcept(std::is_nothrow_move_constructible_v<T>)
668  
        noexcept(std::is_nothrow_move_constructible_v<T>)
653  
    {
669  
    {
654  
        result_.emplace(std::move(value));
670  
        result_.emplace(std::move(value));
655  
    }
671  
    }
656  
};
672  
};
657  

673  

658  
/** Specialization for void tasks (no result storage needed). */
674  
/** Specialization for void tasks (no result storage needed). */
659  
template<>
675  
template<>
660  
struct when_any_homogeneous_state<void>
676  
struct when_any_homogeneous_state<void>
661  
{
677  
{
662  
    when_any_core core_;
678  
    when_any_core core_;
663  
    std::vector<std::coroutine_handle<>> runner_handles_;
679  
    std::vector<std::coroutine_handle<>> runner_handles_;
664  

680  

665  
    explicit when_any_homogeneous_state(std::size_t count)
681  
    explicit when_any_homogeneous_state(std::size_t count)
666  
        : core_(count)
682  
        : core_(count)
667  
        , runner_handles_(count)
683  
        , runner_handles_(count)
668  
    {
684  
    {
669  
    }
685  
    }
670  

686  

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

688  

673  
    // No set_winner_result - void tasks have no result to store
689  
    // No set_winner_result - void tasks have no result to store
674  
};
690  
};
675  

691  

676  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
692  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
677  
template<IoAwaitableRange Range>
693  
template<IoAwaitableRange Range>
678  
class when_any_homogeneous_launcher
694  
class when_any_homogeneous_launcher
679  
{
695  
{
680  
    using Awaitable = std::ranges::range_value_t<Range>;
696  
    using Awaitable = std::ranges::range_value_t<Range>;
681  
    using T = awaitable_result_t<Awaitable>;
697  
    using T = awaitable_result_t<Awaitable>;
682  

698  

683  
    Range* range_;
699  
    Range* range_;
684  
    when_any_homogeneous_state<T>* state_;
700  
    when_any_homogeneous_state<T>* state_;
685  

701  

686  
public:
702  
public:
687  
    when_any_homogeneous_launcher(
703  
    when_any_homogeneous_launcher(
688  
        Range* range,
704  
        Range* range,
689  
        when_any_homogeneous_state<T>* state)
705  
        when_any_homogeneous_state<T>* state)
690  
        : range_(range)
706  
        : range_(range)
691  
        , state_(state)
707  
        , state_(state)
692  
    {
708  
    {
693  
    }
709  
    }
694  

710  

695  
    bool await_ready() const noexcept
711  
    bool await_ready() const noexcept
696  
    {
712  
    {
697  
        return std::ranges::empty(*range_);
713  
        return std::ranges::empty(*range_);
698  
    }
714  
    }
699  

715  

700  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
716  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
701  
        destroys this object before await_suspend returns. Must not reference
717  
        destroys this object before await_suspend returns. Must not reference
702  
        `this` after dispatching begins.
718  
        `this` after dispatching begins.
703  

719  

704  
        Two-phase approach:
720  
        Two-phase approach:
705  
        1. Create all runners (safe - no dispatch yet)
721  
        1. Create all runners (safe - no dispatch yet)
706  
        2. Dispatch all runners (any may complete synchronously)
722  
        2. Dispatch all runners (any may complete synchronously)
707  
    */
723  
    */
708  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
724  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
709  
    {
725  
    {
710  
        state_->core_.continuation_ = continuation;
726  
        state_->core_.continuation_ = continuation;
711  
        state_->core_.caller_env_ = caller_env;
727  
        state_->core_.caller_env_ = caller_env;
712  

728  

713  
        if(caller_env->stop_token.stop_possible())
729  
        if(caller_env->stop_token.stop_possible())
714  
        {
730  
        {
715  
            state_->core_.parent_stop_callback_.emplace(
731  
            state_->core_.parent_stop_callback_.emplace(
716  
                caller_env->stop_token,
732  
                caller_env->stop_token,
717  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
733  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
718  

734  

719  
            if(caller_env->stop_token.stop_requested())
735  
            if(caller_env->stop_token.stop_requested())
720  
                state_->core_.stop_source_.request_stop();
736  
                state_->core_.stop_source_.request_stop();
721  
        }
737  
        }
722  

738  

723  
        auto token = state_->core_.stop_source_.get_token();
739  
        auto token = state_->core_.stop_source_.get_token();
724  

740  

725  
        // Phase 1: Create all runners without dispatching.
741  
        // Phase 1: Create all runners without dispatching.
726  
        // This iterates over *range_ safely because no runners execute yet.
742  
        // This iterates over *range_ safely because no runners execute yet.
727  
        std::size_t index = 0;
743  
        std::size_t index = 0;
728  
        for(auto&& a : *range_)
744  
        for(auto&& a : *range_)
729  
        {
745  
        {
730  
            auto runner = make_when_any_runner(
746  
            auto runner = make_when_any_runner(
731  
                std::move(a), state_, index);
747  
                std::move(a), state_, index);
732  

748  

733  
            auto h = runner.release();
749  
            auto h = runner.release();
734  
            h.promise().state_ = state_;
750  
            h.promise().state_ = state_;
735  
            h.promise().index_ = index;
751  
            h.promise().index_ = index;
736  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
752  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
737  

753  

738  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
754  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
739  
            ++index;
755  
            ++index;
740  
        }
756  
        }
741  

757  

742  
        // Phase 2: Post all runners. Any may complete synchronously.
758  
        // Phase 2: Post all runners. Any may complete synchronously.
743  
        // After last post, state_ and this may be destroyed.
759  
        // After last post, state_ and this may be destroyed.
744  
        // Use raw pointer/count captured before posting.
760  
        // Use raw pointer/count captured before posting.
745  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
761  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
746  
        std::size_t count = state_->runner_handles_.size();
762  
        std::size_t count = state_->runner_handles_.size();
747  
        for(std::size_t i = 0; i < count; ++i)
763  
        for(std::size_t i = 0; i < count; ++i)
748  
            caller_env->executor.post(handles[i]);
764  
            caller_env->executor.post(handles[i]);
749  

765  

750  
        return std::noop_coroutine();
766  
        return std::noop_coroutine();
751  
    }
767  
    }
752  

768  

753  
    void await_resume() const noexcept
769  
    void await_resume() const noexcept
754  
    {
770  
    {
755  
    }
771  
    }
756  
};
772  
};
757  

773  

758  
} // namespace detail
774  
} // namespace detail
759  

775  

760  
/** Wait for the first awaitable to complete (range overload).
776  
/** Wait for the first awaitable to complete (range overload).
761  

777  

762  
    Races a range of awaitables with the same result type. Accepts any
778  
    Races a range of awaitables with the same result type. Accepts any
763  
    sized input range of IoAwaitable types, enabling use with arrays,
779  
    sized input range of IoAwaitable types, enabling use with arrays,
764  
    spans, or custom containers.
780  
    spans, or custom containers.
765  

781  

766  
    @par Suspends
782  
    @par Suspends
767  
    The calling coroutine suspends when co_await is invoked. All awaitables
783  
    The calling coroutine suspends when co_await is invoked. All awaitables
768  
    in the range are launched concurrently and execute in parallel. The
784  
    in the range are launched concurrently and execute in parallel. The
769  
    coroutine resumes only after all awaitables have completed, even though
785  
    coroutine resumes only after all awaitables have completed, even though
770  
    the winner is determined by the first to finish.
786  
    the winner is determined by the first to finish.
771  

787  

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

794  

779  
    @par Cancellation Semantics
795  
    @par Cancellation Semantics
780  
    Cancellation is supported via stop_token propagated through the
796  
    Cancellation is supported via stop_token propagated through the
781  
    IoAwaitable protocol:
797  
    IoAwaitable protocol:
782  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
798  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
783  
    @li When the parent's stop token is activated, the stop is forwarded to all children
799  
    @li When the parent's stop token is activated, the stop is forwarded to all children
784  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
800  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
785  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
801  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
786  
    @li Stop requests are cooperative; tasks must check and respond to them
802  
    @li Stop requests are cooperative; tasks must check and respond to them
787  

803  

788  
    @par Concurrency/Overlap
804  
    @par Concurrency/Overlap
789  
    All awaitables are launched concurrently before any can complete.
805  
    All awaitables are launched concurrently before any can complete.
790  
    The launcher iterates through the range, starting each task on the
806  
    The launcher iterates through the range, starting each task on the
791  
    caller's executor. Tasks may execute in parallel on multi-threaded
807  
    caller's executor. Tasks may execute in parallel on multi-threaded
792  
    executors or interleave on single-threaded executors. There is no
808  
    executors or interleave on single-threaded executors. There is no
793  
    guaranteed ordering of task completion.
809  
    guaranteed ordering of task completion.
794  

810  

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

816  

801  
    @par Example
817  
    @par Example
802  
    @code
818  
    @code
803  
    task<void> example() {
819  
    task<void> example() {
804  
        std::array<task<Response>, 3> requests = {
820  
        std::array<task<Response>, 3> requests = {
805  
            fetch_from_server(0),
821  
            fetch_from_server(0),
806  
            fetch_from_server(1),
822  
            fetch_from_server(1),
807  
            fetch_from_server(2)
823  
            fetch_from_server(2)
808  
        };
824  
        };
809  

825  

810  
        auto [index, response] = co_await when_any(std::move(requests));
826  
        auto [index, response] = co_await when_any(std::move(requests));
811  
    }
827  
    }
812  
    @endcode
828  
    @endcode
813  

829  

814  
    @par Example with Vector
830  
    @par Example with Vector
815  
    @code
831  
    @code
816  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
832  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
817  
        std::vector<task<Response>> requests;
833  
        std::vector<task<Response>> requests;
818  
        for (auto const& server : servers)
834  
        for (auto const& server : servers)
819  
            requests.push_back(fetch_from(server));
835  
            requests.push_back(fetch_from(server));
820  

836  

821  
        auto [index, response] = co_await when_any(std::move(requests));
837  
        auto [index, response] = co_await when_any(std::move(requests));
822  
        co_return response;
838  
        co_return response;
823  
    }
839  
    }
824  
    @endcode
840  
    @endcode
825  

841  

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

845  

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

848  

833  
    @par Remarks
849  
    @par Remarks
834  
    Elements are moved from the range; for lvalue ranges, the original
850  
    Elements are moved from the range; for lvalue ranges, the original
835  
    container will have moved-from elements after this call. The range
851  
    container will have moved-from elements after this call. The range
836  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
852  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
837  
    the variadic overload, no variant wrapper is needed since all tasks
853  
    the variadic overload, no variant wrapper is needed since all tasks
838  
    share the same return type.
854  
    share the same return type.
839  

855  

840  
    @see when_any, IoAwaitableRange
856  
    @see when_any, IoAwaitableRange
841  
*/
857  
*/
842  
template<IoAwaitableRange R>
858  
template<IoAwaitableRange R>
843 -
    requires (!std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>)
859 +
    requires (!std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>)
844  
[[nodiscard]] auto when_any(R&& awaitables)
860  
[[nodiscard]] auto when_any(R&& awaitables)
845 -
    -> task<std::pair<std::size_t, awaitable_result_t<std::ranges::range_value_t<R>>>>
861 +
    -> task<std::pair<std::size_t, detail::awaitable_result_t<std::ranges::range_value_t<R>>>>
846  
{
862  
{
847  
    using Awaitable = std::ranges::range_value_t<R>;
863  
    using Awaitable = std::ranges::range_value_t<R>;
848 -
    using T = awaitable_result_t<Awaitable>;
864 +
    using T = detail::awaitable_result_t<Awaitable>;
849  
    using result_type = std::pair<std::size_t, T>;
865  
    using result_type = std::pair<std::size_t, T>;
850  
    using OwnedRange = std::remove_cvref_t<R>;
866  
    using OwnedRange = std::remove_cvref_t<R>;
851  

867  

852  
    auto count = std::ranges::size(awaitables);
868  
    auto count = std::ranges::size(awaitables);
853  
    if(count == 0)
869  
    if(count == 0)
854  
        throw std::invalid_argument("when_any requires at least one awaitable");
870  
        throw std::invalid_argument("when_any requires at least one awaitable");
855  

871  

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

874  

859  
    detail::when_any_homogeneous_state<T> state(count);
875  
    detail::when_any_homogeneous_state<T> state(count);
860  

876  

861  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
877  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
862  

878  

863  
    if(state.core_.winner_exception_)
879  
    if(state.core_.winner_exception_)
864  
        std::rethrow_exception(state.core_.winner_exception_);
880  
        std::rethrow_exception(state.core_.winner_exception_);
865  

881  

866  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
882  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
867  
}
883  
}
868  

884  

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

886  

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

889  

874  
    @par Suspends
890  
    @par Suspends
875  
    The calling coroutine suspends when co_await is invoked. All awaitables
891  
    The calling coroutine suspends when co_await is invoked. All awaitables
876  
    in the range are launched concurrently and execute in parallel. The
892  
    in the range are launched concurrently and execute in parallel. The
877  
    coroutine resumes only after all awaitables have completed, even though
893  
    coroutine resumes only after all awaitables have completed, even though
878  
    the winner is determined by the first to finish.
894  
    the winner is determined by the first to finish.
879  

895  

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

902  

887  
    @par Cancellation Semantics
903  
    @par Cancellation Semantics
888  
    Cancellation is supported via stop_token propagated through the
904  
    Cancellation is supported via stop_token propagated through the
889  
    IoAwaitable protocol:
905  
    IoAwaitable protocol:
890  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
906  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
891  
    @li When the parent's stop token is activated, the stop is forwarded to all children
907  
    @li When the parent's stop token is activated, the stop is forwarded to all children
892  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
908  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
893  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
909  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
894  
    @li Stop requests are cooperative; tasks must check and respond to them
910  
    @li Stop requests are cooperative; tasks must check and respond to them
895  

911  

896  
    @par Concurrency/Overlap
912  
    @par Concurrency/Overlap
897  
    All awaitables are launched concurrently before any can complete.
913  
    All awaitables are launched concurrently before any can complete.
898  
    The launcher iterates through the range, starting each task on the
914  
    The launcher iterates through the range, starting each task on the
899  
    caller's executor. Tasks may execute in parallel on multi-threaded
915  
    caller's executor. Tasks may execute in parallel on multi-threaded
900  
    executors or interleave on single-threaded executors. There is no
916  
    executors or interleave on single-threaded executors. There is no
901  
    guaranteed ordering of task completion.
917  
    guaranteed ordering of task completion.
902  

918  

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

924  

909  
    @par Example
925  
    @par Example
910  
    @code
926  
    @code
911  
    task<void> example() {
927  
    task<void> example() {
912  
        std::vector<task<void>> tasks;
928  
        std::vector<task<void>> tasks;
913  
        for (int i = 0; i < 5; ++i)
929  
        for (int i = 0; i < 5; ++i)
914  
            tasks.push_back(background_work(i));
930  
            tasks.push_back(background_work(i));
915  

931  

916  
        std::size_t winner = co_await when_any(std::move(tasks));
932  
        std::size_t winner = co_await when_any(std::move(tasks));
917  
        // winner is the index of the first task to complete
933  
        // winner is the index of the first task to complete
918  
    }
934  
    }
919  
    @endcode
935  
    @endcode
920  

936  

921  
    @par Example with Timeout
937  
    @par Example with Timeout
922  
    @code
938  
    @code
923  
    task<void> with_timeout() {
939  
    task<void> with_timeout() {
924  
        std::vector<task<void>> tasks;
940  
        std::vector<task<void>> tasks;
925  
        tasks.push_back(long_running_operation());
941  
        tasks.push_back(long_running_operation());
926  
        tasks.push_back(delay(std::chrono::seconds(5)));
942  
        tasks.push_back(delay(std::chrono::seconds(5)));
927  

943  

928  
        std::size_t winner = co_await when_any(std::move(tasks));
944  
        std::size_t winner = co_await when_any(std::move(tasks));
929  
        if (winner == 1) {
945  
        if (winner == 1) {
930  
            // Timeout occurred
946  
            // Timeout occurred
931  
        }
947  
        }
932  
    }
948  
    }
933  
    @endcode
949  
    @endcode
934  

950  

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

954  

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

957  

942  
    @par Remarks
958  
    @par Remarks
943  
    Elements are moved from the range; for lvalue ranges, the original
959  
    Elements are moved from the range; for lvalue ranges, the original
944  
    container will have moved-from elements after this call. The range
960  
    container will have moved-from elements after this call. The range
945  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
961  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
946  
    the non-void overload, no result storage is needed since void tasks
962  
    the non-void overload, no result storage is needed since void tasks
947  
    produce no value.
963  
    produce no value.
948  

964  

949  
    @see when_any, IoAwaitableRange
965  
    @see when_any, IoAwaitableRange
950  
*/
966  
*/
951  
template<IoAwaitableRange R>
967  
template<IoAwaitableRange R>
952 -
    requires std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>
968 +
    requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
953  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
969  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
954  
{
970  
{
955  
    using OwnedRange = std::remove_cvref_t<R>;
971  
    using OwnedRange = std::remove_cvref_t<R>;
956  

972  

957  
    auto count = std::ranges::size(awaitables);
973  
    auto count = std::ranges::size(awaitables);
958  
    if(count == 0)
974  
    if(count == 0)
959  
        throw std::invalid_argument("when_any requires at least one awaitable");
975  
        throw std::invalid_argument("when_any requires at least one awaitable");
960  

976  

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

979  

964  
    detail::when_any_homogeneous_state<void> state(count);
980  
    detail::when_any_homogeneous_state<void> state(count);
965  

981  

966  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
982  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
967  

983  

968  
    if(state.core_.winner_exception_)
984  
    if(state.core_.winner_exception_)
969  
        std::rethrow_exception(state.core_.winner_exception_);
985  
        std::rethrow_exception(state.core_.winner_exception_);
970  

986  

971  
    co_return state.core_.winner_index_;
987  
    co_return state.core_.winner_index_;
972  
}
988  
}
973  

989  

974  
} // namespace capy
990  
} // namespace capy
975  
} // namespace boost
991  
} // namespace boost
976  

992  

977  
#endif
993  
#endif