1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
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_ALL_HPP
10  
#ifndef BOOST_CAPY_WHEN_ALL_HPP
11  
#define BOOST_CAPY_WHEN_ALL_HPP
11  
#define BOOST_CAPY_WHEN_ALL_HPP
12  

12  

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

23  

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

36  

36  
namespace boost {
37  
namespace boost {
37  
namespace capy {
38  
namespace capy {
38  

39  

39  
namespace detail {
40  
namespace detail {
40  

41  

41  
/** Holds the result of a single task within when_all.
42  
/** Holds the result of a single task within when_all.
42  
*/
43  
*/
43  
template<typename T>
44  
template<typename T>
44  
struct result_holder
45  
struct result_holder
45  
{
46  
{
46  
    std::optional<T> value_;
47  
    std::optional<T> value_;
47  

48  

48  
    void set(T v)
49  
    void set(T v)
49  
    {
50  
    {
50  
        value_ = std::move(v);
51  
        value_ = std::move(v);
51  
    }
52  
    }
52  

53  

53  
    T get() &&
54  
    T get() &&
54  
    {
55  
    {
55  
        return std::move(*value_);
56  
        return std::move(*value_);
56  
    }
57  
    }
57  
};
58  
};
58  

59  

59  
/** Core shared state for when_all operations.
60  
/** Core shared state for when_all operations.
60  

61  

61  
    Contains all members and methods common to both heterogeneous (variadic)
62  
    Contains all members and methods common to both heterogeneous (variadic)
62  
    and homogeneous (range) when_all implementations. State classes embed
63  
    and homogeneous (range) when_all implementations. State classes embed
63  
    this via composition to avoid CRTP destructor ordering issues.
64  
    this via composition to avoid CRTP destructor ordering issues.
64  

65  

65  
    @par Thread Safety
66  
    @par Thread Safety
66  
    Atomic operations protect exception capture and completion count.
67  
    Atomic operations protect exception capture and completion count.
67  
*/
68  
*/
68  
struct when_all_core
69  
struct when_all_core
69  
{
70  
{
70  
    std::atomic<std::size_t> remaining_count_;
71  
    std::atomic<std::size_t> remaining_count_;
71  

72  

72  
    // Exception storage - first error wins, others discarded
73  
    // Exception storage - first error wins, others discarded
73  
    std::atomic<bool> has_exception_{false};
74  
    std::atomic<bool> has_exception_{false};
74  
    std::exception_ptr first_exception_;
75  
    std::exception_ptr first_exception_;
75  

76  

76  
    std::stop_source stop_source_;
77  
    std::stop_source stop_source_;
77  

78  

78  
    // Bridges parent's stop token to our stop_source
79  
    // Bridges parent's stop token to our stop_source
79  
    struct stop_callback_fn
80  
    struct stop_callback_fn
80  
    {
81  
    {
81  
        std::stop_source* source_;
82  
        std::stop_source* source_;
82  
        void operator()() const { source_->request_stop(); }
83  
        void operator()() const { source_->request_stop(); }
83  
    };
84  
    };
84  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
85  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
85  
    std::optional<stop_callback_t> parent_stop_callback_;
86  
    std::optional<stop_callback_t> parent_stop_callback_;
86  

87  

87  
    continuation continuation_;
88  
    continuation continuation_;
88  
    io_env const* caller_env_ = nullptr;
89  
    io_env const* caller_env_ = nullptr;
89  

90  

90  
    explicit when_all_core(std::size_t count) noexcept
91  
    explicit when_all_core(std::size_t count) noexcept
91  
        : remaining_count_(count)
92  
        : remaining_count_(count)
92  
    {
93  
    {
93  
    }
94  
    }
94  

95  

95  
    /** Capture an exception (first one wins). */
96  
    /** Capture an exception (first one wins). */
96  
    void capture_exception(std::exception_ptr ep)
97  
    void capture_exception(std::exception_ptr ep)
97  
    {
98  
    {
98  
        bool expected = false;
99  
        bool expected = false;
99  
        if(has_exception_.compare_exchange_strong(
100  
        if(has_exception_.compare_exchange_strong(
100  
            expected, true, std::memory_order_relaxed))
101  
            expected, true, std::memory_order_relaxed))
101  
            first_exception_ = ep;
102  
            first_exception_ = ep;
102  
    }
103  
    }
103  
};
104  
};
104  

105  

105  
/** Shared state for heterogeneous when_all (variadic overload).
106  
/** Shared state for heterogeneous when_all (variadic overload).
106  

107  

107  
    @tparam Ts The result types of the tasks.
108  
    @tparam Ts The result types of the tasks.
108  
*/
109  
*/
109  
template<typename... Ts>
110  
template<typename... Ts>
110  
struct when_all_state
111  
struct when_all_state
111  
{
112  
{
112  
    static constexpr std::size_t task_count = sizeof...(Ts);
113  
    static constexpr std::size_t task_count = sizeof...(Ts);
113  

114  

114  
    when_all_core core_;
115  
    when_all_core core_;
115  
    std::tuple<result_holder<Ts>...> results_;
116  
    std::tuple<result_holder<Ts>...> results_;
116  
    std::array<continuation, task_count> runner_handles_{};
117  
    std::array<continuation, task_count> runner_handles_{};
117  

118  

118  
    std::atomic<bool> has_error_{false};
119  
    std::atomic<bool> has_error_{false};
119  
    std::error_code first_error_;
120  
    std::error_code first_error_;
120  

121  

121  
    when_all_state()
122  
    when_all_state()
122  
        : core_(task_count)
123  
        : core_(task_count)
123  
    {
124  
    {
124  
    }
125  
    }
125  

126  

126  
    /** Record the first error (subsequent errors are discarded). */
127  
    /** Record the first error (subsequent errors are discarded). */
127  
    void record_error(std::error_code ec)
128  
    void record_error(std::error_code ec)
128  
    {
129  
    {
129  
        bool expected = false;
130  
        bool expected = false;
130  
        if(has_error_.compare_exchange_strong(
131  
        if(has_error_.compare_exchange_strong(
131  
            expected, true, std::memory_order_relaxed))
132  
            expected, true, std::memory_order_relaxed))
132  
            first_error_ = ec;
133  
            first_error_ = ec;
133  
    }
134  
    }
134  
};
135  
};
135  

136  

136  
/** Shared state for homogeneous when_all (range overload).
137  
/** Shared state for homogeneous when_all (range overload).
137  

138  

138  
    Stores extracted io_result payloads in a vector indexed by task
139  
    Stores extracted io_result payloads in a vector indexed by task
139  
    position. Tracks the first error_code for error propagation.
140  
    position. Tracks the first error_code for error propagation.
140  

141  

141  
    @tparam T The payload type extracted from io_result.
142  
    @tparam T The payload type extracted from io_result.
142  
*/
143  
*/
143  
template<typename T>
144  
template<typename T>
144  
struct when_all_homogeneous_state
145  
struct when_all_homogeneous_state
145  
{
146  
{
146  
    when_all_core core_;
147  
    when_all_core core_;
147  
    std::vector<std::optional<T>> results_;
148  
    std::vector<std::optional<T>> results_;
148  
    std::unique_ptr<continuation[]> runner_handles_;
149  
    std::unique_ptr<continuation[]> runner_handles_;
149  

150  

150  
    std::atomic<bool> has_error_{false};
151  
    std::atomic<bool> has_error_{false};
151  
    std::error_code first_error_;
152  
    std::error_code first_error_;
152  

153  

153  
    explicit when_all_homogeneous_state(std::size_t count)
154  
    explicit when_all_homogeneous_state(std::size_t count)
154  
        : core_(count)
155  
        : core_(count)
155  
        , results_(count)
156  
        , results_(count)
156  
        , runner_handles_(std::make_unique<continuation[]>(count))
157  
        , runner_handles_(std::make_unique<continuation[]>(count))
157  
    {
158  
    {
158  
    }
159  
    }
159  

160  

160  
    void set_result(std::size_t index, T value)
161  
    void set_result(std::size_t index, T value)
161  
    {
162  
    {
162  
        results_[index].emplace(std::move(value));
163  
        results_[index].emplace(std::move(value));
163  
    }
164  
    }
164  

165  

165  
    /** Record the first error (subsequent errors are discarded). */
166  
    /** Record the first error (subsequent errors are discarded). */
166  
    void record_error(std::error_code ec)
167  
    void record_error(std::error_code ec)
167  
    {
168  
    {
168  
        bool expected = false;
169  
        bool expected = false;
169  
        if(has_error_.compare_exchange_strong(
170  
        if(has_error_.compare_exchange_strong(
170  
            expected, true, std::memory_order_relaxed))
171  
            expected, true, std::memory_order_relaxed))
171  
            first_error_ = ec;
172  
            first_error_ = ec;
172  
    }
173  
    }
173  
};
174  
};
174  

175  

175  
/** Specialization for void io_result children (no payload storage). */
176  
/** Specialization for void io_result children (no payload storage). */
176  
template<>
177  
template<>
177  
struct when_all_homogeneous_state<std::tuple<>>
178  
struct when_all_homogeneous_state<std::tuple<>>
178  
{
179  
{
179  
    when_all_core core_;
180  
    when_all_core core_;
180  
    std::unique_ptr<continuation[]> runner_handles_;
181  
    std::unique_ptr<continuation[]> runner_handles_;
181  

182  

182  
    std::atomic<bool> has_error_{false};
183  
    std::atomic<bool> has_error_{false};
183  
    std::error_code first_error_;
184  
    std::error_code first_error_;
184  

185  

185  
    explicit when_all_homogeneous_state(std::size_t count)
186  
    explicit when_all_homogeneous_state(std::size_t count)
186  
        : core_(count)
187  
        : core_(count)
187  
        , runner_handles_(std::make_unique<continuation[]>(count))
188  
        , runner_handles_(std::make_unique<continuation[]>(count))
188  
    {
189  
    {
189  
    }
190  
    }
190  

191  

191  
    /** Record the first error (subsequent errors are discarded). */
192  
    /** Record the first error (subsequent errors are discarded). */
192  
    void record_error(std::error_code ec)
193  
    void record_error(std::error_code ec)
193  
    {
194  
    {
194  
        bool expected = false;
195  
        bool expected = false;
195  
        if(has_error_.compare_exchange_strong(
196  
        if(has_error_.compare_exchange_strong(
196  
            expected, true, std::memory_order_relaxed))
197  
            expected, true, std::memory_order_relaxed))
197  
            first_error_ = ec;
198  
            first_error_ = ec;
198  
    }
199  
    }
199  
};
200  
};
200  

201  

201  
/** Wrapper coroutine that intercepts task completion for when_all.
202  
/** Wrapper coroutine that intercepts task completion for when_all.
202  

203  

203  
    Parameterized on StateType to work with both heterogeneous (variadic)
204  
    Parameterized on StateType to work with both heterogeneous (variadic)
204  
    and homogeneous (range) state types. All state types expose their
205  
    and homogeneous (range) state types. All state types expose their
205  
    shared members through a `core_` member of type when_all_core.
206  
    shared members through a `core_` member of type when_all_core.
206  

207  

207  
    @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
208  
    @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
208  
*/
209  
*/
209  
template<typename StateType>
210  
template<typename StateType>
210 -
struct when_all_runner
211 +
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_all_runner
211  
{
212  
{
212  
    struct promise_type
213  
    struct promise_type
 
214 +
        : frame_alloc_mixin
213  
    {
215  
    {
214  
        StateType* state_ = nullptr;
216  
        StateType* state_ = nullptr;
215  
        std::size_t index_ = 0;
217  
        std::size_t index_ = 0;
216  
        io_env env_;
218  
        io_env env_;
217  

219  

218  
        when_all_runner get_return_object() noexcept
220  
        when_all_runner get_return_object() noexcept
219  
        {
221  
        {
220  
            return when_all_runner(
222  
            return when_all_runner(
221  
                std::coroutine_handle<promise_type>::from_promise(*this));
223  
                std::coroutine_handle<promise_type>::from_promise(*this));
222  
        }
224  
        }
223  

225  

224  
        std::suspend_always initial_suspend() noexcept
226  
        std::suspend_always initial_suspend() noexcept
225  
        {
227  
        {
226  
            return {};
228  
            return {};
227  
        }
229  
        }
228  

230  

229  
        auto final_suspend() noexcept
231  
        auto final_suspend() noexcept
230  
        {
232  
        {
231  
            struct awaiter
233  
            struct awaiter
232  
            {
234  
            {
233  
                promise_type* p_;
235  
                promise_type* p_;
234  
                bool await_ready() const noexcept { return false; }
236  
                bool await_ready() const noexcept { return false; }
235  
                auto await_suspend(std::coroutine_handle<> h) noexcept
237  
                auto await_suspend(std::coroutine_handle<> h) noexcept
236  
                {
238  
                {
237  
                    auto& core = p_->state_->core_;
239  
                    auto& core = p_->state_->core_;
238  
                    auto* counter = &core.remaining_count_;
240  
                    auto* counter = &core.remaining_count_;
239  
                    auto* caller_env = core.caller_env_;
241  
                    auto* caller_env = core.caller_env_;
240  
                    auto& cont = core.continuation_;
242  
                    auto& cont = core.continuation_;
241  

243  

242  
                    h.destroy();
244  
                    h.destroy();
243  

245  

244  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
246  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
245  
                    if(remaining == 1)
247  
                    if(remaining == 1)
246  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
248  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
247  
                    return detail::symmetric_transfer(std::noop_coroutine());
249  
                    return detail::symmetric_transfer(std::noop_coroutine());
248  
                }
250  
                }
249  
                void await_resume() const noexcept {}
251  
                void await_resume() const noexcept {}
250  
            };
252  
            };
251  
            return awaiter{this};
253  
            return awaiter{this};
252  
        }
254  
        }
253  

255  

254  
        void return_void() noexcept {}
256  
        void return_void() noexcept {}
255  

257  

256 -
        void unhandled_exception()
258 +
        void unhandled_exception() noexcept
257  
        {
259  
        {
258  
            state_->core_.capture_exception(std::current_exception());
260  
            state_->core_.capture_exception(std::current_exception());
259  
            state_->core_.stop_source_.request_stop();
261  
            state_->core_.stop_source_.request_stop();
260  
        }
262  
        }
261  

263  

262  
        template<class Awaitable>
264  
        template<class Awaitable>
263  
        struct transform_awaiter
265  
        struct transform_awaiter
264  
        {
266  
        {
265  
            std::decay_t<Awaitable> a_;
267  
            std::decay_t<Awaitable> a_;
266  
            promise_type* p_;
268  
            promise_type* p_;
267  

269  

268  
            bool await_ready() { return a_.await_ready(); }
270  
            bool await_ready() { return a_.await_ready(); }
269  
            decltype(auto) await_resume() { return a_.await_resume(); }
271  
            decltype(auto) await_resume() { return a_.await_resume(); }
270  

272  

271  
            template<class Promise>
273  
            template<class Promise>
272  
            auto await_suspend(std::coroutine_handle<Promise> h)
274  
            auto await_suspend(std::coroutine_handle<Promise> h)
273  
            {
275  
            {
274  
                using R = decltype(a_.await_suspend(h, &p_->env_));
276  
                using R = decltype(a_.await_suspend(h, &p_->env_));
275  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
277  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
276  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
278  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
277  
                else
279  
                else
278  
                    return a_.await_suspend(h, &p_->env_);
280  
                    return a_.await_suspend(h, &p_->env_);
279  
            }
281  
            }
280  
        };
282  
        };
281  

283  

282  
        template<class Awaitable>
284  
        template<class Awaitable>
283  
        auto await_transform(Awaitable&& a)
285  
        auto await_transform(Awaitable&& a)
284  
        {
286  
        {
285  
            using A = std::decay_t<Awaitable>;
287  
            using A = std::decay_t<Awaitable>;
286  
            if constexpr (IoAwaitable<A>)
288  
            if constexpr (IoAwaitable<A>)
287  
            {
289  
            {
288  
                return transform_awaiter<Awaitable>{
290  
                return transform_awaiter<Awaitable>{
289  
                    std::forward<Awaitable>(a), this};
291  
                    std::forward<Awaitable>(a), this};
290  
            }
292  
            }
291  
            else
293  
            else
292  
            {
294  
            {
293  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
295  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
294  
            }
296  
            }
295  
        }
297  
        }
296  
    };
298  
    };
297  

299  

298  
    std::coroutine_handle<promise_type> h_;
300  
    std::coroutine_handle<promise_type> h_;
299  

301  

300  
    explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
302  
    explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
301  
        : h_(h)
303  
        : h_(h)
302  
    {
304  
    {
303  
    }
305  
    }
304  

306  

305  
    // Enable move for all clang versions - some versions need it
307  
    // Enable move for all clang versions - some versions need it
306  
    when_all_runner(when_all_runner&& other) noexcept
308  
    when_all_runner(when_all_runner&& other) noexcept
307  
        : h_(std::exchange(other.h_, nullptr))
309  
        : h_(std::exchange(other.h_, nullptr))
308  
    {
310  
    {
309  
    }
311  
    }
310  

312  

311  
    when_all_runner(when_all_runner const&) = delete;
313  
    when_all_runner(when_all_runner const&) = delete;
312  
    when_all_runner& operator=(when_all_runner const&) = delete;
314  
    when_all_runner& operator=(when_all_runner const&) = delete;
313  
    when_all_runner& operator=(when_all_runner&&) = delete;
315  
    when_all_runner& operator=(when_all_runner&&) = delete;
314  

316  

315  
    auto release() noexcept
317  
    auto release() noexcept
316  
    {
318  
    {
317  
        return std::exchange(h_, nullptr);
319  
        return std::exchange(h_, nullptr);
318  
    }
320  
    }
319  
};
321  
};
320  

322  

321  
/** Create an io_result-aware runner for a single awaitable (range path).
323  
/** Create an io_result-aware runner for a single awaitable (range path).
322  

324  

323  
    Checks the error code, records errors and requests stop on failure,
325  
    Checks the error code, records errors and requests stop on failure,
324  
    or extracts the payload on success.
326  
    or extracts the payload on success.
325  
*/
327  
*/
326  
template<IoAwaitable Awaitable, typename StateType>
328  
template<IoAwaitable Awaitable, typename StateType>
327  
when_all_runner<StateType>
329  
when_all_runner<StateType>
328  
make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index)
330  
make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index)
329  
{
331  
{
330  
    auto result = co_await std::move(inner);
332  
    auto result = co_await std::move(inner);
331  

333  

332  
    if(result.ec)
334  
    if(result.ec)
333  
    {
335  
    {
334  
        state->record_error(result.ec);
336  
        state->record_error(result.ec);
335  
        state->core_.stop_source_.request_stop();
337  
        state->core_.stop_source_.request_stop();
336  
    }
338  
    }
337  
    else
339  
    else
338  
    {
340  
    {
339  
        using PayloadT = io_result_payload_t<
341  
        using PayloadT = io_result_payload_t<
340  
            awaitable_result_t<Awaitable>>;
342  
            awaitable_result_t<Awaitable>>;
341  
        if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
343  
        if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
342  
        {
344  
        {
343  
            state->set_result(index,
345  
            state->set_result(index,
344  
                extract_io_payload(std::move(result)));
346  
                extract_io_payload(std::move(result)));
345  
        }
347  
        }
346  
    }
348  
    }
347  
}
349  
}
348  

350  

349  
/** Create a runner for io_result children that requests stop on ec. */
351  
/** Create a runner for io_result children that requests stop on ec. */
350  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
352  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
351  
when_all_runner<when_all_state<Ts...>>
353  
when_all_runner<when_all_state<Ts...>>
352  
make_when_all_io_runner(Awaitable inner, when_all_state<Ts...>* state)
354  
make_when_all_io_runner(Awaitable inner, when_all_state<Ts...>* state)
353  
{
355  
{
354  
    auto result = co_await std::move(inner);
356  
    auto result = co_await std::move(inner);
355  
    auto ec = result.ec;
357  
    auto ec = result.ec;
356  
    std::get<Index>(state->results_).set(std::move(result));
358  
    std::get<Index>(state->results_).set(std::move(result));
357  

359  

358  
    if(ec)
360  
    if(ec)
359  
    {
361  
    {
360  
        state->record_error(ec);
362  
        state->record_error(ec);
361  
        state->core_.stop_source_.request_stop();
363  
        state->core_.stop_source_.request_stop();
362  
    }
364  
    }
363  
}
365  
}
364  

366  

365  
/** Launcher that uses io_result-aware runners. */
367  
/** Launcher that uses io_result-aware runners. */
366  
template<IoAwaitable... Awaitables>
368  
template<IoAwaitable... Awaitables>
367  
class when_all_io_launcher
369  
class when_all_io_launcher
368  
{
370  
{
369  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
371  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
370  

372  

371  
    std::tuple<Awaitables...>* awaitables_;
373  
    std::tuple<Awaitables...>* awaitables_;
372  
    state_type* state_;
374  
    state_type* state_;
373  

375  

374  
public:
376  
public:
375  
    when_all_io_launcher(
377  
    when_all_io_launcher(
376  
        std::tuple<Awaitables...>* awaitables,
378  
        std::tuple<Awaitables...>* awaitables,
377  
        state_type* state)
379  
        state_type* state)
378  
        : awaitables_(awaitables)
380  
        : awaitables_(awaitables)
379  
        , state_(state)
381  
        , state_(state)
380  
    {
382  
    {
381  
    }
383  
    }
382  

384  

383  
    bool await_ready() const noexcept
385  
    bool await_ready() const noexcept
384  
    {
386  
    {
385  
        return sizeof...(Awaitables) == 0;
387  
        return sizeof...(Awaitables) == 0;
386  
    }
388  
    }
387  

389  

388  
    std::coroutine_handle<> await_suspend(
390  
    std::coroutine_handle<> await_suspend(
389  
        std::coroutine_handle<> continuation, io_env const* caller_env)
391  
        std::coroutine_handle<> continuation, io_env const* caller_env)
390  
    {
392  
    {
391  
        state_->core_.continuation_.h = continuation;
393  
        state_->core_.continuation_.h = continuation;
392  
        state_->core_.caller_env_ = caller_env;
394  
        state_->core_.caller_env_ = caller_env;
393  

395  

394  
        if(caller_env->stop_token.stop_possible())
396  
        if(caller_env->stop_token.stop_possible())
395  
        {
397  
        {
396  
            state_->core_.parent_stop_callback_.emplace(
398  
            state_->core_.parent_stop_callback_.emplace(
397  
                caller_env->stop_token,
399  
                caller_env->stop_token,
398  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
400  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
399  

401  

400  
            if(caller_env->stop_token.stop_requested())
402  
            if(caller_env->stop_token.stop_requested())
401  
                state_->core_.stop_source_.request_stop();
403  
                state_->core_.stop_source_.request_stop();
402  
        }
404  
        }
403  

405  

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

410  

409  
        return std::noop_coroutine();
411  
        return std::noop_coroutine();
410  
    }
412  
    }
411  

413  

412  
    void await_resume() const noexcept {}
414  
    void await_resume() const noexcept {}
413  

415  

414  
private:
416  
private:
415  
    template<std::size_t I>
417  
    template<std::size_t I>
416  
    void launch_one(executor_ref caller_ex, std::stop_token token)
418  
    void launch_one(executor_ref caller_ex, std::stop_token token)
417  
    {
419  
    {
418  
        auto runner = make_when_all_io_runner<I>(
420  
        auto runner = make_when_all_io_runner<I>(
419  
            std::move(std::get<I>(*awaitables_)), state_);
421  
            std::move(std::get<I>(*awaitables_)), state_);
420  

422  

421  
        auto h = runner.release();
423  
        auto h = runner.release();
422  
        h.promise().state_ = state_;
424  
        h.promise().state_ = state_;
423  
        h.promise().env_ = io_env{caller_ex, token,
425  
        h.promise().env_ = io_env{caller_ex, token,
424  
            state_->core_.caller_env_->frame_allocator};
426  
            state_->core_.caller_env_->frame_allocator};
425  

427  

426  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
428  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
427  
        state_->core_.caller_env_->executor.post(state_->runner_handles_[I]);
429  
        state_->core_.caller_env_->executor.post(state_->runner_handles_[I]);
428  
    }
430  
    }
429  
};
431  
};
430  

432  

431  
/** Helper to extract a single result from state.
433  
/** Helper to extract a single result from state.
432  
    This is a separate function to work around a GCC-11 ICE that occurs
434  
    This is a separate function to work around a GCC-11 ICE that occurs
433  
    when using nested immediately-invoked lambdas with pack expansion.
435  
    when using nested immediately-invoked lambdas with pack expansion.
434  
*/
436  
*/
435  
template<std::size_t I, typename... Ts>
437  
template<std::size_t I, typename... Ts>
436  
auto extract_single_result(when_all_state<Ts...>& state)
438  
auto extract_single_result(when_all_state<Ts...>& state)
437  
{
439  
{
438  
    return std::move(std::get<I>(state.results_)).get();
440  
    return std::move(std::get<I>(state.results_)).get();
439  
}
441  
}
440  

442  

441  
/** Extract all results from state as a tuple.
443  
/** Extract all results from state as a tuple.
442  
*/
444  
*/
443  
template<typename... Ts>
445  
template<typename... Ts>
444  
auto extract_results(when_all_state<Ts...>& state)
446  
auto extract_results(when_all_state<Ts...>& state)
445  
{
447  
{
446  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
448  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
447  
        return std::tuple(extract_single_result<Is>(state)...);
449  
        return std::tuple(extract_single_result<Is>(state)...);
448  
    }(std::index_sequence_for<Ts...>{});
450  
    }(std::index_sequence_for<Ts...>{});
449  
}
451  
}
450  

452  

451  
/** Launches all homogeneous runners concurrently.
453  
/** Launches all homogeneous runners concurrently.
452  

454  

453  
    Two-phase approach: create all runners first, then post all.
455  
    Two-phase approach: create all runners first, then post all.
454  
    This avoids lifetime issues if a task completes synchronously.
456  
    This avoids lifetime issues if a task completes synchronously.
455  
*/
457  
*/
456  
template<typename Range>
458  
template<typename Range>
457  
class when_all_homogeneous_launcher
459  
class when_all_homogeneous_launcher
458  
{
460  
{
459  
    using Awaitable = std::ranges::range_value_t<Range>;
461  
    using Awaitable = std::ranges::range_value_t<Range>;
460  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
462  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
461  

463  

462  
    Range* range_;
464  
    Range* range_;
463  
    when_all_homogeneous_state<PayloadT>* state_;
465  
    when_all_homogeneous_state<PayloadT>* state_;
464  

466  

465  
public:
467  
public:
466  
    when_all_homogeneous_launcher(
468  
    when_all_homogeneous_launcher(
467  
        Range* range,
469  
        Range* range,
468  
        when_all_homogeneous_state<PayloadT>* state)
470  
        when_all_homogeneous_state<PayloadT>* state)
469  
        : range_(range)
471  
        : range_(range)
470  
        , state_(state)
472  
        , state_(state)
471  
    {
473  
    {
472  
    }
474  
    }
473  

475  

474  
    bool await_ready() const noexcept
476  
    bool await_ready() const noexcept
475  
    {
477  
    {
476  
        return std::ranges::empty(*range_);
478  
        return std::ranges::empty(*range_);
477  
    }
479  
    }
478  

480  

479  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
481  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
480  
    {
482  
    {
481  
        state_->core_.continuation_.h = continuation;
483  
        state_->core_.continuation_.h = continuation;
482  
        state_->core_.caller_env_ = caller_env;
484  
        state_->core_.caller_env_ = caller_env;
483  

485  

484  
        if(caller_env->stop_token.stop_possible())
486  
        if(caller_env->stop_token.stop_possible())
485  
        {
487  
        {
486  
            state_->core_.parent_stop_callback_.emplace(
488  
            state_->core_.parent_stop_callback_.emplace(
487  
                caller_env->stop_token,
489  
                caller_env->stop_token,
488  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
490  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
489  

491  

490  
            if(caller_env->stop_token.stop_requested())
492  
            if(caller_env->stop_token.stop_requested())
491  
                state_->core_.stop_source_.request_stop();
493  
                state_->core_.stop_source_.request_stop();
492  
        }
494  
        }
493  

495  

494  
        auto token = state_->core_.stop_source_.get_token();
496  
        auto token = state_->core_.stop_source_.get_token();
495  

497  

496  
        // Phase 1: Create all runners without dispatching.
498  
        // Phase 1: Create all runners without dispatching.
497  
        std::size_t index = 0;
499  
        std::size_t index = 0;
498  
        for(auto&& a : *range_)
500  
        for(auto&& a : *range_)
499  
        {
501  
        {
500  
            auto runner = make_when_all_homogeneous_runner(
502  
            auto runner = make_when_all_homogeneous_runner(
501  
                std::move(a), state_, index);
503  
                std::move(a), state_, index);
502  

504  

503  
            auto h = runner.release();
505  
            auto h = runner.release();
504  
            h.promise().state_ = state_;
506  
            h.promise().state_ = state_;
505  
            h.promise().index_ = index;
507  
            h.promise().index_ = index;
506  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
508  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
507  

509  

508  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
510  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
509  
            ++index;
511  
            ++index;
510  
        }
512  
        }
511  

513  

512  
        // Phase 2: Post all runners. Any may complete synchronously.
514  
        // Phase 2: Post all runners. Any may complete synchronously.
513  
        // After last post, state_ and this may be destroyed.
515  
        // After last post, state_ and this may be destroyed.
514  
        auto* handles = state_->runner_handles_.get();
516  
        auto* handles = state_->runner_handles_.get();
515  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
517  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
516  
        for(std::size_t i = 0; i < count; ++i)
518  
        for(std::size_t i = 0; i < count; ++i)
517  
            caller_env->executor.post(handles[i]);
519  
            caller_env->executor.post(handles[i]);
518  

520  

519  
        return std::noop_coroutine();
521  
        return std::noop_coroutine();
520  
    }
522  
    }
521  

523  

522  
    void await_resume() const noexcept
524  
    void await_resume() const noexcept
523  
    {
525  
    {
524  
    }
526  
    }
525  
};
527  
};
526  

528  

527  
} // namespace detail
529  
} // namespace detail
528  

530  

529  
/** Execute a range of io_result-returning awaitables concurrently.
531  
/** Execute a range of io_result-returning awaitables concurrently.
530  

532  

531  
    Launches all awaitables simultaneously and waits for all to complete.
533  
    Launches all awaitables simultaneously and waits for all to complete.
532  
    On success, extracted payloads are collected in a vector preserving
534  
    On success, extracted payloads are collected in a vector preserving
533  
    input order. The first error_code cancels siblings and is propagated
535  
    input order. The first error_code cancels siblings and is propagated
534  
    in the outer io_result. Exceptions always beat error codes.
536  
    in the outer io_result. Exceptions always beat error codes.
535  

537  

536  
    @li All child awaitables run concurrently on the caller's executor
538  
    @li All child awaitables run concurrently on the caller's executor
537  
    @li Payloads are returned as a vector in input order
539  
    @li Payloads are returned as a vector in input order
538  
    @li First error_code wins and cancels siblings
540  
    @li First error_code wins and cancels siblings
539  
    @li Exception always beats error_code
541  
    @li Exception always beats error_code
540  
    @li Completes only after all children have finished
542  
    @li Completes only after all children have finished
541  

543  

542  
    @par Thread Safety
544  
    @par Thread Safety
543  
    The returned task must be awaited from a single execution context.
545  
    The returned task must be awaited from a single execution context.
544  
    Child awaitables execute concurrently but complete through the caller's
546  
    Child awaitables execute concurrently but complete through the caller's
545  
    executor.
547  
    executor.
546  

548  

547  
    @param awaitables Range of io_result-returning awaitables to execute
549  
    @param awaitables Range of io_result-returning awaitables to execute
548  
        concurrently (must not be empty).
550  
        concurrently (must not be empty).
549  

551  

550  
    @return A task yielding io_result<vector<PayloadT>> where PayloadT
552  
    @return A task yielding io_result<vector<PayloadT>> where PayloadT
551  
        is the payload extracted from each child's io_result.
553  
        is the payload extracted from each child's io_result.
552  

554  

553  
    @throws std::invalid_argument if range is empty (thrown before
555  
    @throws std::invalid_argument if range is empty (thrown before
554  
        coroutine suspends).
556  
        coroutine suspends).
555  
    @throws Rethrows the first child exception after all children
557  
    @throws Rethrows the first child exception after all children
556  
        complete (exception beats error_code).
558  
        complete (exception beats error_code).
557  

559  

558  
    @par Example
560  
    @par Example
559  
    @code
561  
    @code
560  
    task<void> example()
562  
    task<void> example()
561  
    {
563  
    {
562  
        std::vector<io_task<size_t>> reads;
564  
        std::vector<io_task<size_t>> reads;
563  
        for (auto& buf : buffers)
565  
        for (auto& buf : buffers)
564  
            reads.push_back(stream.read_some(buf));
566  
            reads.push_back(stream.read_some(buf));
565  

567  

566  
        auto [ec, counts] = co_await when_all(std::move(reads));
568  
        auto [ec, counts] = co_await when_all(std::move(reads));
567  
        if (ec) { // handle error
569  
        if (ec) { // handle error
568  
        }
570  
        }
569  
    }
571  
    }
570  
    @endcode
572  
    @endcode
571  

573  

572  
    @see IoAwaitableRange, when_all
574  
    @see IoAwaitableRange, when_all
573  
*/
575  
*/
574  
template<IoAwaitableRange R>
576  
template<IoAwaitableRange R>
575  
    requires detail::is_io_result_v<
577  
    requires detail::is_io_result_v<
576  
        awaitable_result_t<std::ranges::range_value_t<R>>>
578  
        awaitable_result_t<std::ranges::range_value_t<R>>>
577  
    && (!std::is_same_v<
579  
    && (!std::is_same_v<
578  
            detail::io_result_payload_t<
580  
            detail::io_result_payload_t<
579  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
581  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
580  
            std::tuple<>>)
582  
            std::tuple<>>)
581  
[[nodiscard]] auto when_all(R&& awaitables)
583  
[[nodiscard]] auto when_all(R&& awaitables)
582  
    -> task<io_result<std::vector<
584  
    -> task<io_result<std::vector<
583  
        detail::io_result_payload_t<
585  
        detail::io_result_payload_t<
584  
            awaitable_result_t<std::ranges::range_value_t<R>>>>>>
586  
            awaitable_result_t<std::ranges::range_value_t<R>>>>>>
585  
{
587  
{
586  
    using Awaitable = std::ranges::range_value_t<R>;
588  
    using Awaitable = std::ranges::range_value_t<R>;
587  
    using PayloadT = detail::io_result_payload_t<
589  
    using PayloadT = detail::io_result_payload_t<
588  
        awaitable_result_t<Awaitable>>;
590  
        awaitable_result_t<Awaitable>>;
589  
    using OwnedRange = std::remove_cvref_t<R>;
591  
    using OwnedRange = std::remove_cvref_t<R>;
590  

592  

591  
    auto count = std::ranges::size(awaitables);
593  
    auto count = std::ranges::size(awaitables);
592  
    if(count == 0)
594  
    if(count == 0)
593  
        throw std::invalid_argument("when_all requires at least one awaitable");
595  
        throw std::invalid_argument("when_all requires at least one awaitable");
594  

596  

595  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
597  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
596  

598  

597  
    detail::when_all_homogeneous_state<PayloadT> state(count);
599  
    detail::when_all_homogeneous_state<PayloadT> state(count);
598  

600  

599  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
601  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
600  
        &owned_awaitables, &state);
602  
        &owned_awaitables, &state);
601  

603  

602  
    if(state.core_.first_exception_)
604  
    if(state.core_.first_exception_)
603  
        std::rethrow_exception(state.core_.first_exception_);
605  
        std::rethrow_exception(state.core_.first_exception_);
604  

606  

605  
    if(state.has_error_.load(std::memory_order_relaxed))
607  
    if(state.has_error_.load(std::memory_order_relaxed))
606  
        co_return io_result<std::vector<PayloadT>>{state.first_error_, {}};
608  
        co_return io_result<std::vector<PayloadT>>{state.first_error_, {}};
607  

609  

608  
    std::vector<PayloadT> results;
610  
    std::vector<PayloadT> results;
609  
    results.reserve(count);
611  
    results.reserve(count);
610  
    for(auto& opt : state.results_)
612  
    for(auto& opt : state.results_)
611  
        results.push_back(std::move(*opt));
613  
        results.push_back(std::move(*opt));
612  

614  

613  
    co_return io_result<std::vector<PayloadT>>{{}, std::move(results)};
615  
    co_return io_result<std::vector<PayloadT>>{{}, std::move(results)};
614  
}
616  
}
615  

617  

616  
/** Execute a range of void io_result-returning awaitables concurrently.
618  
/** Execute a range of void io_result-returning awaitables concurrently.
617  

619  

618  
    Launches all awaitables simultaneously and waits for all to complete.
620  
    Launches all awaitables simultaneously and waits for all to complete.
619  
    Since all awaitables return io_result<>, no payload values are
621  
    Since all awaitables return io_result<>, no payload values are
620  
    collected. The first error_code cancels siblings and is propagated.
622  
    collected. The first error_code cancels siblings and is propagated.
621  
    Exceptions always beat error codes.
623  
    Exceptions always beat error codes.
622  

624  

623  
    @param awaitables Range of io_result<>-returning awaitables to
625  
    @param awaitables Range of io_result<>-returning awaitables to
624  
        execute concurrently (must not be empty).
626  
        execute concurrently (must not be empty).
625  

627  

626  
    @return A task yielding io_result<> whose ec is the first child
628  
    @return A task yielding io_result<> whose ec is the first child
627  
        error, or default-constructed on success.
629  
        error, or default-constructed on success.
628  

630  

629  
    @throws std::invalid_argument if range is empty.
631  
    @throws std::invalid_argument if range is empty.
630  
    @throws Rethrows the first child exception after all children
632  
    @throws Rethrows the first child exception after all children
631  
        complete (exception beats error_code).
633  
        complete (exception beats error_code).
632  

634  

633  
    @par Example
635  
    @par Example
634  
    @code
636  
    @code
635  
    task<void> example()
637  
    task<void> example()
636  
    {
638  
    {
637  
        std::vector<io_task<>> jobs;
639  
        std::vector<io_task<>> jobs;
638  
        for (int i = 0; i < n; ++i)
640  
        for (int i = 0; i < n; ++i)
639  
            jobs.push_back(process(i));
641  
            jobs.push_back(process(i));
640  

642  

641  
        auto [ec] = co_await when_all(std::move(jobs));
643  
        auto [ec] = co_await when_all(std::move(jobs));
642  
    }
644  
    }
643  
    @endcode
645  
    @endcode
644  

646  

645  
    @see IoAwaitableRange, when_all
647  
    @see IoAwaitableRange, when_all
646  
*/
648  
*/
647  
template<IoAwaitableRange R>
649  
template<IoAwaitableRange R>
648  
    requires detail::is_io_result_v<
650  
    requires detail::is_io_result_v<
649  
        awaitable_result_t<std::ranges::range_value_t<R>>>
651  
        awaitable_result_t<std::ranges::range_value_t<R>>>
650  
    && std::is_same_v<
652  
    && std::is_same_v<
651  
            detail::io_result_payload_t<
653  
            detail::io_result_payload_t<
652  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
654  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
653  
            std::tuple<>>
655  
            std::tuple<>>
654  
[[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>>
656  
[[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>>
655  
{
657  
{
656  
    using OwnedRange = std::remove_cvref_t<R>;
658  
    using OwnedRange = std::remove_cvref_t<R>;
657  

659  

658  
    auto count = std::ranges::size(awaitables);
660  
    auto count = std::ranges::size(awaitables);
659  
    if(count == 0)
661  
    if(count == 0)
660  
        throw std::invalid_argument("when_all requires at least one awaitable");
662  
        throw std::invalid_argument("when_all requires at least one awaitable");
661  

663  

662  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
664  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
663  

665  

664  
    detail::when_all_homogeneous_state<std::tuple<>> state(count);
666  
    detail::when_all_homogeneous_state<std::tuple<>> state(count);
665  

667  

666  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
668  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
667  
        &owned_awaitables, &state);
669  
        &owned_awaitables, &state);
668  

670  

669  
    if(state.core_.first_exception_)
671  
    if(state.core_.first_exception_)
670  
        std::rethrow_exception(state.core_.first_exception_);
672  
        std::rethrow_exception(state.core_.first_exception_);
671  

673  

672  
    if(state.has_error_.load(std::memory_order_relaxed))
674  
    if(state.has_error_.load(std::memory_order_relaxed))
673  
        co_return io_result<>{state.first_error_};
675  
        co_return io_result<>{state.first_error_};
674  

676  

675  
    co_return io_result<>{};
677  
    co_return io_result<>{};
676  
}
678  
}
677  

679  

678  
/** Execute io_result-returning awaitables concurrently, inspecting error codes.
680  
/** Execute io_result-returning awaitables concurrently, inspecting error codes.
679  

681  

680  
    Overload selected when all children return io_result<Ts...>.
682  
    Overload selected when all children return io_result<Ts...>.
681  
    The error_code is lifted out of each child into a single outer
683  
    The error_code is lifted out of each child into a single outer
682  
    io_result. On success all values are returned; on failure the
684  
    io_result. On success all values are returned; on failure the
683  
    first error_code wins.
685  
    first error_code wins.
684  

686  

685  
    @par Exception Safety
687  
    @par Exception Safety
686  
    Exception always beats error_code. If any child throws, the
688  
    Exception always beats error_code. If any child throws, the
687  
    exception is rethrown regardless of error_code results.
689  
    exception is rethrown regardless of error_code results.
688  

690  

689  
    @param awaitables One or more awaitables each returning
691  
    @param awaitables One or more awaitables each returning
690  
        io_result<Ts...>.
692  
        io_result<Ts...>.
691  

693  

692  
    @return A task yielding io_result<R1, R2, ..., Rn> where each Ri
694  
    @return A task yielding io_result<R1, R2, ..., Rn> where each Ri
693  
        follows the payload flattening rules.
695  
        follows the payload flattening rules.
694  
*/
696  
*/
695  
template<IoAwaitable... As>
697  
template<IoAwaitable... As>
696  
    requires (sizeof...(As) > 0)
698  
    requires (sizeof...(As) > 0)
697  
          && detail::all_io_result_awaitables<As...>
699  
          && detail::all_io_result_awaitables<As...>
698  
[[nodiscard]] auto when_all(As... awaitables)
700  
[[nodiscard]] auto when_all(As... awaitables)
699  
    -> task<io_result<
701  
    -> task<io_result<
700  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
702  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
701  
{
703  
{
702  
    using result_type = io_result<
704  
    using result_type = io_result<
703  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
705  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
704  

706  

705  
    detail::when_all_state<awaitable_result_t<As>...> state;
707  
    detail::when_all_state<awaitable_result_t<As>...> state;
706  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
708  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
707  

709  

708  
    co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state);
710  
    co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state);
709  

711  

710  
    // Exception always wins over error_code
712  
    // Exception always wins over error_code
711  
    if(state.core_.first_exception_)
713  
    if(state.core_.first_exception_)
712  
        std::rethrow_exception(state.core_.first_exception_);
714  
        std::rethrow_exception(state.core_.first_exception_);
713  

715  

714  
    auto r = detail::build_when_all_io_result<result_type>(
716  
    auto r = detail::build_when_all_io_result<result_type>(
715  
        detail::extract_results(state));
717  
        detail::extract_results(state));
716  
    if(state.has_error_.load(std::memory_order_relaxed))
718  
    if(state.has_error_.load(std::memory_order_relaxed))
717  
        r.ec = state.first_error_;
719  
        r.ec = state.first_error_;
718  
    co_return r;
720  
    co_return r;
719  
}
721  
}
720  

722  

721  
} // namespace capy
723  
} // namespace capy
722  
} // namespace boost
724  
} // namespace boost
723  

725  

724  
#endif
726  
#endif