1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/cppalliance/capy
8  
// Official repository: https://github.com/cppalliance/capy
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_CAPY_WHEN_ANY_HPP
11  
#ifndef BOOST_CAPY_WHEN_ANY_HPP
12  
#define BOOST_CAPY_WHEN_ANY_HPP
12  
#define BOOST_CAPY_WHEN_ANY_HPP
13  

13  

14  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/config.hpp>
15  
#include <boost/capy/detail/io_result_combinators.hpp>
15  
#include <boost/capy/detail/io_result_combinators.hpp>
16  
#include <boost/capy/continuation.hpp>
16  
#include <boost/capy/continuation.hpp>
17  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/executor.hpp>
18  
#include <boost/capy/concept/io_awaitable.hpp>
18  
#include <boost/capy/concept/io_awaitable.hpp>
19  
#include <coroutine>
19  
#include <coroutine>
20  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
21  
#include <boost/capy/ex/frame_alloc_mixin.hpp>
21  
#include <boost/capy/ex/frame_alloc_mixin.hpp>
22  
#include <boost/capy/ex/frame_allocator.hpp>
22  
#include <boost/capy/ex/frame_allocator.hpp>
23  
#include <boost/capy/ex/io_env.hpp>
23  
#include <boost/capy/ex/io_env.hpp>
24  
#include <boost/capy/task.hpp>
24  
#include <boost/capy/task.hpp>
25  

25  

26  
#include <array>
26  
#include <array>
27  
#include <atomic>
27  
#include <atomic>
28  
#include <exception>
28  
#include <exception>
29  
#include <memory>
29  
#include <memory>
30  
#include <mutex>
30  
#include <mutex>
31  
#include <optional>
31  
#include <optional>
32  
#include <ranges>
32  
#include <ranges>
33  
#include <stdexcept>
33  
#include <stdexcept>
34  
#include <stop_token>
34  
#include <stop_token>
35  
#include <tuple>
35  
#include <tuple>
36  
#include <type_traits>
36  
#include <type_traits>
37  
#include <utility>
37  
#include <utility>
38  
#include <variant>
38  
#include <variant>
39  
#include <vector>
39  
#include <vector>
40  

40  

41  
/*
41  
/*
42  
   when_any - Race multiple io_result tasks, select first success
42  
   when_any - Race multiple io_result tasks, select first success
43  
   =============================================================
43  
   =============================================================
44  

44  

45  
   OVERVIEW:
45  
   OVERVIEW:
46  
   ---------
46  
   ---------
47  
   when_any launches N io_result-returning tasks concurrently. A task
47  
   when_any launches N io_result-returning tasks concurrently. A task
48  
   wins by returning !ec; errors and exceptions do not win. Once a
48  
   wins by returning !ec; errors and exceptions do not win. Once a
49  
   winner is found, stop is requested for siblings and the winner's
49  
   winner is found, stop is requested for siblings and the winner's
50  
   payload is returned. If no winner exists (all fail), the first
50  
   payload is returned. If no winner exists (all fail), the first
51  
   error_code is returned or the last exception is rethrown.
51  
   error_code is returned or the last exception is rethrown.
52  

52  

53  
   ARCHITECTURE:
53  
   ARCHITECTURE:
54  
   -------------
54  
   -------------
55  
   The design mirrors when_all but with inverted completion semantics:
55  
   The design mirrors when_all but with inverted completion semantics:
56  

56  

57  
     when_all:  complete when remaining_count reaches 0 (all done)
57  
     when_all:  complete when remaining_count reaches 0 (all done)
58  
     when_any:  complete when has_winner becomes true (first done)
58  
     when_any:  complete when has_winner becomes true (first done)
59  
                BUT still wait for remaining_count to reach 0 for cleanup
59  
                BUT still wait for remaining_count to reach 0 for cleanup
60  

60  

61  
   Key components:
61  
   Key components:
62  
     - when_any_core:    Shared state tracking winner and completion
62  
     - when_any_core:    Shared state tracking winner and completion
63  
     - when_any_io_runner: Wrapper coroutine for each child task
63  
     - when_any_io_runner: Wrapper coroutine for each child task
64  
     - when_any_io_launcher/when_any_io_homogeneous_launcher:
64  
     - when_any_io_launcher/when_any_io_homogeneous_launcher:
65  
                          Awaitables that start all runners concurrently
65  
                          Awaitables that start all runners concurrently
66  

66  

67  
   CRITICAL INVARIANTS:
67  
   CRITICAL INVARIANTS:
68  
   --------------------
68  
   --------------------
69  
   1. Only a task returning !ec can become the winner (via atomic CAS)
69  
   1. Only a task returning !ec can become the winner (via atomic CAS)
70  
   2. All tasks must complete before parent resumes (cleanup safety)
70  
   2. All tasks must complete before parent resumes (cleanup safety)
71  
   3. Stop is requested immediately when winner is determined
71  
   3. Stop is requested immediately when winner is determined
72  
   4. Exceptions and errors do not claim winner status
72  
   4. Exceptions and errors do not claim winner status
73  

73  

74  
   POSITIONAL VARIANT:
74  
   POSITIONAL VARIANT:
75  
   -------------------
75  
   -------------------
76  
   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
76  
   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
77  
   Index 0 is error_code (failure/no-winner). Index 1..N identifies the
77  
   Index 0 is error_code (failure/no-winner). Index 1..N identifies the
78  
   winning child and carries its payload.
78  
   winning child and carries its payload.
79  

79  

80  
   RANGE OVERLOAD:
80  
   RANGE OVERLOAD:
81  
   ---------------
81  
   ---------------
82  
   The range overload returns variant<error_code, pair<size_t, T>> for
82  
   The range overload returns variant<error_code, pair<size_t, T>> for
83  
   non-void children or variant<error_code, size_t> for void children.
83  
   non-void children or variant<error_code, size_t> for void children.
84  

84  

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

88  

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

95  

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

105  

106  
   EXCEPTION SEMANTICS:
106  
   EXCEPTION SEMANTICS:
107  
   --------------------
107  
   --------------------
108  
   Exceptions do NOT claim winner status. If a child throws, the exception
108  
   Exceptions do NOT claim winner status. If a child throws, the exception
109  
   is recorded but the combinator keeps waiting for a success. Only when
109  
   is recorded but the combinator keeps waiting for a success. Only when
110  
   all children complete without a winner does the combinator check: if
110  
   all children complete without a winner does the combinator check: if
111  
   any exception was recorded, it is rethrown (exception beats error_code).
111  
   any exception was recorded, it is rethrown (exception beats error_code).
112  
*/
112  
*/
113  

