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/void_to_monostate.hpp>
 
14  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
15  
#include <boost/capy/concept/executor.hpp>
14  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/io_awaitable.hpp>
15  
#include <boost/capy/concept/io_awaitable.hpp>
17  
#include <coroutine>
16  
#include <coroutine>
18  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
18  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
19  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/task.hpp>
20  
#include <boost/capy/task.hpp>
22  

21  

23  
#include <array>
22  
#include <array>
24  
#include <atomic>
23  
#include <atomic>
25  
#include <exception>
24  
#include <exception>
26  
#include <optional>
25  
#include <optional>
27  
#include <ranges>
26  
#include <ranges>
28  
#include <stdexcept>
27  
#include <stdexcept>
29  
#include <stop_token>
28  
#include <stop_token>
30  
#include <tuple>
29  
#include <tuple>
31  
#include <type_traits>
30  
#include <type_traits>
32  
#include <utility>
31  
#include <utility>
33  
#include <variant>
32  
#include <variant>
34  
#include <vector>
33  
#include <vector>
35  

34  

36  
/*
35  
/*
37  
   when_any - Race multiple tasks, return first completion
36  
   when_any - Race multiple tasks, return first completion
38  
   ========================================================
37  
   ========================================================
39  

38  

40  
   OVERVIEW:
39  
   OVERVIEW:
41  
   ---------
40  
   ---------
42  
   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
43  
   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
44  
   waits for them to acknowledge before returning.
43  
   waits for them to acknowledge before returning.
45  

44  

46  
   ARCHITECTURE:
45  
   ARCHITECTURE:
47  
   -------------
46  
   -------------
48  
   The design mirrors when_all but with inverted completion semantics:
47  
   The design mirrors when_all but with inverted completion semantics:
49  

48  

50  
     when_all:  complete when remaining_count reaches 0 (all done)
49  
     when_all:  complete when remaining_count reaches 0 (all done)
51  
     when_any:  complete when has_winner becomes true (first done)
50  
     when_any:  complete when has_winner becomes true (first done)
52  
                BUT still wait for remaining_count to reach 0 for cleanup
51  
                BUT still wait for remaining_count to reach 0 for cleanup
53  

52  

54  
   Key components:
53  
   Key components:
55  
     - when_any_state:    Shared state tracking winner and completion
54  
     - when_any_state:    Shared state tracking winner and completion
56  
     - when_any_runner:   Wrapper coroutine for each child task
55  
     - when_any_runner:   Wrapper coroutine for each child task
57  
     - when_any_launcher: Awaitable that starts all runners concurrently
56  
     - when_any_launcher: Awaitable that starts all runners concurrently
58  

57  

59  
   CRITICAL INVARIANTS:
58  
   CRITICAL INVARIANTS:
60  
   --------------------
59  
   --------------------
61  
   1. Exactly one task becomes the winner (via atomic compare_exchange)
60  
   1. Exactly one task becomes the winner (via atomic compare_exchange)
62  
   2. All tasks must complete before parent resumes (cleanup safety)
61  
   2. All tasks must complete before parent resumes (cleanup safety)
63  
   3. Stop is requested immediately when winner is determined
62  
   3. Stop is requested immediately when winner is determined
64  
   4. Only the winner's result/exception is stored
63  
   4. Only the winner's result/exception is stored
65  

64  

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

70  

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

75  

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

80  

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

84  

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

91  

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

101  

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

107  

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

114  

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

117  

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

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

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

119  
namespace detail {
129  
namespace detail {
120  

130  

121  
/** Core shared state for when_any operations.
131  
/** Core shared state for when_any operations.
122  

132  

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

136  

127  
    @par Thread Safety
137  
    @par Thread Safety
128  
    Atomic operations protect winner selection and completion count.
138  
    Atomic operations protect winner selection and completion count.
129  
*/
139  
*/
130  
struct when_any_core
140  
struct when_any_core
131  
{
141  
{
132  
    std::atomic<std::size_t> remaining_count_;
142  
    std::atomic<std::size_t> remaining_count_;
133  
    std::size_t winner_index_{0};
143  
    std::size_t winner_index_{0};
134  
    std::exception_ptr winner_exception_;
144  
    std::exception_ptr winner_exception_;
135  
    std::stop_source stop_source_;
145  
    std::stop_source stop_source_;
136  

146  

137  
    // Bridges parent's stop token to our stop_source
147  
    // Bridges parent's stop token to our stop_source
138  
    struct stop_callback_fn
148  
    struct stop_callback_fn
139  
    {
149  
    {
140  
        std::stop_source* source_;
150  
        std::stop_source* source_;
141  
        void operator()() const noexcept { source_->request_stop(); }
151  
        void operator()() const noexcept { source_->request_stop(); }
142  
    };
152  
    };
143  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
153  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
144  
    std::optional<stop_callback_t> parent_stop_callback_;
154  
    std::optional<stop_callback_t> parent_stop_callback_;
145  

155  

146  
    std::coroutine_handle<> continuation_;
156  
    std::coroutine_handle<> continuation_;
147  
    io_env const* caller_env_ = nullptr;
157  
    io_env const* caller_env_ = nullptr;
148  

158  

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

161  

152  
    explicit when_any_core(std::size_t count) noexcept
162  
    explicit when_any_core(std::size_t count) noexcept
153  
        : remaining_count_(count)
163  
        : remaining_count_(count)
154  
    {
164  
    {
155  
    }
165  
    }
156  

166  

157  
    /** Atomically claim winner status; exactly one task succeeds. */
167  
    /** Atomically claim winner status; exactly one task succeeds. */
158  
    bool try_win(std::size_t index) noexcept
168  
    bool try_win(std::size_t index) noexcept
159  
    {
169  
    {
160  
        bool expected = false;
170  
        bool expected = false;
161  
        if(has_winner_.compare_exchange_strong(
171  
        if(has_winner_.compare_exchange_strong(
162  
            expected, true, std::memory_order_acq_rel))
172  
            expected, true, std::memory_order_acq_rel))
163  
        {
173  
        {
164  
            winner_index_ = index;
174  
            winner_index_ = index;
165  
            stop_source_.request_stop();
175  
            stop_source_.request_stop();
166  
            return true;
176  
            return true;
167  
        }
177  
        }
168  
        return false;
178  
        return false;
169  
    }
179  
    }
170  

180  

171  
    /** @pre try_win() returned true. */
181  
    /** @pre try_win() returned true. */
172  
    void set_winner_exception(std::exception_ptr ep) noexcept
182  
    void set_winner_exception(std::exception_ptr ep) noexcept
173  
    {
183  
    {
174  
        winner_exception_ = ep;
184  
        winner_exception_ = ep;
175  
    }
185  
    }
176  

186  

177  
    // Runners signal completion directly via final_suspend; no member function needed.
187  
    // Runners signal completion directly via final_suspend; no member function needed.
178  
};
188  
};
179  

