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 +
   TYPE DEDUPLICATION:
66  
   -------------------
66  
   -------------------
67 -
   The variadic overload returns a std::variant with one alternative per
67 +
   std::variant requires unique alternative types. Since when_any can race
68 -
   input task, preserving positional correspondence. Use .index() on
68 +
   tasks with identical return types (e.g., three task<int>), we must
69 -
   the variant to identify which task won.
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 -
     - Result variant: std::variant<int, string, int>
73 +
     - Deduplicated variant: std::variant<int, string>
74 -
     - variant.index() tells you which task won (0, 1, or 2)
74 +
     - Return: pair<size_t, variant<int, string>>
 
75 +

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

78  

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

83  

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

87  

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

94  

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

104  

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

110  

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

117  

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

120  

118  
namespace detail {
121  
namespace detail {
119  

122  

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

124  

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

128  

126  
    @tparam T The type to potentially convert (void becomes monostate).
129  
    @tparam T The type to potentially convert (void becomes monostate).
127  
*/
130  
*/
128  
template<typename T>
131  
template<typename T>
129  
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>;
130  

133  

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

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

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

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

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

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

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

174  

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

176  

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

180  

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

190  

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

199  

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

202  

165  
    // 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)
166  
    std::atomic<bool> has_winner_{false};
204  
    std::atomic<bool> has_winner_{false};
167  

205  

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

210  

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

224  

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

230  

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

233  

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

235  

198  
    Coordinates winner selection, result storage, and completion tracking
236  
    Coordinates winner selection, result storage, and completion tracking
199  
    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
200  
    when_any_core for shared functionality.
238  
    when_any_core for shared functionality.
201  

239  

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

242  

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

251  

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

255  

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

260  

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

262  

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

272  

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

279  

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

281  

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

284  

248  
    @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).
249  
*/
286  
*/
250  
template<typename StateType>
287  
template<typename StateType>
251  
struct when_any_runner
288  
struct when_any_runner
252  
{
289  
{
253  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
290  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
254  
    {
291  
    {
255  
        StateType* state_ = nullptr;
292  
        StateType* state_ = nullptr;
256  
        std::size_t index_ = 0;
293  
        std::size_t index_ = 0;
257  
        io_env env_;
294  
        io_env env_;
258  

295  

259  
        when_any_runner get_return_object() noexcept
296  
        when_any_runner get_return_object() noexcept
260  
        {
297  
        {
261  
            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));
262  
        }
299  
        }
263  

300  

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

306  

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

320  

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

322  

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

333  

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

335  

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

342  

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

349  

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

352  

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

367  

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

383  

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

385  

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

390  

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

393  

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

398  

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

404  

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

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

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

406  

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

446  

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

452  

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

455  

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

464  

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

469  

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

478  

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

484  

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

488  

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

493  

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

496  

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

500  

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

508  

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

513  

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

519  

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

521  

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

523  

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

527  

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

533  

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

540  

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

549  

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

556  

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

561  

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

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

587  

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

593  

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

595  

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

602  

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

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

619  

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

621  

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

624  

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

627  

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

629  

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

633  

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

635  

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

640  

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

647  

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

656  

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

664  

643  
namespace detail {
665  
namespace detail {
644  

666  

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

668  

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

679  

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

685  

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

687  

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

695  

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

702  

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

708  

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

710  

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

713  

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

720  

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

723  

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

732  

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

737  

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

741  

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

750  

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

756  

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

760  

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

762  

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

770  

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

775  

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

779  

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

787  

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

790  

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

795  

774  
} // namespace detail
796  
} // namespace detail
775  

797  

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

799  

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

803  

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

809  

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

816  

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

825  

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

832  

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

838  

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

847  

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

851  

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

858  

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

863  

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

867  

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

870  

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

877  

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

889  

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

893  

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

896  

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

898  

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

900  

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

903  

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

906  

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

908  

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

911  

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

917  

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

924  

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

933  

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

940  

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

946  

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

953  

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

958  

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

965  

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

972  

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

976  

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

979  

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

986  

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

994  

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

998  

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

1001  

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

1003  

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

1005  

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

1008  

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

1011  

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

1014  

993  
#endif
1015  
#endif