113  

114  
namespace boost {
114  
namespace boost {
115  
namespace capy {
115  
namespace capy {
116  

116  

117  
namespace detail {
117  
namespace detail {
118  

118  

119  
/** Core shared state for when_any operations.
119  
/** Core shared state for when_any operations.
120  

120  

121  
    Contains all members and methods common to both heterogeneous (variadic)
121  
    Contains all members and methods common to both heterogeneous (variadic)
122  
    and homogeneous (range) when_any implementations. State classes embed
122  
    and homogeneous (range) when_any implementations. State classes embed
123  
    this via composition to avoid CRTP destructor ordering issues.
123  
    this via composition to avoid CRTP destructor ordering issues.
124  

124  

125  
    @par Thread Safety
125  
    @par Thread Safety
126  
    Atomic operations protect winner selection and completion count.
126  
    Atomic operations protect winner selection and completion count.
127  
*/
127  
*/
128  
struct when_any_core
128  
struct when_any_core
129  
{
129  
{
130  
    std::atomic<std::size_t> remaining_count_;
130  
    std::atomic<std::size_t> remaining_count_;
131  
    std::size_t winner_index_{0};
131  
    std::size_t winner_index_{0};
132  
    std::exception_ptr winner_exception_;
132  
    std::exception_ptr winner_exception_;
133  
    std::stop_source stop_source_;
133  
    std::stop_source stop_source_;
134  

134  

135  
    // Bridges parent's stop token to our stop_source
135  
    // Bridges parent's stop token to our stop_source
136  
    struct stop_callback_fn
136  
    struct stop_callback_fn
137  
    {
137  
    {
138  
        std::stop_source* source_;
138  
        std::stop_source* source_;
139  
        void operator()() const noexcept { source_->request_stop(); }
139  
        void operator()() const noexcept { source_->request_stop(); }
140  
    };
140  
    };
141  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
141  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
142  
    std::optional<stop_callback_t> parent_stop_callback_;
142  
    std::optional<stop_callback_t> parent_stop_callback_;
143  

143  

144  
    continuation continuation_;
144  
    continuation continuation_;
145  
    io_env const* caller_env_ = nullptr;
145  
    io_env const* caller_env_ = nullptr;
146  

146  

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

149  

150  
    explicit when_any_core(std::size_t count) noexcept
150  
    explicit when_any_core(std::size_t count) noexcept
151  
        : remaining_count_(count)
151  
        : remaining_count_(count)
152  
    {
152  
    {
153  
    }
153  
    }
154  

154  

155  
    /** Atomically claim winner status; exactly one task succeeds. */
155  
    /** Atomically claim winner status; exactly one task succeeds. */
156  
    bool try_win(std::size_t index) noexcept
156  
    bool try_win(std::size_t index) noexcept
157  
    {
157  
    {
158  
        bool expected = false;
158  
        bool expected = false;
159  
        if(has_winner_.compare_exchange_strong(
159  
        if(has_winner_.compare_exchange_strong(
160  
            expected, true, std::memory_order_acq_rel))
160  
            expected, true, std::memory_order_acq_rel))
161  
        {
161  
        {
162  
            winner_index_ = index;
162  
            winner_index_ = index;
163  
            stop_source_.request_stop();
163  
            stop_source_.request_stop();
164  
            return true;
164  
            return true;
165  
        }
165  
        }
166  
        return false;
166  
        return false;
167  
    }
167  
    }
168  

168  

169  
    /** @pre try_win() returned true. */
169  
    /** @pre try_win() returned true. */
170  
    void set_winner_exception(std::exception_ptr ep) noexcept
170  
    void set_winner_exception(std::exception_ptr ep) noexcept
171  
    {
171  
    {
172  
        winner_exception_ = ep;
172  
        winner_exception_ = ep;
173  
    }
173  
    }
174  

174  

175  
    // Runners signal completion directly via final_suspend; no member function needed.
175  
    // Runners signal completion directly via final_suspend; no member function needed.
176  
};
176  
};
177  

177  

178  
} // namespace detail
178  
} // namespace detail
179  

179  

180  
namespace detail {
180  
namespace detail {
181  

181  

182  
// State for io_result-aware when_any: only !ec wins.
182  
// State for io_result-aware when_any: only !ec wins.
183  
template<typename... Ts>
183  
template<typename... Ts>
184  
struct when_any_io_state
184  
struct when_any_io_state
185  
{
185  
{
186  
    static constexpr std::size_t task_count = sizeof...(Ts);
186  
    static constexpr std::size_t task_count = sizeof...(Ts);
187  
    using variant_type = std::variant<std::error_code, Ts...>;
187  
    using variant_type = std::variant<std::error_code, Ts...>;
188  

188  

189  
    when_any_core core_;
189  
    when_any_core core_;
190  
    std::optional<variant_type> result_;
190  
    std::optional<variant_type> result_;
191  
    std::array<continuation, task_count> runner_handles_{};
191  
    std::array<continuation, task_count> runner_handles_{};
192  

192  

193  
    // Last failure (error or exception) for the all-fail case.
193  
    // Last failure (error or exception) for the all-fail case.
194  
    // Last writer wins — no priority between errors and exceptions.
194  
    // Last writer wins — no priority between errors and exceptions.
195  
    std::mutex failure_mu_;
195  
    std::mutex failure_mu_;
196  
    std::error_code last_error_;
196  
    std::error_code last_error_;
197  
    std::exception_ptr last_exception_;
197  
    std::exception_ptr last_exception_;
198  

198  

199  
    when_any_io_state()
199  
    when_any_io_state()
200  
        : core_(task_count)
200  
        : core_(task_count)
201  
    {
201  
    {
202  
    }
202  
    }
203  

203  

204  
    void record_error(std::error_code ec)
204  
    void record_error(std::error_code ec)
205  
    {
205  
    {
206  
        std::lock_guard lk(failure_mu_);
206  
        std::lock_guard lk(failure_mu_);
207  
        last_error_ = ec;
207  
        last_error_ = ec;
208  
        last_exception_ = nullptr;
208  
        last_exception_ = nullptr;
209  
    }
209  
    }
210  

210  

211  
    void record_exception(std::exception_ptr ep)
211  
    void record_exception(std::exception_ptr ep)
212  
    {
212  
    {
213  
        std::lock_guard lk(failure_mu_);
213  
        std::lock_guard lk(failure_mu_);
214  
        last_exception_ = ep;
214  
        last_exception_ = ep;
215  
        last_error_ = {};
215  
        last_error_ = {};
216  
    }
216  
    }
217  
};
217  
};
218  

218  

219  
// Wrapper coroutine for io_result-aware when_any children.
219  
// Wrapper coroutine for io_result-aware when_any children.
220  
// unhandled_exception records the exception but does NOT claim winner status.
220  
// unhandled_exception records the exception but does NOT claim winner status.
221  
template<typename StateType>
221  
template<typename StateType>
222  
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner
222  
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner
223  
{
223  
{
224  
    struct promise_type
224  
    struct promise_type
225  
        : frame_alloc_mixin
225  
        : frame_alloc_mixin
226  
    {
226  
    {
227  
        StateType* state_ = nullptr;
227  
        StateType* state_ = nullptr;
228  
        std::size_t index_ = 0;
228  
        std::size_t index_ = 0;
229  
        io_env env_;
229  
        io_env env_;
230  

230  

231  
        when_any_io_runner get_return_object() noexcept
231  
        when_any_io_runner get_return_object() noexcept
232  
        {
232  
        {
233  
            return when_any_io_runner(
233  
            return when_any_io_runner(
234  
                std::coroutine_handle<promise_type>::from_promise(*this));
234  
                std::coroutine_handle<promise_type>::from_promise(*this));
235  
        }
235  
        }
236  

236  

237  
        std::suspend_always initial_suspend() noexcept { return {}; }
237  
        std::suspend_always initial_suspend() noexcept { return {}; }
238  

238  

239  
        auto final_suspend() noexcept
239  
        auto final_suspend() noexcept
240  
        {
240  
        {
241  
            struct awaiter
241  
            struct awaiter
242  
            {
242  
            {
243  
                promise_type* p_;
243  
                promise_type* p_;
244  
                bool await_ready() const noexcept { return false; }
244  
                bool await_ready() const noexcept { return false; }
245  
                auto await_suspend(std::coroutine_handle<> h) noexcept
245  
                auto await_suspend(std::coroutine_handle<> h) noexcept
246  
                {
246  
                {
247  
                    auto& core = p_->state_->core_;
247  
                    auto& core = p_->state_->core_;
248  
                    auto* counter = &core.remaining_count_;
248  
                    auto* counter = &core.remaining_count_;
249  
                    auto* caller_env = core.caller_env_;
249  
                    auto* caller_env = core.caller_env_;
250  
                    auto& cont = core.continuation_;
250  
                    auto& cont = core.continuation_;
251  

251  

252  
                    h.destroy();
252  
                    h.destroy();
253  

253  

254  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
254  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
255  
                    if(remaining == 1)
255  
                    if(remaining == 1)
256  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
256  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
257  
                    return detail::symmetric_transfer(std::noop_coroutine());
257  
                    return detail::symmetric_transfer(std::noop_coroutine());
258  
                }
258  
                }
259  
                void await_resume() const noexcept {}
259  
                void await_resume() const noexcept {}
260  
            };
260  
            };
261  
            return awaiter{this};
261  
            return awaiter{this};
262  
        }
262  
        }
263  

263  

264  
        void return_void() noexcept {}
264  
        void return_void() noexcept {}
265  

265  

266  
        // Exceptions do NOT win in io_result when_any
266  
        // Exceptions do NOT win in io_result when_any
267  
        void unhandled_exception() noexcept
267  
        void unhandled_exception() noexcept
268  
        {
268  
        {
269  
            state_->record_exception(std::current_exception());
269  
            state_->record_exception(std::current_exception());
270  
        }
270  
        }
271  

271  

272  
        template<class Awaitable>
272  
        template<class Awaitable>
273  
        struct transform_awaiter
273  
        struct transform_awaiter
274  
        {
274  
        {
275  
            std::decay_t<Awaitable> a_;
275  
            std::decay_t<Awaitable> a_;
276  
            promise_type* p_;
276  
            promise_type* p_;
277  

277  

278  
            bool await_ready() { return a_.await_ready(); }
278  
            bool await_ready() { return a_.await_ready(); }
279  
            decltype(auto) await_resume() { return a_.await_resume(); }
279  
            decltype(auto) await_resume() { return a_.await_resume(); }
280  

280  

281  
            template<class Promise>
281  
            template<class Promise>
282  
            auto await_suspend(std::coroutine_handle<Promise> h)
282  
            auto await_suspend(std::coroutine_handle<Promise> h)
283  
            {
283  
            {
284  
                using R = decltype(a_.await_suspend(h, &p_->env_));
284  
                using R = decltype(a_.await_suspend(h, &p_->env_));
285  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
285  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
286  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
286  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
287  
                else
287  
                else
288  
                    return a_.await_suspend(h, &p_->env_);
288  
                    return a_.await_suspend(h, &p_->env_);
289  
            }
289  
            }
290  
        };
290  
        };
291  

291  

292  
        template<class Awaitable>
292  
        template<class Awaitable>
293  
        auto await_transform(Awaitable&& a)
293  
        auto await_transform(Awaitable&& a)
294  
        {
294  
        {
295  
            using A = std::decay_t<Awaitable>;
295  
            using A = std::decay_t<Awaitable>;
296  
            if constexpr (IoAwaitable<A>)
296  
            if constexpr (IoAwaitable<A>)
297  
            {
297  
            {
298  
                return transform_awaiter<Awaitable>{
298  
                return transform_awaiter<Awaitable>{
299  
                    std::forward<Awaitable>(a), this};
299  
                    std::forward<Awaitable>(a), this};
300  
            }
300  
            }
301  
            else
301  
            else
302  
            {
302  
            {
303  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
303  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
304  
            }
304  
            }
305  
        }
305  
        }
306  
    };
306  
    };
307  

307  

308  
    std::coroutine_handle<promise_type> h_;
308  
    std::coroutine_handle<promise_type> h_;
309  

309  

310  
    explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
310  
    explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
311  
        : h_(h)
311  
        : h_(h)
312  
    {
312  
    {
313  
    }
313  
    }
314  

314  

315  
    when_any_io_runner(when_any_io_runner&& other) noexcept
315  
    when_any_io_runner(when_any_io_runner&& other) noexcept
316  
        : h_(std::exchange(other.h_, nullptr))
316  
        : h_(std::exchange(other.h_, nullptr))
317  
    {
317  
    {
318  
    }
318  
    }
319  

319  

320  
    when_any_io_runner(when_any_io_runner const&) = delete;
320  
    when_any_io_runner(when_any_io_runner const&) = delete;
321  
    when_any_io_runner& operator=(when_any_io_runner const&) = delete;
321  
    when_any_io_runner& operator=(when_any_io_runner const&) = delete;
322  
    when_any_io_runner& operator=(when_any_io_runner&&) = delete;
322  
    when_any_io_runner& operator=(when_any_io_runner&&) = delete;
323  

323  

324  
    auto release() noexcept
324  
    auto release() noexcept
325  
    {
325  
    {
326  
        return std::exchange(h_, nullptr);
326  
        return std::exchange(h_, nullptr);
327  
    }
327  
    }
328  
};
328  
};
329  

329  

330  
// Runner coroutine: only tries to win when the child returns !ec.
330  
// Runner coroutine: only tries to win when the child returns !ec.
331  
template<std::size_t I, IoAwaitable Awaitable, typename StateType>
331  
template<std::size_t I, IoAwaitable Awaitable, typename StateType>
332  
when_any_io_runner<StateType>
332  
when_any_io_runner<StateType>
333  
make_when_any_io_runner(Awaitable inner, StateType* state)
333  
make_when_any_io_runner(Awaitable inner, StateType* state)
334  
{
334  
{
335  
    auto result = co_await std::move(inner);
335  
    auto result = co_await std::move(inner);
336  

336  

337  
    if(!result.ec)
337  
    if(!result.ec)
338  
    {
338  
    {
339  
        // Success: try to claim winner
339  
        // Success: try to claim winner
340  
        if(state->core_.try_win(I))
340  
        if(state->core_.try_win(I))
341  
        {
341  
        {
342  
            try
342  
            try
343  
            {
343  
            {
344  
                state->result_.emplace(
344  
                state->result_.emplace(
345  
                    std::in_place_index<I + 1>,
345  
                    std::in_place_index<I + 1>,
346  
                    detail::extract_io_payload(std::move(result)));
346  
                    detail::extract_io_payload(std::move(result)));
347  
            }
347  
            }
348  
            catch(...)
348  
            catch(...)
349  
            {
349  
            {
350  
                state->core_.set_winner_exception(std::current_exception());
350  
                state->core_.set_winner_exception(std::current_exception());
351  
            }
351  
            }
352  
        }
352  
        }
353  
    }
353  
    }
354  
    else
354  
    else
355  
    {
355  
    {
356  
        // Error: record but don't win
356  
        // Error: record but don't win
357  
        state->record_error(result.ec);
357  
        state->record_error(result.ec);
358  
    }
358  
    }
359  
}
359  
}
360  

360  

361  
// Launcher for io_result-aware when_any.
361  
// Launcher for io_result-aware when_any.
362  
template<IoAwaitable... Awaitables>
362  
template<IoAwaitable... Awaitables>
363  
class when_any_io_launcher
363  
class when_any_io_launcher
364  
{
364  
{
365  
    using state_type = when_any_io_state<
365  
    using state_type = when_any_io_state<
366  
        io_result_payload_t<awaitable_result_t<Awaitables>>...>;
366  
        io_result_payload_t<awaitable_result_t<Awaitables>>...>;
367  

367  

368  
    std::tuple<Awaitables...>* tasks_;
368  
    std::tuple<Awaitables...>* tasks_;
369  
    state_type* state_;
369  
    state_type* state_;
370  

370  

371  
public:
371  
public:
372  
    when_any_io_launcher(
372  
    when_any_io_launcher(
373  
        std::tuple<Awaitables...>* tasks,
373  
        std::tuple<Awaitables...>* tasks,
374  
        state_type* state)
374  
        state_type* state)
375  
        : tasks_(tasks)
375  
        : tasks_(tasks)
376  
        , state_(state)
376  
        , state_(state)
377  
    {
377  
    {
378  
    }
378  
    }
379  

379  

380  
    bool await_ready() const noexcept
380  
    bool await_ready() const noexcept
381  
    {
381  
    {
382  
        return sizeof...(Awaitables) == 0;
382  
        return sizeof...(Awaitables) == 0;
383  
    }
383  
    }
384  

384  

385  
    std::coroutine_handle<> await_suspend(
385  
    std::coroutine_handle<> await_suspend(
386  
        std::coroutine_handle<> continuation, io_env const* caller_env)
386  
        std::coroutine_handle<> continuation, io_env const* caller_env)
387  
    {
387  
    {
388  
        state_->core_.continuation_.h = continuation;
388  
        state_->core_.continuation_.h = continuation;
389  
        state_->core_.caller_env_ = caller_env;
389  
        state_->core_.caller_env_ = caller_env;
390  

390  

391  
        if(caller_env->stop_token.stop_possible())
391  
        if(caller_env->stop_token.stop_possible())
392  
        {
392  
        {
393  
            state_->core_.parent_stop_callback_.emplace(
393  
            state_->core_.parent_stop_callback_.emplace(
394  
                caller_env->stop_token,
394  
                caller_env->stop_token,
395  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
395  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
396  

396  

397  
            if(caller_env->stop_token.stop_requested())
397  
            if(caller_env->stop_token.stop_requested())
398  
                state_->core_.stop_source_.request_stop();
398  
                state_->core_.stop_source_.request_stop();
399  
        }
399  
        }
400  

400  

401  
        auto token = state_->core_.stop_source_.get_token();
401  
        auto token = state_->core_.stop_source_.get_token();
402  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
402  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
403  
            (..., launch_one<Is>(caller_env->executor, token));
403  
            (..., launch_one<Is>(caller_env->executor, token));
404  
        }(std::index_sequence_for<Awaitables...>{});
404  
        }(std::index_sequence_for<Awaitables...>{});
405  

405  

406  
        return std::noop_coroutine();
406  
        return std::noop_coroutine();
407  
    }
407  
    }
408  

408  

409  
    void await_resume() const noexcept {}
409  
    void await_resume() const noexcept {}
410  

410  

411  
private:
411  
private:
412  
    template<std::size_t I>
412  
    template<std::size_t I>
413  
    void launch_one(executor_ref caller_ex, std::stop_token token)
413  
    void launch_one(executor_ref caller_ex, std::stop_token token)
414  
    {
414  
    {
415  
        auto runner = make_when_any_io_runner<I>(
415  
        auto runner = make_when_any_io_runner<I>(
416  
            std::move(std::get<I>(*tasks_)), state_);
416  
            std::move(std::get<I>(*tasks_)), state_);
417  

417  

418  
        auto h = runner.release();
418  
        auto h = runner.release();
419  
        h.promise().state_ = state_;
419  
        h.promise().state_ = state_;
420  
        h.promise().index_ = I;
420  
        h.promise().index_ = I;
421  
        h.promise().env_ = io_env{caller_ex, token,
421  
        h.promise().env_ = io_env{caller_ex, token,
422  
            state_->core_.caller_env_->frame_allocator};
422  
            state_->core_.caller_env_->frame_allocator};
423  

423  

424  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
424  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
425  
        caller_ex.post(state_->runner_handles_[I]);
425  
        caller_ex.post(state_->runner_handles_[I]);
426  
    }
426  
    }
427  
};
427  
};
428  

428  

429  
/** Shared state for homogeneous io_result-aware when_any (range overload).
429  
/** Shared state for homogeneous io_result-aware when_any (range overload).
430  

430  

431  
    @tparam T The payload type extracted from io_result.
431  
    @tparam T The payload type extracted from io_result.
432  
*/
432  
*/
433  
template<typename T>
433  
template<typename T>
434  
struct when_any_io_homogeneous_state
434  
struct when_any_io_homogeneous_state
435  
{
435  
{
436  
    when_any_core core_;
436  
    when_any_core core_;
437  
    std::optional<T> result_;
437  
    std::optional<T> result_;
438  
    std::unique_ptr<continuation[]> runner_handles_;
438  
    std::unique_ptr<continuation[]> runner_handles_;
439  

439  

440  
    std::mutex failure_mu_;
440  
    std::mutex failure_mu_;
441  
    std::error_code last_error_;
441  
    std::error_code last_error_;
442  
    std::exception_ptr last_exception_;
442  
    std::exception_ptr last_exception_;
443  

443  

444  
    explicit when_any_io_homogeneous_state(std::size_t count)
444  
    explicit when_any_io_homogeneous_state(std::size_t count)
445  
        : core_(count)
445  
        : core_(count)
446  
        , runner_handles_(std::make_unique<continuation[]>(count))
446  
        , runner_handles_(std::make_unique<continuation[]>(count))
447  
    {
447  
    {
448  
    }
448  
    }
449  

449  

450  
    void record_error(std::error_code ec)
450  
    void record_error(std::error_code ec)
451  
    {
451  
    {
452  
        std::lock_guard lk(failure_mu_);
452  
        std::lock_guard lk(failure_mu_);
453  
        last_error_ = ec;
453  
        last_error_ = ec;
454  
        last_exception_ = nullptr;
454  
        last_exception_ = nullptr;
455  
    }
455  
    }
456  

456  

457  
    void record_exception(std::exception_ptr ep)
457  
    void record_exception(std::exception_ptr ep)
458  
    {
458  
    {
459  
        std::lock_guard lk(failure_mu_);
459  
        std::lock_guard lk(failure_mu_);
460  
        last_exception_ = ep;
460  
        last_exception_ = ep;
461  
        last_error_ = {};
461  
        last_error_ = {};
462  
    }
462  
    }
463  
};
463  
};
464  

464  

465  
/** Specialization for void io_result children (no payload storage). */
465  
/** Specialization for void io_result children (no payload storage). */
466  
template<>
466  
template<>
467  
struct when_any_io_homogeneous_state<std::tuple<>>
467  
struct when_any_io_homogeneous_state<std::tuple<>>
468  
{
468  
{
469  
    when_any_core core_;
469  
    when_any_core core_;
470  
    std::unique_ptr<continuation[]> runner_handles_;
470  
    std::unique_ptr<continuation[]> runner_handles_;
471  

471  

472  
    std::mutex failure_mu_;
472  
    std::mutex failure_mu_;
473  
    std::error_code last_error_;
473  
    std::error_code last_error_;
474  
    std::exception_ptr last_exception_;
474  
    std::exception_ptr last_exception_;
475  

475  

476  
    explicit when_any_io_homogeneous_state(std::size_t count)
476  
    explicit when_any_io_homogeneous_state(std::size_t count)
477  
        : core_(count)
477  
        : core_(count)
478  
        , runner_handles_(std::make_unique<continuation[]>(count))
478  
        , runner_handles_(std::make_unique<continuation[]>(count))
479  
    {
479  
    {
480  
    }
480  
    }
481  

481  

482  
    void record_error(std::error_code ec)
482  
    void record_error(std::error_code ec)
483  
    {
483  
    {
484  
        std::lock_guard lk(failure_mu_);
484  
        std::lock_guard lk(failure_mu_);
485  
        last_error_ = ec;
485  
        last_error_ = ec;
486  
        last_exception_ = nullptr;
486  
        last_exception_ = nullptr;
487  
    }
487  
    }
488  

488  

489  
    void record_exception(std::exception_ptr ep)
489  
    void record_exception(std::exception_ptr ep)
490  
    {
490  
    {
491  
        std::lock_guard lk(failure_mu_);
491  
        std::lock_guard lk(failure_mu_);
492  
        last_exception_ = ep;
492  
        last_exception_ = ep;
493  
        last_error_ = {};
493  
        last_error_ = {};
494  
    }
494  
    }
495  
};
495  
};
496  

496  

497  
/** Create an io_result-aware runner for homogeneous when_any (range path).
497  
/** Create an io_result-aware runner for homogeneous when_any (range path).
498  

498  

499  
    Only tries to win when the child returns !ec.
499  
    Only tries to win when the child returns !ec.
500  
*/
500  
*/
501  
template<IoAwaitable Awaitable, typename StateType>
501  
template<IoAwaitable Awaitable, typename StateType>
502  
when_any_io_runner<StateType>
502  
when_any_io_runner<StateType>
503  
make_when_any_io_homogeneous_runner(
503  
make_when_any_io_homogeneous_runner(
504  
    Awaitable inner, StateType* state, std::size_t index)
504  
    Awaitable inner, StateType* state, std::size_t index)
505  
{
505  
{
506  
    auto result = co_await std::move(inner);
506  
    auto result = co_await std::move(inner);
507  

507  

508  
    if(!result.ec)
508  
    if(!result.ec)
509  
    {
509  
    {
510  
        if(state->core_.try_win(index))
510  
        if(state->core_.try_win(index))
511  
        {
511  
        {
512  
            using PayloadT = io_result_payload_t<
512  
            using PayloadT = io_result_payload_t<
513  
                awaitable_result_t<Awaitable>>;
513  
                awaitable_result_t<Awaitable>>;
514  
            if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
514  
            if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
515  
            {
515  
            {
516  
                try
516  
                try
517  
                {
517  
                {
518  
                    state->result_.emplace(
518  
                    state->result_.emplace(
519  
                        extract_io_payload(std::move(result)));
519  
                        extract_io_payload(std::move(result)));
520  
                }
520  
                }
521  
                catch(...)
521  
                catch(...)
522  
                {
522  
                {
523  
                    state->core_.set_winner_exception(
523  
                    state->core_.set_winner_exception(
524  
                        std::current_exception());
524  
                        std::current_exception());
525  
                }
525  
                }
526  
            }
526  
            }
527  
        }
527  
        }
528  
    }
528  
    }
529  
    else
529  
    else
530  
    {
530  
    {
531  
        state->record_error(result.ec);
531  
        state->record_error(result.ec);
532  
    }
532  
    }
533  
}
533  
}
534  

534  

535  
/** Launches all io_result-aware homogeneous runners concurrently. */
535  
/** Launches all io_result-aware homogeneous runners concurrently. */
536  
template<IoAwaitableRange Range>
536  
template<IoAwaitableRange Range>
537  
class when_any_io_homogeneous_launcher
537  
class when_any_io_homogeneous_launcher
538  
{
538  
{
539  
    using Awaitable = std::ranges::range_value_t<Range>;
539  
    using Awaitable = std::ranges::range_value_t<Range>;
540  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
540  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
541  

541  

542  
    Range* range_;
542  
    Range* range_;
543  
    when_any_io_homogeneous_state<PayloadT>* state_;
543  
    when_any_io_homogeneous_state<PayloadT>* state_;
544  

544  

545  
public:
545  
public:
546  
    when_any_io_homogeneous_launcher(
546  
    when_any_io_homogeneous_launcher(
547  
        Range* range,
547  
        Range* range,
548  
        when_any_io_homogeneous_state<PayloadT>* state)
548  
        when_any_io_homogeneous_state<PayloadT>* state)
549  
        : range_(range)
549  
        : range_(range)
550  
        , state_(state)
550  
        , state_(state)
551  
    {
551  
    {
552  
    }
552  
    }
553  

553  

554  
    bool await_ready() const noexcept
554  
    bool await_ready() const noexcept
555  
    {
555  
    {
556  
        return std::ranges::empty(*range_);
556  
        return std::ranges::empty(*range_);
557  
    }
557  
    }
558  

558  

559  
    std::coroutine_handle<> await_suspend(
559  
    std::coroutine_handle<> await_suspend(
560  
        std::coroutine_handle<> continuation, io_env const* caller_env)
560  
        std::coroutine_handle<> continuation, io_env const* caller_env)
561  
    {
561  
    {
562  
        state_->core_.continuation_.h = continuation;
562  
        state_->core_.continuation_.h = continuation;
563  
        state_->core_.caller_env_ = caller_env;
563  
        state_->core_.caller_env_ = caller_env;
564  

564  

565  
        if(caller_env->stop_token.stop_possible())
565  
        if(caller_env->stop_token.stop_possible())
566  
        {
566  
        {
567  
            state_->core_.parent_stop_callback_.emplace(
567  
            state_->core_.parent_stop_callback_.emplace(
568  
                caller_env->stop_token,
568  
                caller_env->stop_token,
569  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
569  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
570  

570  

571  
            if(caller_env->stop_token.stop_requested())
571  
            if(caller_env->stop_token.stop_requested())
572  
                state_->core_.stop_source_.request_stop();
572  
                state_->core_.stop_source_.request_stop();
573  
        }
573  
        }
574  

574  

575  
        auto token = state_->core_.stop_source_.get_token();
575  
        auto token = state_->core_.stop_source_.get_token();
576  

576  

577  
        // Phase 1: Create all runners without dispatching.
577  
        // Phase 1: Create all runners without dispatching.
578  
        std::size_t index = 0;
578  
        std::size_t index = 0;
579  
        for(auto&& a : *range_)
579  
        for(auto&& a : *range_)
580  
        {
580  
        {
581  
            auto runner = make_when_any_io_homogeneous_runner(
581  
            auto runner = make_when_any_io_homogeneous_runner(
582  
                std::move(a), state_, index);
582  
                std::move(a), state_, index);
583  

583  

584  
            auto h = runner.release();
584  
            auto h = runner.release();
585  
            h.promise().state_ = state_;
585  
            h.promise().state_ = state_;
586  
            h.promise().index_ = index;
586  
            h.promise().index_ = index;
587  
            h.promise().env_ = io_env{caller_env->executor, token,
587  
            h.promise().env_ = io_env{caller_env->executor, token,
588  
                caller_env->frame_allocator};
588  
                caller_env->frame_allocator};
589  

589  

590  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
590  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
591  
            ++index;
591  
            ++index;
592  
        }
592  
        }
593  

593  

594  
        // Phase 2: Post all runners. Any may complete synchronously.
594  
        // Phase 2: Post all runners. Any may complete synchronously.
595  
        auto* handles = state_->runner_handles_.get();
595  
        auto* handles = state_->runner_handles_.get();
596  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
596  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
597  
        for(std::size_t i = 0; i < count; ++i)
597  
        for(std::size_t i = 0; i < count; ++i)
598  
            caller_env->executor.post(handles[i]);
598  
            caller_env->executor.post(handles[i]);
599  

599  

600  
        return std::noop_coroutine();
600  
        return std::noop_coroutine();
601  
    }
601  
    }
602  

602  

603  
    void await_resume() const noexcept {}
603  
    void await_resume() const noexcept {}
604  
};
604  
};
605  

605  

606  
} // namespace detail
606  
} // namespace detail
607  

607  

608  
/** Race a range of io_result-returning awaitables (non-void payloads).
608  
/** Race a range of io_result-returning awaitables (non-void payloads).
609  

609  

610  
    Only a child returning !ec can win. Errors and exceptions do not
610  
    Only a child returning !ec can win. Errors and exceptions do not
611  
    claim winner status. If all children fail, the last failure
611  
    claim winner status. If all children fail, the last failure
612  
    is reported — either the last error_code at variant index 0,
612  
    is reported — either the last error_code at variant index 0,
613  
    or the last exception rethrown.
613  
    or the last exception rethrown.
614  

614  

615  
    @param awaitables Range of io_result-returning awaitables (must
615  
    @param awaitables Range of io_result-returning awaitables (must
616  
        not be empty).
616  
        not be empty).
617  

617  

618  
    @return A task yielding variant<error_code, pair<size_t, PayloadT>>
618  
    @return A task yielding variant<error_code, pair<size_t, PayloadT>>
619  
        where index 0 is failure and index 1 carries the winner's
619  
        where index 0 is failure and index 1 carries the winner's
620  
        index and payload.
620  
        index and payload.
621  

621  

622  
    @throws std::invalid_argument if range is empty.
622  
    @throws std::invalid_argument if range is empty.
623  
    @throws Rethrows last exception when no winner and the last
623  
    @throws Rethrows last exception when no winner and the last
624  
        failure was an exception.
624  
        failure was an exception.
625  

625  

626  
    @par Example
626  
    @par Example
627  
    @code
627  
    @code
628  
    task<void> example()
628  
    task<void> example()
629  
    {
629  
    {
630  
        std::vector<io_task<size_t>> reads;
630  
        std::vector<io_task<size_t>> reads;
631  
        for (auto& buf : buffers)
631  
        for (auto& buf : buffers)
632  
            reads.push_back(stream.read_some(buf));
632  
            reads.push_back(stream.read_some(buf));
633  

633  

634  
        auto result = co_await when_any(std::move(reads));
634  
        auto result = co_await when_any(std::move(reads));
635  
        if (result.index() == 1)
635  
        if (result.index() == 1)
636  
        {
636  
        {
637  
            auto [idx, n] = std::get<1>(result);
637  
            auto [idx, n] = std::get<1>(result);
638  
        }
638  
        }
639  
    }
639  
    }
640  
    @endcode
640  
    @endcode
641  

641  

642  
    @see IoAwaitableRange, when_any
642  
    @see IoAwaitableRange, when_any
643  
*/
643  
*/
644  
template<IoAwaitableRange R>
644  
template<IoAwaitableRange R>
645  
    requires detail::is_io_result_v<
645  
    requires detail::is_io_result_v<
646  
        awaitable_result_t<std::ranges::range_value_t<R>>>
646  
        awaitable_result_t<std::ranges::range_value_t<R>>>
647  
    && (!std::is_same_v<
647  
    && (!std::is_same_v<
648  
            detail::io_result_payload_t<
648  
            detail::io_result_payload_t<
649  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
649  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
650  
            std::tuple<>>)
650  
            std::tuple<>>)
651  
[[nodiscard]] auto when_any(R&& awaitables)
651  
[[nodiscard]] auto when_any(R&& awaitables)
652  
    -> task<std::variant<std::error_code,
652  
    -> task<std::variant<std::error_code,
653  
        std::pair<std::size_t,
653  
        std::pair<std::size_t,
654  
            detail::io_result_payload_t<
654  
            detail::io_result_payload_t<
655  
                awaitable_result_t<std::ranges::range_value_t<R>>>>>>
655  
                awaitable_result_t<std::ranges::range_value_t<R>>>>>>
656  
{
656  
{
657  
    using Awaitable = std::ranges::range_value_t<R>;
657  
    using Awaitable = std::ranges::range_value_t<R>;
658  
    using PayloadT = detail::io_result_payload_t<
658  
    using PayloadT = detail::io_result_payload_t<
659  
        awaitable_result_t<Awaitable>>;
659  
        awaitable_result_t<Awaitable>>;
660  
    using result_type = std::variant<std::error_code,
660  
    using result_type = std::variant<std::error_code,
661  
        std::pair<std::size_t, PayloadT>>;
661  
        std::pair<std::size_t, PayloadT>>;
662  
    using OwnedRange = std::remove_cvref_t<R>;
662  
    using OwnedRange = std::remove_cvref_t<R>;
663  

663  

664  
    auto count = std::ranges::size(awaitables);
664  
    auto count = std::ranges::size(awaitables);
665  
    if(count == 0)
665  
    if(count == 0)
666  
        throw std::invalid_argument("when_any requires at least one awaitable");
666  
        throw std::invalid_argument("when_any requires at least one awaitable");
667  

667  

668  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
668  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
669  

669  

670  
    detail::when_any_io_homogeneous_state<PayloadT> state(count);
670  
    detail::when_any_io_homogeneous_state<PayloadT> state(count);
671  

671  

672  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
672  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
673  
        &owned_awaitables, &state);
673  
        &owned_awaitables, &state);
674  

674  

675  
    // Winner found
675  
    // Winner found
676  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
676  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
677  
    {
677  
    {
678  
        if(state.core_.winner_exception_)
678  
        if(state.core_.winner_exception_)
679  
            std::rethrow_exception(state.core_.winner_exception_);
679  
            std::rethrow_exception(state.core_.winner_exception_);
680  
        co_return result_type{std::in_place_index<1>,
680  
        co_return result_type{std::in_place_index<1>,
681  
            std::pair{state.core_.winner_index_, std::move(*state.result_)}};
681  
            std::pair{state.core_.winner_index_, std::move(*state.result_)}};
682  
    }
682  
    }
683  

683  

684  
    // No winner — report last failure
684  
    // No winner — report last failure
685  
    if(state.last_exception_)
685  
    if(state.last_exception_)
686  
        std::rethrow_exception(state.last_exception_);
686  
        std::rethrow_exception(state.last_exception_);
687  
    co_return result_type{std::in_place_index<0>, state.last_error_};
687  
    co_return result_type{std::in_place_index<0>, state.last_error_};
688  
}
688  
}
689  

689  

690  
/** Race a range of void io_result-returning awaitables.
690  
/** Race a range of void io_result-returning awaitables.
691  

691  

692  
    Only a child returning !ec can win. Returns the winner's index
692  
    Only a child returning !ec can win. Returns the winner's index
693  
    at variant index 1, or error_code at index 0 on all-fail.
693  
    at variant index 1, or error_code at index 0 on all-fail.
694  

694  

695  
    @param awaitables Range of io_result<>-returning awaitables (must
695  
    @param awaitables Range of io_result<>-returning awaitables (must
696  
        not be empty).
696  
        not be empty).
697  

697  

698  
    @return A task yielding variant<error_code, size_t> where index 0
698  
    @return A task yielding variant<error_code, size_t> where index 0
699  
        is failure and index 1 carries the winner's index.
699  
        is failure and index 1 carries the winner's index.
700  

700  

701  
    @throws std::invalid_argument if range is empty.
701  
    @throws std::invalid_argument if range is empty.
702  
    @throws Rethrows first exception when no winner and at least one
702  
    @throws Rethrows first exception when no winner and at least one
703  
        child threw.
703  
        child threw.
704  

704  

705  
    @par Example
705  
    @par Example
706  
    @code
706  
    @code
707  
    task<void> example()
707  
    task<void> example()
708  
    {
708  
    {
709  
        std::vector<io_task<>> jobs;
709  
        std::vector<io_task<>> jobs;
710  
        jobs.push_back(background_work_a());
710  
        jobs.push_back(background_work_a());
711  
        jobs.push_back(background_work_b());
711  
        jobs.push_back(background_work_b());
712  

712  

713  
        auto result = co_await when_any(std::move(jobs));
713  
        auto result = co_await when_any(std::move(jobs));
714  
        if (result.index() == 1)
714  
        if (result.index() == 1)
715  
        {
715  
        {
716  
            auto winner = std::get<1>(result);
716  
            auto winner = std::get<1>(result);
717  
        }
717  
        }
718  
    }
718  
    }
719  
    @endcode
719  
    @endcode
720  

720  

721  
    @see IoAwaitableRange, when_any
721  
    @see IoAwaitableRange, when_any
722  
*/
722  
*/
723  
template<IoAwaitableRange R>
723  
template<IoAwaitableRange R>
724  
    requires detail::is_io_result_v<
724  
    requires detail::is_io_result_v<
725  
        awaitable_result_t<std::ranges::range_value_t<R>>>
725  
        awaitable_result_t<std::ranges::range_value_t<R>>>
726  
    && std::is_same_v<
726  
    && std::is_same_v<
727  
            detail::io_result_payload_t<
727  
            detail::io_result_payload_t<
728  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
728  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
729  
            std::tuple<>>
729  
            std::tuple<>>
730  
[[nodiscard]] auto when_any(R&& awaitables)
730  
[[nodiscard]] auto when_any(R&& awaitables)
731  
    -> task<std::variant<std::error_code, std::size_t>>
731  
    -> task<std::variant<std::error_code, std::size_t>>
732  
{
732  
{
733  
    using OwnedRange = std::remove_cvref_t<R>;
733  
    using OwnedRange = std::remove_cvref_t<R>;
734  
    using result_type = std::variant<std::error_code, std::size_t>;
734  
    using result_type = std::variant<std::error_code, std::size_t>;
735  

735  

736  
    auto count = std::ranges::size(awaitables);
736  
    auto count = std::ranges::size(awaitables);
737  
    if(count == 0)
737  
    if(count == 0)
738  
        throw std::invalid_argument("when_any requires at least one awaitable");
738  
        throw std::invalid_argument("when_any requires at least one awaitable");
739  

739  

740  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
740  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
741  

741  

742  
    detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
742  
    detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
743  

743  

744  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
744  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
745  
        &owned_awaitables, &state);
745  
        &owned_awaitables, &state);
746  

746  

747  
    // Winner found
747  
    // Winner found
748  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
748  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
749  
    {
749  
    {
750  
        if(state.core_.winner_exception_)
750  
        if(state.core_.winner_exception_)
751  
            std::rethrow_exception(state.core_.winner_exception_);
751  
            std::rethrow_exception(state.core_.winner_exception_);
752  
        co_return result_type{std::in_place_index<1>,
752  
        co_return result_type{std::in_place_index<1>,
753  
            state.core_.winner_index_};
753  
            state.core_.winner_index_};
754  
    }
754  
    }
755  

755  

756  
    // No winner — report last failure
756  
    // No winner — report last failure
757  
    if(state.last_exception_)
757  
    if(state.last_exception_)
758  
        std::rethrow_exception(state.last_exception_);
758  
        std::rethrow_exception(state.last_exception_);
759  
    co_return result_type{std::in_place_index<0>, state.last_error_};
759  
    co_return result_type{std::in_place_index<0>, state.last_error_};
760  
}
760  
}
761  

761  

762  
/** Race io_result-returning awaitables, selecting the first success.
762  
/** Race io_result-returning awaitables, selecting the first success.
763  

763  

764  
    Overload selected when all children return io_result<Ts...>.
764  
    Overload selected when all children return io_result<Ts...>.
765  
    Only a child returning !ec can win. Errors and exceptions do
765  
    Only a child returning !ec can win. Errors and exceptions do
766  
    not claim winner status.
766  
    not claim winner status.
767  

767  

768  
    @return A task yielding variant<error_code, R1, ..., Rn> where
768  
    @return A task yielding variant<error_code, R1, ..., Rn> where
769  
        index 0 is the failure/no-winner case and index i+1
769  
        index 0 is the failure/no-winner case and index i+1
770  
        identifies the winning child.
770  
        identifies the winning child.
771  
*/
771  
*/
772  
template<IoAwaitable... As>
772  
template<IoAwaitable... As>
773  
    requires (sizeof...(As) > 0)
773  
    requires (sizeof...(As) > 0)
774  
          && detail::all_io_result_awaitables<As...>
774  
          && detail::all_io_result_awaitables<As...>
775  
[[nodiscard]] auto when_any(As... as)
775  
[[nodiscard]] auto when_any(As... as)
776  
    -> task<std::variant<
776  
    -> task<std::variant<
777  
        std::error_code,
777  
        std::error_code,
778  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
778  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
779  
{
779  
{
780  
    using result_type = std::variant<
780  
    using result_type = std::variant<
781  
        std::error_code,
781  
        std::error_code,
782  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
782  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
783  

783  

784  
    detail::when_any_io_state<
784  
    detail::when_any_io_state<
785  
        detail::io_result_payload_t<awaitable_result_t<As>>...> state;
785  
        detail::io_result_payload_t<awaitable_result_t<As>>...> state;
786  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
786  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
787  

787  

788  
    co_await detail::when_any_io_launcher<As...>(
788  
    co_await detail::when_any_io_launcher<As...>(
789  
        &awaitable_tuple, &state);
789  
        &awaitable_tuple, &state);
790  

790  

791  
    // Winner found: return their result
791  
    // Winner found: return their result
792  
    if(state.result_.has_value())
792  
    if(state.result_.has_value())
793  
        co_return std::move(*state.result_);
793  
        co_return std::move(*state.result_);
794  

794  

795  
    // Winner claimed but payload construction failed
795  
    // Winner claimed but payload construction failed
796  
    if(state.core_.winner_exception_)
796  
    if(state.core_.winner_exception_)
797  
        std::rethrow_exception(state.core_.winner_exception_);
797  
        std::rethrow_exception(state.core_.winner_exception_);
798  

798  

799  
    // No winner — report last failure
799  
    // No winner — report last failure
800  
    if(state.last_exception_)
800  
    if(state.last_exception_)
801  
        std::rethrow_exception(state.last_exception_);
801  
        std::rethrow_exception(state.last_exception_);
802  
    co_return result_type{std::in_place_index<0>, state.last_error_};
802  
    co_return result_type{std::in_place_index<0>, state.last_error_};
803  
}
803  
}
804  

804  

805  
} // namespace capy
805  
} // namespace capy
806  
} // namespace boost
806  
} // namespace boost
807  

807  

808  
#endif
808  
#endif