189  

180  
/** Shared state for heterogeneous when_any operation.
190  
/** Shared state for heterogeneous when_any operation.
181  

191  

182  
    Coordinates winner selection, result storage, and completion tracking
192  
    Coordinates winner selection, result storage, and completion tracking
183  
    for all child tasks in a when_any operation. Uses composition with
193  
    for all child tasks in a when_any operation. Uses composition with
184  
    when_any_core for shared functionality.
194  
    when_any_core for shared functionality.
185  

195  

186  
    @par Lifetime
196  
    @par Lifetime
187  
    Allocated on the parent coroutine's frame, outlives all runners.
197  
    Allocated on the parent coroutine's frame, outlives all runners.
188  

198  

189  
    @tparam Ts Task result types.
199  
    @tparam Ts Task result types.
190  
*/
200  
*/
191  
template<typename... Ts>
201  
template<typename... Ts>
192  
struct when_any_state
202  
struct when_any_state
193  
{
203  
{
194  
    static constexpr std::size_t task_count = sizeof...(Ts);
204  
    static constexpr std::size_t task_count = sizeof...(Ts);
195  
    using variant_type = std::variant<void_to_monostate_t<Ts>...>;
205  
    using variant_type = std::variant<void_to_monostate_t<Ts>...>;
196  

206  

197  
    when_any_core core_;
207  
    when_any_core core_;
198  
    std::optional<variant_type> result_;
208  
    std::optional<variant_type> result_;
199  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
209  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
200  

210  

201  
    when_any_state()
211  
    when_any_state()
202  
        : core_(task_count)
212  
        : core_(task_count)
203  
    {
213  
    {
204  
    }
214  
    }
205  

215  

206  
    // Runners self-destruct in final_suspend. No destruction needed here.
216  
    // Runners self-destruct in final_suspend. No destruction needed here.
207  

217  

208  
    /** @pre core_.try_win() returned true.
218  
    /** @pre core_.try_win() returned true.
209  
        @note Uses in_place_index (not type) for positional variant access.
219  
        @note Uses in_place_index (not type) for positional variant access.
210  
    */
220  
    */
211  
    template<std::size_t I, typename T>
221  
    template<std::size_t I, typename T>
212  
    void set_winner_result(T value)
222  
    void set_winner_result(T value)
213  
        noexcept(std::is_nothrow_move_constructible_v<T>)
223  
        noexcept(std::is_nothrow_move_constructible_v<T>)
214  
    {
224  
    {
215  
        result_.emplace(std::in_place_index<I>, std::move(value));
225  
        result_.emplace(std::in_place_index<I>, std::move(value));
216  
    }
226  
    }
217  

227  

218  
    /** @pre core_.try_win() returned true. */
228  
    /** @pre core_.try_win() returned true. */
219  
    template<std::size_t I>
229  
    template<std::size_t I>
220  
    void set_winner_void() noexcept
230  
    void set_winner_void() noexcept
221  
    {
231  
    {
222  
        result_.emplace(std::in_place_index<I>, std::monostate{});
232  
        result_.emplace(std::in_place_index<I>, std::monostate{});
223  
    }
233  
    }
224  
};
234  
};
225  

235  

226  
/** Wrapper coroutine that runs a single child task for when_any.
236  
/** Wrapper coroutine that runs a single child task for when_any.
227  

237  

228  
    Propagates executor/stop_token to the child, attempts to claim winner
238  
    Propagates executor/stop_token to the child, attempts to claim winner
229  
    status on completion, and signals completion for cleanup coordination.
239  
    status on completion, and signals completion for cleanup coordination.
230  

240  

231  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
241  
    @tparam StateType The state type (when_any_state or when_any_homogeneous_state).
232  
*/
242  
*/
233  
template<typename StateType>
243  
template<typename StateType>
234  
struct when_any_runner
244  
struct when_any_runner
235  
{
245  
{
236  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
246  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
237  
    {
247  
    {
238  
        StateType* state_ = nullptr;
248  
        StateType* state_ = nullptr;
239  
        std::size_t index_ = 0;
249  
        std::size_t index_ = 0;
240  
        io_env env_;
250  
        io_env env_;
241  

251  

242  
        when_any_runner get_return_object() noexcept
252  
        when_any_runner get_return_object() noexcept
243  
        {
253  
        {
244  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
254  
            return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
245  
        }
255  
        }
246  

256  

247  
        // Starts suspended; launcher sets up state/ex/token then resumes
257  
        // Starts suspended; launcher sets up state/ex/token then resumes
248  
        std::suspend_always initial_suspend() noexcept
258  
        std::suspend_always initial_suspend() noexcept
249  
        {
259  
        {
250  
            return {};
260  
            return {};
251  
        }
261  
        }
252  

262  

253  
        auto final_suspend() noexcept
263  
        auto final_suspend() noexcept
254  
        {
264  
        {
255  
            struct awaiter
265  
            struct awaiter
256  
            {
266  
            {
257  
                promise_type* p_;
267  
                promise_type* p_;
258  
                bool await_ready() const noexcept { return false; }
268  
                bool await_ready() const noexcept { return false; }
259  
                auto await_suspend(std::coroutine_handle<> h) noexcept
269  
                auto await_suspend(std::coroutine_handle<> h) noexcept
260  
                {
270  
                {
261  
                    // Extract everything needed before self-destruction.
271  
                    // Extract everything needed before self-destruction.
262  
                    auto& core = p_->state_->core_;
272  
                    auto& core = p_->state_->core_;
263  
                    auto* counter = &core.remaining_count_;
273  
                    auto* counter = &core.remaining_count_;
264  
                    auto* caller_env = core.caller_env_;
274  
                    auto* caller_env = core.caller_env_;
265  
                    auto cont = core.continuation_;
275  
                    auto cont = core.continuation_;
266  

276  

267  
                    h.destroy();
277  
                    h.destroy();
268  

278  

269  
                    // If last runner, dispatch parent for symmetric transfer.
279  
                    // If last runner, dispatch parent for symmetric transfer.
270  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
280  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
271  
                    if(remaining == 1)
281  
                    if(remaining == 1)
272  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
282  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
273  
                    return detail::symmetric_transfer(std::noop_coroutine());
283  
                    return detail::symmetric_transfer(std::noop_coroutine());
274  
                }
284  
                }
275  
                void await_resume() const noexcept {}
285  
                void await_resume() const noexcept {}
276  
            };
286  
            };
277  
            return awaiter{this};
287  
            return awaiter{this};
278  
        }
288  
        }
279  

289  

280  
        void return_void() noexcept {}
290  
        void return_void() noexcept {}
281  

291  

282  
        // Exceptions are valid completions in when_any (unlike when_all)
292  
        // Exceptions are valid completions in when_any (unlike when_all)
283  
        void unhandled_exception()
293  
        void unhandled_exception()
284  
        {
294  
        {
285  
            if(state_->core_.try_win(index_))
295  
            if(state_->core_.try_win(index_))
286  
                state_->core_.set_winner_exception(std::current_exception());
296  
                state_->core_.set_winner_exception(std::current_exception());
287  
        }
297  
        }
288  

298  

289  
        /** Injects executor and stop token into child awaitables. */
299  
        /** Injects executor and stop token into child awaitables. */
290  
        template<class Awaitable>
300  
        template<class Awaitable>
291  
        struct transform_awaiter
301  
        struct transform_awaiter
292  
        {
302  
        {
293  
            std::decay_t<Awaitable> a_;
303  
            std::decay_t<Awaitable> a_;
294  
            promise_type* p_;
304  
            promise_type* p_;
295  

305  

296  
            bool await_ready() { return a_.await_ready(); }
306  
            bool await_ready() { return a_.await_ready(); }
297  
            auto await_resume() { return a_.await_resume(); }
307  
            auto await_resume() { return a_.await_resume(); }
298  

308  

299  
            template<class Promise>
309  
            template<class Promise>
300  
            auto await_suspend(std::coroutine_handle<Promise> h)
310  
            auto await_suspend(std::coroutine_handle<Promise> h)
301  
            {
311  
            {
302  
                using R = decltype(a_.await_suspend(h, &p_->env_));
312  
                using R = decltype(a_.await_suspend(h, &p_->env_));
303  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
313  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
304  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
314  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
305  
                else
315  
                else
306  
                    return a_.await_suspend(h, &p_->env_);
316  
                    return a_.await_suspend(h, &p_->env_);
307  
            }
317  
            }
308  
        };
318  
        };
309  

319  

310  
        template<class Awaitable>
320  
        template<class Awaitable>
311  
        auto await_transform(Awaitable&& a)
321  
        auto await_transform(Awaitable&& a)
312  
        {
322  
        {
313  
            using A = std::decay_t<Awaitable>;
323  
            using A = std::decay_t<Awaitable>;
314  
            if constexpr (IoAwaitable<A>)
324  
            if constexpr (IoAwaitable<A>)
315  
            {
325  
            {
316  
                return transform_awaiter<Awaitable>{
326  
                return transform_awaiter<Awaitable>{
317  
                    std::forward<Awaitable>(a), this};
327  
                    std::forward<Awaitable>(a), this};
318  
            }
328  
            }
319  
            else
329  
            else
320  
            {
330  
            {
321  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
331  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
322  
            }
332  
            }
323  
        }
333  
        }
324  
    };
334  
    };
325  

335  

326  
    std::coroutine_handle<promise_type> h_;
336  
    std::coroutine_handle<promise_type> h_;
327  

337  

328  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
338  
    explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
329  
        : h_(h)
339  
        : h_(h)
330  
    {
340  
    {
331  
    }
341  
    }
332  

342  

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

345  

336  
    // Non-copyable
346  
    // Non-copyable
337  
    when_any_runner(when_any_runner const&) = delete;
347  
    when_any_runner(when_any_runner const&) = delete;
338  
    when_any_runner& operator=(when_any_runner const&) = delete;
348  
    when_any_runner& operator=(when_any_runner const&) = delete;
339  
    when_any_runner& operator=(when_any_runner&&) = delete;
349  
    when_any_runner& operator=(when_any_runner&&) = delete;
340  

350  

341  
    auto release() noexcept
351  
    auto release() noexcept
342  
    {
352  
    {
343  
        return std::exchange(h_, nullptr);
353  
        return std::exchange(h_, nullptr);
344  
    }
354  
    }
345  
};
355  
};
346  

356  

347  
/** Indexed overload for heterogeneous when_any (compile-time index).
357  
/** Indexed overload for heterogeneous when_any (compile-time index).
348  

358  

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

389  

380  
/** Runtime-index overload for homogeneous when_any (range path).
390  
/** Runtime-index overload for homogeneous when_any (range path).
381  

391  

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

427  

418  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
428  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
419  
template<IoAwaitable... Awaitables>
429  
template<IoAwaitable... Awaitables>
420  
class when_any_launcher
430  
class when_any_launcher
421  
{
431  
{
422  
    using state_type = when_any_state<awaitable_result_t<Awaitables>...>;
432  
    using state_type = when_any_state<awaitable_result_t<Awaitables>...>;
423  

433  

424  
    std::tuple<Awaitables...>* tasks_;
434  
    std::tuple<Awaitables...>* tasks_;
425  
    state_type* state_;
435  
    state_type* state_;
426  

436  

427  
public:
437  
public:
428  
    when_any_launcher(
438  
    when_any_launcher(
429  
        std::tuple<Awaitables...>* tasks,
439  
        std::tuple<Awaitables...>* tasks,
430  
        state_type* state)
440  
        state_type* state)
431  
        : tasks_(tasks)
441  
        : tasks_(tasks)
432  
        , state_(state)
442  
        , state_(state)
433  
    {
443  
    {
434  
    }
444  
    }
435  

445  

436  
    bool await_ready() const noexcept
446  
    bool await_ready() const noexcept
437  
    {
447  
    {
438  
        return sizeof...(Awaitables) == 0;
448  
        return sizeof...(Awaitables) == 0;
439  
    }
449  
    }
440  

450  

441  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
451  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
442  
        destroys this object before await_suspend returns. Must not reference
452  
        destroys this object before await_suspend returns. Must not reference
443  
        `this` after the final launch_one call.
453  
        `this` after the final launch_one call.
444  
    */
454  
    */
445  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
455  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
446  
    {
456  
    {
447  
        state_->core_.continuation_ = continuation;
457  
        state_->core_.continuation_ = continuation;
448  
        state_->core_.caller_env_ = caller_env;
458  
        state_->core_.caller_env_ = caller_env;
449  

459  

450  
        if(caller_env->stop_token.stop_possible())
460  
        if(caller_env->stop_token.stop_possible())
451  
        {
461  
        {
452  
            state_->core_.parent_stop_callback_.emplace(
462  
            state_->core_.parent_stop_callback_.emplace(
453  
                caller_env->stop_token,
463  
                caller_env->stop_token,
454  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
464  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
455  

465  

456  
            if(caller_env->stop_token.stop_requested())
466  
            if(caller_env->stop_token.stop_requested())
457  
                state_->core_.stop_source_.request_stop();
467  
                state_->core_.stop_source_.request_stop();
458  
        }
468  
        }
459  

469  

460  
        auto token = state_->core_.stop_source_.get_token();
470  
        auto token = state_->core_.stop_source_.get_token();
461  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
471  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
462  
            (..., launch_one<Is>(caller_env->executor, token));
472  
            (..., launch_one<Is>(caller_env->executor, token));
463  
        }(std::index_sequence_for<Awaitables...>{});
473  
        }(std::index_sequence_for<Awaitables...>{});
464  

474  

465  
        return std::noop_coroutine();
475  
        return std::noop_coroutine();
466  
    }
476  
    }
467  

477  

468  
    void await_resume() const noexcept
478  
    void await_resume() const noexcept
469  
    {
479  
    {
470  
    }
480  
    }
471  

481  

472  
private:
482  
private:
473  
    /** @pre Ex::dispatch() and std::coroutine_handle<>::resume() must not throw (handle may leak). */
483  
    /** @pre Ex::dispatch() and std::coroutine_handle<>::resume() must not throw (handle may leak). */
474  
    template<std::size_t I>
484  
    template<std::size_t I>
475  
    void launch_one(executor_ref caller_ex, std::stop_token token)
485  
    void launch_one(executor_ref caller_ex, std::stop_token token)
476  
    {
486  
    {
477  
        auto runner = make_when_any_runner<I>(
487  
        auto runner = make_when_any_runner<I>(
478  
            std::move(std::get<I>(*tasks_)), state_);
488  
            std::move(std::get<I>(*tasks_)), state_);
479  

489  

480  
        auto h = runner.release();
490  
        auto h = runner.release();
481  
        h.promise().state_ = state_;
491  
        h.promise().state_ = state_;
482  
        h.promise().index_ = I;
492  
        h.promise().index_ = I;
483  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
493  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
484  

494  

485  
        std::coroutine_handle<> ch{h};
495  
        std::coroutine_handle<> ch{h};
486  
        state_->runner_handles_[I] = ch;
496  
        state_->runner_handles_[I] = ch;
487  
        caller_ex.post(ch);
497  
        caller_ex.post(ch);
488  
    }
498  
    }
489  
};
499  
};
490  

500  

491  
} // namespace detail
501  
} // namespace detail
492  

502  

493  
/** Wait for the first awaitable to complete.
503  
/** Wait for the first awaitable to complete.
494  

504  

495  
    Races multiple heterogeneous awaitables concurrently and returns when the
505  
    Races multiple heterogeneous awaitables concurrently and returns when the
496  
    first one completes. The result is a variant with one alternative per
506  
    first one completes. The result is a variant with one alternative per
497  
    input task, preserving positional correspondence.
507  
    input task, preserving positional correspondence.
498  

508  

499  
    @par Suspends
509  
    @par Suspends
500  
    The calling coroutine suspends when co_await is invoked. All awaitables
510  
    The calling coroutine suspends when co_await is invoked. All awaitables
501  
    are launched concurrently and execute in parallel. The coroutine resumes
511  
    are launched concurrently and execute in parallel. The coroutine resumes
502  
    only after all awaitables have completed, even though the winner is
512  
    only after all awaitables have completed, even though the winner is
503  
    determined by the first to finish.
513  
    determined by the first to finish.
504  

514  

505  
    @par Completion Conditions
515  
    @par Completion Conditions
506  
    @li Winner is determined when the first awaitable completes (success or exception)
516  
    @li Winner is determined when the first awaitable completes (success or exception)
507  
    @li Only one task can claim winner status via atomic compare-exchange
517  
    @li Only one task can claim winner status via atomic compare-exchange
508  
    @li Once a winner exists, stop is requested for all remaining siblings
518  
    @li Once a winner exists, stop is requested for all remaining siblings
509  
    @li Parent coroutine resumes only after all siblings acknowledge completion
519  
    @li Parent coroutine resumes only after all siblings acknowledge completion
510  
    @li The winner's result is returned; if the winner threw, the exception is rethrown
520  
    @li The winner's result is returned; if the winner threw, the exception is rethrown
511  

521  

512  
    @par Cancellation Semantics
522  
    @par Cancellation Semantics
513  
    Cancellation is supported via stop_token propagated through the
523  
    Cancellation is supported via stop_token propagated through the
514  
    IoAwaitable protocol:
524  
    IoAwaitable protocol:
515  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
525  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
516  
    @li When the parent's stop token is activated, the stop is forwarded to all children
526  
    @li When the parent's stop token is activated, the stop is forwarded to all children
517  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
527  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
518  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
528  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
519  
    @li Stop requests are cooperative; tasks must check and respond to them
529  
    @li Stop requests are cooperative; tasks must check and respond to them
520  

530  

521  
    @par Concurrency/Overlap
531  
    @par Concurrency/Overlap
522  
    All awaitables are launched concurrently before any can complete.
532  
    All awaitables are launched concurrently before any can complete.
523  
    The launcher iterates through the arguments, starting each task on the
533  
    The launcher iterates through the arguments, starting each task on the
524  
    caller's executor. Tasks may execute in parallel on multi-threaded
534  
    caller's executor. Tasks may execute in parallel on multi-threaded
525  
    executors or interleave on single-threaded executors. There is no
535  
    executors or interleave on single-threaded executors. There is no
526  
    guaranteed ordering of task completion.
536  
    guaranteed ordering of task completion.
527  

537  

528  
    @par Notable Error Conditions
538  
    @par Notable Error Conditions
529  
    @li Winner exception: if the winning task threw, that exception is rethrown
539  
    @li Winner exception: if the winning task threw, that exception is rethrown
530  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
540  
    @li Non-winner exceptions: silently discarded (only winner's result matters)
531  
    @li Cancellation: tasks may complete via cancellation without throwing
541  
    @li Cancellation: tasks may complete via cancellation without throwing
532  

542  

533  
    @par Example
543  
    @par Example
534  
    @code
544  
    @code
535  
    task<void> example() {
545  
    task<void> example() {
536  
        auto result = co_await when_any(
546  
        auto result = co_await when_any(
537  
            fetch_int(),      // task<int>
547  
            fetch_int(),      // task<int>
538  
            fetch_string()    // task<std::string>
548  
            fetch_string()    // task<std::string>
539  
        );
549  
        );
540  
        // result.index() is 0 or 1
550  
        // result.index() is 0 or 1
541  
        if (result.index() == 0)
551  
        if (result.index() == 0)
542  
            std::cout << "Got int: " << std::get<0>(result) << "\n";
552  
            std::cout << "Got int: " << std::get<0>(result) << "\n";
543  
        else
553  
        else
544  
            std::cout << "Got string: " << std::get<1>(result) << "\n";
554  
            std::cout << "Got string: " << std::get<1>(result) << "\n";
545  
    }
555  
    }
546  
    @endcode
556  
    @endcode
547  

557  

548  
    @param as Awaitables to race concurrently (at least one required; each
558  
    @param as Awaitables to race concurrently (at least one required; each
549  
        must satisfy IoAwaitable).
559  
        must satisfy IoAwaitable).
550  
    @return A task yielding a std::variant with one alternative per awaitable.
560  
    @return A task yielding a std::variant with one alternative per awaitable.
551  
        Use .index() to identify the winner. Void awaitables contribute
561  
        Use .index() to identify the winner. Void awaitables contribute
552  
        std::monostate.
562  
        std::monostate.
553  

563  

554  
    @throws Rethrows the winner's exception if the winning task threw an exception.
564  
    @throws Rethrows the winner's exception if the winning task threw an exception.
555  

565  

556  
    @par Remarks
566  
    @par Remarks
557  
    Awaitables are moved into the coroutine frame; original objects become
567  
    Awaitables are moved into the coroutine frame; original objects become
558  
    empty after the call. The variant preserves one alternative per input
568  
    empty after the call. The variant preserves one alternative per input
559  
    task. Use .index() to determine which awaitable completed first.
569  
    task. Use .index() to determine which awaitable completed first.
560  
    Void awaitables contribute std::monostate to the variant.
570  
    Void awaitables contribute std::monostate to the variant.
561  

571  

562  
    @see when_all, IoAwaitable
572  
    @see when_all, IoAwaitable
563  
*/
573  
*/
564  
template<IoAwaitable... As>
574  
template<IoAwaitable... As>
565  
    requires (sizeof...(As) > 0)
575  
    requires (sizeof...(As) > 0)
566  
[[nodiscard]] auto when_any(As... as)
576  
[[nodiscard]] auto when_any(As... as)
567  
    -> task<std::variant<void_to_monostate_t<awaitable_result_t<As>>...>>
577  
    -> task<std::variant<void_to_monostate_t<awaitable_result_t<As>>...>>
568  
{
578  
{
569  
    detail::when_any_state<awaitable_result_t<As>...> state;
579  
    detail::when_any_state<awaitable_result_t<As>...> state;
570  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
580  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
571  

581  

572  
    co_await detail::when_any_launcher<As...>(&awaitable_tuple, &state);
582  
    co_await detail::when_any_launcher<As...>(&awaitable_tuple, &state);
573  

583  

574  
    if(state.core_.winner_exception_)
584  
    if(state.core_.winner_exception_)
575  
        std::rethrow_exception(state.core_.winner_exception_);
585  
        std::rethrow_exception(state.core_.winner_exception_);
576  

586  

577  
    co_return std::move(*state.result_);
587  
    co_return std::move(*state.result_);
578  
}
588  
}
 
589 +

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

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

 
596 +
    @tparam R The range type.
 
597 +

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

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

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

 
619 +
    @see when_any, IoAwaitable
 
620 +
*/
 
621 +
template<typename R>
 
622 +
concept IoAwaitableRange =
 
623 +
    std::ranges::input_range<R> &&
 
624 +
    std::ranges::sized_range<R> &&
 
625 +
    IoAwaitable<std::ranges::range_value_t<R>>;
579  

626  

580  
namespace detail {
627  
namespace detail {
581  

628  

582  
/** Shared state for homogeneous when_any (range overload).
629  
/** Shared state for homogeneous when_any (range overload).
583  

630  

584  
    Uses composition with when_any_core for shared functionality.
631  
    Uses composition with when_any_core for shared functionality.
585  
    Simpler than heterogeneous: optional<T> instead of variant, vector
632  
    Simpler than heterogeneous: optional<T> instead of variant, vector
586  
    instead of array for runner handles.
633  
    instead of array for runner handles.
587  
*/
634  
*/
588  
template<typename T>
635  
template<typename T>
589  
struct when_any_homogeneous_state
636  
struct when_any_homogeneous_state
590  
{
637  
{
591  
    when_any_core core_;
638  
    when_any_core core_;
592  
    std::optional<T> result_;
639  
    std::optional<T> result_;
593  
    std::vector<std::coroutine_handle<>> runner_handles_;
640  
    std::vector<std::coroutine_handle<>> runner_handles_;
594  

641  

595  
    explicit when_any_homogeneous_state(std::size_t count)
642  
    explicit when_any_homogeneous_state(std::size_t count)
596  
        : core_(count)
643  
        : core_(count)
597  
        , runner_handles_(count)
644  
        , runner_handles_(count)
598  
    {
645  
    {
599  
    }
646  
    }
600  

647  

601  
    // Runners self-destruct in final_suspend. No destruction needed here.
648  
    // Runners self-destruct in final_suspend. No destruction needed here.
602  

649  

603  
    /** @pre core_.try_win() returned true. */
650  
    /** @pre core_.try_win() returned true. */
604  
    void set_winner_result(T value)
651  
    void set_winner_result(T value)
605  
        noexcept(std::is_nothrow_move_constructible_v<T>)
652  
        noexcept(std::is_nothrow_move_constructible_v<T>)
606  
    {
653  
    {
607  
        result_.emplace(std::move(value));
654  
        result_.emplace(std::move(value));
608  
    }
655  
    }
609  
};
656  
};
610  

657  

611  
/** Specialization for void tasks (no result storage needed). */
658  
/** Specialization for void tasks (no result storage needed). */
612  
template<>
659  
template<>
613  
struct when_any_homogeneous_state<void>
660  
struct when_any_homogeneous_state<void>
614  
{
661  
{
615  
    when_any_core core_;
662  
    when_any_core core_;
616  
    std::vector<std::coroutine_handle<>> runner_handles_;
663  
    std::vector<std::coroutine_handle<>> runner_handles_;
617  

664  

618  
    explicit when_any_homogeneous_state(std::size_t count)
665  
    explicit when_any_homogeneous_state(std::size_t count)
619  
        : core_(count)
666  
        : core_(count)
620  
        , runner_handles_(count)
667  
        , runner_handles_(count)
621  
    {
668  
    {
622  
    }
669  
    }
623  

670  

624  
    // Runners self-destruct in final_suspend. No destruction needed here.
671  
    // Runners self-destruct in final_suspend. No destruction needed here.
625  

672  

626  
    // No set_winner_result - void tasks have no result to store
673  
    // No set_winner_result - void tasks have no result to store
627  
};
674  
};
628  

675  

629  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
676  
/** Launches all runners concurrently; see await_suspend for lifetime concerns. */
630  
template<IoAwaitableRange Range>
677  
template<IoAwaitableRange Range>
631  
class when_any_homogeneous_launcher
678  
class when_any_homogeneous_launcher
632  
{
679  
{
633  
    using Awaitable = std::ranges::range_value_t<Range>;
680  
    using Awaitable = std::ranges::range_value_t<Range>;
634  
    using T = awaitable_result_t<Awaitable>;
681  
    using T = awaitable_result_t<Awaitable>;
635  

682  

636  
    Range* range_;
683  
    Range* range_;
637  
    when_any_homogeneous_state<T>* state_;
684  
    when_any_homogeneous_state<T>* state_;
638  

685  

639  
public:
686  
public:
640  
    when_any_homogeneous_launcher(
687  
    when_any_homogeneous_launcher(
641  
        Range* range,
688  
        Range* range,
642  
        when_any_homogeneous_state<T>* state)
689  
        when_any_homogeneous_state<T>* state)
643  
        : range_(range)
690  
        : range_(range)
644  
        , state_(state)
691  
        , state_(state)
645  
    {
692  
    {
646  
    }
693  
    }
647  

694  

648  
    bool await_ready() const noexcept
695  
    bool await_ready() const noexcept
649  
    {
696  
    {
650  
        return std::ranges::empty(*range_);
697  
        return std::ranges::empty(*range_);
651  
    }
698  
    }
652  

699  

653  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
700  
    /** CRITICAL: If the last task finishes synchronously, parent resumes and
654  
        destroys this object before await_suspend returns. Must not reference
701  
        destroys this object before await_suspend returns. Must not reference
655  
        `this` after dispatching begins.
702  
        `this` after dispatching begins.
656  

703  

657  
        Two-phase approach:
704  
        Two-phase approach:
658  
        1. Create all runners (safe - no dispatch yet)
705  
        1. Create all runners (safe - no dispatch yet)
659  
        2. Dispatch all runners (any may complete synchronously)
706  
        2. Dispatch all runners (any may complete synchronously)
660  
    */
707  
    */
661  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
708  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
662  
    {
709  
    {
663  
        state_->core_.continuation_ = continuation;
710  
        state_->core_.continuation_ = continuation;
664  
        state_->core_.caller_env_ = caller_env;
711  
        state_->core_.caller_env_ = caller_env;
665  

712  

666  
        if(caller_env->stop_token.stop_possible())
713  
        if(caller_env->stop_token.stop_possible())
667  
        {
714  
        {
668  
            state_->core_.parent_stop_callback_.emplace(
715  
            state_->core_.parent_stop_callback_.emplace(
669  
                caller_env->stop_token,
716  
                caller_env->stop_token,
670  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
717  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
671  

718  

672  
            if(caller_env->stop_token.stop_requested())
719  
            if(caller_env->stop_token.stop_requested())
673  
                state_->core_.stop_source_.request_stop();
720  
                state_->core_.stop_source_.request_stop();
674  
        }
721  
        }
675  

722  

676  
        auto token = state_->core_.stop_source_.get_token();
723  
        auto token = state_->core_.stop_source_.get_token();
677  

724  

678  
        // Phase 1: Create all runners without dispatching.
725  
        // Phase 1: Create all runners without dispatching.
679  
        // This iterates over *range_ safely because no runners execute yet.
726  
        // This iterates over *range_ safely because no runners execute yet.
680  
        std::size_t index = 0;
727  
        std::size_t index = 0;
681  
        for(auto&& a : *range_)
728  
        for(auto&& a : *range_)
682  
        {
729  
        {
683  
            auto runner = make_when_any_runner(
730  
            auto runner = make_when_any_runner(
684  
                std::move(a), state_, index);
731  
                std::move(a), state_, index);
685  

732  

686  
            auto h = runner.release();
733  
            auto h = runner.release();
687  
            h.promise().state_ = state_;
734  
            h.promise().state_ = state_;
688  
            h.promise().index_ = index;
735  
            h.promise().index_ = index;
689  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
736  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
690  

737  

691  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
738  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
692  
            ++index;
739  
            ++index;
693  
        }
740  
        }
694  

741  

695  
        // Phase 2: Post all runners. Any may complete synchronously.
742  
        // Phase 2: Post all runners. Any may complete synchronously.
696  
        // After last post, state_ and this may be destroyed.
743  
        // After last post, state_ and this may be destroyed.
697  
        // Use raw pointer/count captured before posting.
744  
        // Use raw pointer/count captured before posting.
698  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
745  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
699  
        std::size_t count = state_->runner_handles_.size();
746  
        std::size_t count = state_->runner_handles_.size();
700  
        for(std::size_t i = 0; i < count; ++i)
747  
        for(std::size_t i = 0; i < count; ++i)
701  
            caller_env->executor.post(handles[i]);
748  
            caller_env->executor.post(handles[i]);
702  

749  

703  
        return std::noop_coroutine();
750  
        return std::noop_coroutine();
704  
    }
751  
    }
705  

752  

706  
    void await_resume() const noexcept
753  
    void await_resume() const noexcept
707  
    {
754  
    {
708  
    }
755  
    }
709  
};
756  
};
710  

757  

711  
} // namespace detail
758  
} // namespace detail
712  

759  

713  
/** Wait for the first awaitable to complete (range overload).
760  
/** Wait for the first awaitable to complete (range overload).
714  

761  

715  
    Races a range of awaitables with the same result type. Accepts any
762  
    Races a range of awaitables with the same result type. Accepts any
716  
    sized input range of IoAwaitable types, enabling use with arrays,
763  
    sized input range of IoAwaitable types, enabling use with arrays,
717  
    spans, or custom containers.
764  
    spans, or custom containers.
718  

765  

719  
    @par Suspends
766  
    @par Suspends
720  
    The calling coroutine suspends when co_await is invoked. All awaitables
767  
    The calling coroutine suspends when co_await is invoked. All awaitables
721  
    in the range are launched concurrently and execute in parallel. The
768  
    in the range are launched concurrently and execute in parallel. The
722  
    coroutine resumes only after all awaitables have completed, even though
769  
    coroutine resumes only after all awaitables have completed, even though
723  
    the winner is determined by the first to finish.
770  
    the winner is determined by the first to finish.
724  

771  

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

778  

732  
    @par Cancellation Semantics
779  
    @par Cancellation Semantics
733  
    Cancellation is supported via stop_token propagated through the
780  
    Cancellation is supported via stop_token propagated through the
734  
    IoAwaitable protocol:
781  
    IoAwaitable protocol:
735  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
782  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
736  
    @li When the parent's stop token is activated, the stop is forwarded to all children
783  
    @li When the parent's stop token is activated, the stop is forwarded to all children
737  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
784  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
738  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
785  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
739  
    @li Stop requests are cooperative; tasks must check and respond to them
786  
    @li Stop requests are cooperative; tasks must check and respond to them
740  

787  

741  
    @par Concurrency/Overlap
788  
    @par Concurrency/Overlap
742  
    All awaitables are launched concurrently before any can complete.
789  
    All awaitables are launched concurrently before any can complete.
743  
    The launcher iterates through the range, starting each task on the
790  
    The launcher iterates through the range, starting each task on the
744  
    caller's executor. Tasks may execute in parallel on multi-threaded
791  
    caller's executor. Tasks may execute in parallel on multi-threaded
745  
    executors or interleave on single-threaded executors. There is no
792  
    executors or interleave on single-threaded executors. There is no
746  
    guaranteed ordering of task completion.
793  
    guaranteed ordering of task completion.
747  

794  

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

800  

754  
    @par Example
801  
    @par Example
755  
    @code
802  
    @code
756  
    task<void> example() {
803  
    task<void> example() {
757  
        std::array<task<Response>, 3> requests = {
804  
        std::array<task<Response>, 3> requests = {
758  
            fetch_from_server(0),
805  
            fetch_from_server(0),
759  
            fetch_from_server(1),
806  
            fetch_from_server(1),
760  
            fetch_from_server(2)
807  
            fetch_from_server(2)
761  
        };
808  
        };
762  

809  

763  
        auto [index, response] = co_await when_any(std::move(requests));
810  
        auto [index, response] = co_await when_any(std::move(requests));
764  
    }
811  
    }
765  
    @endcode
812  
    @endcode
766  

813  

767  
    @par Example with Vector
814  
    @par Example with Vector
768  
    @code
815  
    @code
769  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
816  
    task<Response> fetch_fastest(std::vector<Server> const& servers) {
770  
        std::vector<task<Response>> requests;
817  
        std::vector<task<Response>> requests;
771  
        for (auto const& server : servers)
818  
        for (auto const& server : servers)
772  
            requests.push_back(fetch_from(server));
819  
            requests.push_back(fetch_from(server));
773  

820  

774  
        auto [index, response] = co_await when_any(std::move(requests));
821  
        auto [index, response] = co_await when_any(std::move(requests));
775  
        co_return response;
822  
        co_return response;
776  
    }
823  
    }
777  
    @endcode
824  
    @endcode
778  

825  

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

829  

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

832  

786  
    @par Remarks
833  
    @par Remarks
787  
    Elements are moved from the range; for lvalue ranges, the original
834  
    Elements are moved from the range; for lvalue ranges, the original
788  
    container will have moved-from elements after this call. The range
835  
    container will have moved-from elements after this call. The range
789  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
836  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
790  
    the variadic overload, no variant wrapper is needed since all tasks
837  
    the variadic overload, no variant wrapper is needed since all tasks
791  
    share the same return type.
838  
    share the same return type.
792  

839  

793  
    @see when_any, IoAwaitableRange
840  
    @see when_any, IoAwaitableRange
794  
*/
841  
*/
795  
template<IoAwaitableRange R>
842  
template<IoAwaitableRange R>
796  
    requires (!std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>)
843  
    requires (!std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>)
797  
[[nodiscard]] auto when_any(R&& awaitables)
844  
[[nodiscard]] auto when_any(R&& awaitables)
798  
    -> task<std::pair<std::size_t, awaitable_result_t<std::ranges::range_value_t<R>>>>
845  
    -> task<std::pair<std::size_t, awaitable_result_t<std::ranges::range_value_t<R>>>>
799  
{
846  
{
800  
    using Awaitable = std::ranges::range_value_t<R>;
847  
    using Awaitable = std::ranges::range_value_t<R>;
801  
    using T = awaitable_result_t<Awaitable>;
848  
    using T = awaitable_result_t<Awaitable>;
802  
    using result_type = std::pair<std::size_t, T>;
849  
    using result_type = std::pair<std::size_t, T>;
803  
    using OwnedRange = std::remove_cvref_t<R>;
850  
    using OwnedRange = std::remove_cvref_t<R>;
804  

851  

805  
    auto count = std::ranges::size(awaitables);
852  
    auto count = std::ranges::size(awaitables);
806  
    if(count == 0)
853  
    if(count == 0)
807  
        throw std::invalid_argument("when_any requires at least one awaitable");
854  
        throw std::invalid_argument("when_any requires at least one awaitable");
808  

855  

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

858  

812  
    detail::when_any_homogeneous_state<T> state(count);
859  
    detail::when_any_homogeneous_state<T> state(count);
813  

860  

814  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
861  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
815  

862  

816  
    if(state.core_.winner_exception_)
863  
    if(state.core_.winner_exception_)
817  
        std::rethrow_exception(state.core_.winner_exception_);
864  
        std::rethrow_exception(state.core_.winner_exception_);
818  

865  

819  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
866  
    co_return result_type{state.core_.winner_index_, std::move(*state.result_)};
820  
}
867  
}
821  

868  

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

870  

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

873  

827  
    @par Suspends
874  
    @par Suspends
828  
    The calling coroutine suspends when co_await is invoked. All awaitables
875  
    The calling coroutine suspends when co_await is invoked. All awaitables
829  
    in the range are launched concurrently and execute in parallel. The
876  
    in the range are launched concurrently and execute in parallel. The
830  
    coroutine resumes only after all awaitables have completed, even though
877  
    coroutine resumes only after all awaitables have completed, even though
831  
    the winner is determined by the first to finish.
878  
    the winner is determined by the first to finish.
832  

879  

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

886  

840  
    @par Cancellation Semantics
887  
    @par Cancellation Semantics
841  
    Cancellation is supported via stop_token propagated through the
888  
    Cancellation is supported via stop_token propagated through the
842  
    IoAwaitable protocol:
889  
    IoAwaitable protocol:
843  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
890  
    @li Each child awaitable receives a stop_token derived from a shared stop_source
844  
    @li When the parent's stop token is activated, the stop is forwarded to all children
891  
    @li When the parent's stop token is activated, the stop is forwarded to all children
845  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
892  
    @li When a winner is determined, stop_source_.request_stop() is called immediately
846  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
893  
    @li Siblings must handle cancellation gracefully and complete before parent resumes
847  
    @li Stop requests are cooperative; tasks must check and respond to them
894  
    @li Stop requests are cooperative; tasks must check and respond to them
848  

895  

849  
    @par Concurrency/Overlap
896  
    @par Concurrency/Overlap
850  
    All awaitables are launched concurrently before any can complete.
897  
    All awaitables are launched concurrently before any can complete.
851  
    The launcher iterates through the range, starting each task on the
898  
    The launcher iterates through the range, starting each task on the
852  
    caller's executor. Tasks may execute in parallel on multi-threaded
899  
    caller's executor. Tasks may execute in parallel on multi-threaded
853  
    executors or interleave on single-threaded executors. There is no
900  
    executors or interleave on single-threaded executors. There is no
854  
    guaranteed ordering of task completion.
901  
    guaranteed ordering of task completion.
855  

902  

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

908  

862  
    @par Example
909  
    @par Example
863  
    @code
910  
    @code
864  
    task<void> example() {
911  
    task<void> example() {
865  
        std::vector<task<void>> tasks;
912  
        std::vector<task<void>> tasks;
866  
        for (int i = 0; i < 5; ++i)
913  
        for (int i = 0; i < 5; ++i)
867  
            tasks.push_back(background_work(i));
914  
            tasks.push_back(background_work(i));
868  

915  

869  
        std::size_t winner = co_await when_any(std::move(tasks));
916  
        std::size_t winner = co_await when_any(std::move(tasks));
870  
        // winner is the index of the first task to complete
917  
        // winner is the index of the first task to complete
871  
    }
918  
    }
872  
    @endcode
919  
    @endcode
873  

920  

874  
    @par Example with Timeout
921  
    @par Example with Timeout
875  
    @code
922  
    @code
876  
    task<void> with_timeout() {
923  
    task<void> with_timeout() {
877  
        std::vector<task<void>> tasks;
924  
        std::vector<task<void>> tasks;
878  
        tasks.push_back(long_running_operation());
925  
        tasks.push_back(long_running_operation());
879  
        tasks.push_back(delay(std::chrono::seconds(5)));
926  
        tasks.push_back(delay(std::chrono::seconds(5)));
880  

927  

881  
        std::size_t winner = co_await when_any(std::move(tasks));
928  
        std::size_t winner = co_await when_any(std::move(tasks));
882  
        if (winner == 1) {
929  
        if (winner == 1) {
883  
            // Timeout occurred
930  
            // Timeout occurred
884  
        }
931  
        }
885  
    }
932  
    }
886  
    @endcode
933  
    @endcode
887  

934  

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

938  

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

941  

895  
    @par Remarks
942  
    @par Remarks
896  
    Elements are moved from the range; for lvalue ranges, the original
943  
    Elements are moved from the range; for lvalue ranges, the original
897  
    container will have moved-from elements after this call. The range
944  
    container will have moved-from elements after this call. The range
898  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
945  
    is moved onto the coroutine frame to ensure lifetime safety. Unlike
899  
    the non-void overload, no result storage is needed since void tasks
946  
    the non-void overload, no result storage is needed since void tasks
900  
    produce no value.
947  
    produce no value.
901  

948  

902  
    @see when_any, IoAwaitableRange
949  
    @see when_any, IoAwaitableRange
903  
*/
950  
*/
904  
template<IoAwaitableRange R>
951  
template<IoAwaitableRange R>
905  
    requires std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>
952  
    requires std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>
906  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
953  
[[nodiscard]] auto when_any(R&& awaitables) -> task<std::size_t>
907  
{
954  
{
908  
    using OwnedRange = std::remove_cvref_t<R>;
955  
    using OwnedRange = std::remove_cvref_t<R>;
909  

956  

910  
    auto count = std::ranges::size(awaitables);
957  
    auto count = std::ranges::size(awaitables);
911  
    if(count == 0)
958  
    if(count == 0)
912  
        throw std::invalid_argument("when_any requires at least one awaitable");
959  
        throw std::invalid_argument("when_any requires at least one awaitable");
913  

960  

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

963  

917  
    detail::when_any_homogeneous_state<void> state(count);
964  
    detail::when_any_homogeneous_state<void> state(count);
918  

965  

919  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
966  
    co_await detail::when_any_homogeneous_launcher<OwnedRange>(&owned_awaitables, &state);
920  

967  

921  
    if(state.core_.winner_exception_)
968  
    if(state.core_.winner_exception_)
922  
        std::rethrow_exception(state.core_.winner_exception_);
969  
        std::rethrow_exception(state.core_.winner_exception_);
923  

970  

924  
    co_return state.core_.winner_index_;
971  
    co_return state.core_.winner_index_;
925  
}
972  
}
926  

973  

927  
} // namespace capy
974  
} // namespace capy
928  
} // namespace boost
975  
} // namespace boost
929  

976  

930  
#endif
977  
#endif