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

21  

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

33  

34  
namespace boost {
34  
namespace boost {
35  
namespace capy {
35  
namespace capy {
36  

36  

37  
namespace detail {
37  
namespace detail {
38  

38  

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

45  

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

50  

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

56  

57  
/** Specialization for void tasks - returns monostate to preserve index mapping.
57  
/** Specialization for void tasks - returns monostate to preserve index mapping.
58  
*/
58  
*/
59  
template<>
59  
template<>
60  
struct result_holder<void>
60  
struct result_holder<void>
61  
{
61  
{
62  
    std::monostate get() && { return {}; }
62  
    std::monostate get() && { return {}; }
63  
};
63  
};
64  

64  

65  
/** Core shared state for when_all operations.
65  
/** Core shared state for when_all operations.
66  

66  

67  
    Contains all members and methods common to both heterogeneous (variadic)
67  
    Contains all members and methods common to both heterogeneous (variadic)
68  
    and homogeneous (range) when_all implementations. State classes embed
68  
    and homogeneous (range) when_all implementations. State classes embed
69  
    this via composition to avoid CRTP destructor ordering issues.
69  
    this via composition to avoid CRTP destructor ordering issues.
70  

70  

71  
    @par Thread Safety
71  
    @par Thread Safety
72  
    Atomic operations protect exception capture and completion count.
72  
    Atomic operations protect exception capture and completion count.
73  
*/
73  
*/
74  
struct when_all_core
74  
struct when_all_core
75  
{
75  
{
76  
    std::atomic<std::size_t> remaining_count_;
76  
    std::atomic<std::size_t> remaining_count_;
77  

77  

78  
    // Exception storage - first error wins, others discarded
78  
    // Exception storage - first error wins, others discarded
79  
    std::atomic<bool> has_exception_{false};
79  
    std::atomic<bool> has_exception_{false};
80  
    std::exception_ptr first_exception_;
80  
    std::exception_ptr first_exception_;
81  

81  

82  
    std::stop_source stop_source_;
82  
    std::stop_source stop_source_;
83  

83  

84  
    // Bridges parent's stop token to our stop_source
84  
    // Bridges parent's stop token to our stop_source
85  
    struct stop_callback_fn
85  
    struct stop_callback_fn
86  
    {
86  
    {
87  
        std::stop_source* source_;
87  
        std::stop_source* source_;
88  
        void operator()() const { source_->request_stop(); }
88  
        void operator()() const { source_->request_stop(); }
89  
    };
89  
    };
90  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
90  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
91  
    std::optional<stop_callback_t> parent_stop_callback_;
91  
    std::optional<stop_callback_t> parent_stop_callback_;
92  

92  

93  
    std::coroutine_handle<> continuation_;
93  
    std::coroutine_handle<> continuation_;
94  
    io_env const* caller_env_ = nullptr;
94  
    io_env const* caller_env_ = nullptr;
95  

95  

96  
    explicit when_all_core(std::size_t count) noexcept
96  
    explicit when_all_core(std::size_t count) noexcept
97  
        : remaining_count_(count)
97  
        : remaining_count_(count)
98  
    {
98  
    {
99  
    }
99  
    }
100  

100  

101  
    /** Capture an exception (first one wins). */
101  
    /** Capture an exception (first one wins). */
102  
    void capture_exception(std::exception_ptr ep)
102  
    void capture_exception(std::exception_ptr ep)
103  
    {
103  
    {
104  
        bool expected = false;
104  
        bool expected = false;
105  
        if(has_exception_.compare_exchange_strong(
105  
        if(has_exception_.compare_exchange_strong(
106  
            expected, true, std::memory_order_relaxed))
106  
            expected, true, std::memory_order_relaxed))
107  
            first_exception_ = ep;
107  
            first_exception_ = ep;
108  
    }
108  
    }
109  
};
109  
};
110  

110  

111  
/** Shared state for heterogeneous when_all (variadic overload).
111  
/** Shared state for heterogeneous when_all (variadic overload).
112  

112  

113  
    @tparam Ts The result types of the tasks.
113  
    @tparam Ts The result types of the tasks.
114  
*/
114  
*/
115  
template<typename... Ts>
115  
template<typename... Ts>
116  
struct when_all_state
116  
struct when_all_state
117  
{
117  
{
118  
    static constexpr std::size_t task_count = sizeof...(Ts);
118  
    static constexpr std::size_t task_count = sizeof...(Ts);
119  

119  

120  
    when_all_core core_;
120  
    when_all_core core_;
121  
    std::tuple<result_holder<Ts>...> results_;
121  
    std::tuple<result_holder<Ts>...> results_;
122  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
122  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
123  

123  

124  
    when_all_state()
124  
    when_all_state()
125  
        : core_(task_count)
125  
        : core_(task_count)
126  
    {
126  
    {
127  
    }
127  
    }
128  
};
128  
};
129  

129  

130  
/** Shared state for homogeneous when_all (range overload).
130  
/** Shared state for homogeneous when_all (range overload).
131  

131  

132  
    Stores all results in a vector indexed by task position.
132  
    Stores all results in a vector indexed by task position.
133  

133  

134  
    @tparam T The common result type of all tasks.
134  
    @tparam T The common result type of all tasks.
135  
*/
135  
*/
136  
template<typename T>
136  
template<typename T>
137  
struct when_all_homogeneous_state
137  
struct when_all_homogeneous_state
138  
{
138  
{
139  
    when_all_core core_;
139  
    when_all_core core_;
140  
    std::vector<std::optional<T>> results_;
140  
    std::vector<std::optional<T>> results_;
141  
    std::vector<std::coroutine_handle<>> runner_handles_;
141  
    std::vector<std::coroutine_handle<>> runner_handles_;
142  

142  

143  
    explicit when_all_homogeneous_state(std::size_t count)
143  
    explicit when_all_homogeneous_state(std::size_t count)
144  
        : core_(count)
144  
        : core_(count)
145  
        , results_(count)
145  
        , results_(count)
146  
        , runner_handles_(count)
146  
        , runner_handles_(count)
147  
    {
147  
    {
148  
    }
148  
    }
149  

149  

150  
    void set_result(std::size_t index, T value)
150  
    void set_result(std::size_t index, T value)
151  
    {
151  
    {
152  
        results_[index].emplace(std::move(value));
152  
        results_[index].emplace(std::move(value));
153  
    }
153  
    }
154  
};
154  
};
155  

155  

156  
/** Specialization for void tasks (no result storage). */
156  
/** Specialization for void tasks (no result storage). */
157  
template<>
157  
template<>
158  
struct when_all_homogeneous_state<void>
158  
struct when_all_homogeneous_state<void>
159  
{
159  
{
160  
    when_all_core core_;
160  
    when_all_core core_;
161  
    std::vector<std::coroutine_handle<>> runner_handles_;
161  
    std::vector<std::coroutine_handle<>> runner_handles_;
162  

162  

163  
    explicit when_all_homogeneous_state(std::size_t count)
163  
    explicit when_all_homogeneous_state(std::size_t count)
164  
        : core_(count)
164  
        : core_(count)
165  
        , runner_handles_(count)
165  
        , runner_handles_(count)
166  
    {
166  
    {
167  
    }
167  
    }
168  
};
168  
};
169  

169  

170  
/** Wrapper coroutine that intercepts task completion for when_all.
170  
/** Wrapper coroutine that intercepts task completion for when_all.
171  

171  

172  
    Parameterized on StateType to work with both heterogeneous (variadic)
172  
    Parameterized on StateType to work with both heterogeneous (variadic)
173  
    and homogeneous (range) state types. All state types expose their
173  
    and homogeneous (range) state types. All state types expose their
174  
    shared members through a `core_` member of type when_all_core.
174  
    shared members through a `core_` member of type when_all_core.
175  

175  

176  
    @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
176  
    @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
177  
*/
177  
*/
178  
template<typename StateType>
178  
template<typename StateType>
179  
struct when_all_runner
179  
struct when_all_runner
180  
{
180  
{
181  
    struct promise_type
181  
    struct promise_type
182  
    {
182  
    {
183  
        StateType* state_ = nullptr;
183  
        StateType* state_ = nullptr;
184  
        std::size_t index_ = 0;
184  
        std::size_t index_ = 0;
185  
        io_env env_;
185  
        io_env env_;
186  

186  

187  
        when_all_runner get_return_object() noexcept
187  
        when_all_runner get_return_object() noexcept
188  
        {
188  
        {
189  
            return when_all_runner(
189  
            return when_all_runner(
190  
                std::coroutine_handle<promise_type>::from_promise(*this));
190  
                std::coroutine_handle<promise_type>::from_promise(*this));
191  
        }
191  
        }
192  

192  

193  
        std::suspend_always initial_suspend() noexcept
193  
        std::suspend_always initial_suspend() noexcept
194  
        {
194  
        {
195  
            return {};
195  
            return {};
196  
        }
196  
        }
197  

197  

198  
        auto final_suspend() noexcept
198  
        auto final_suspend() noexcept
199  
        {
199  
        {
200  
            struct awaiter
200  
            struct awaiter
201  
            {
201  
            {
202  
                promise_type* p_;
202  
                promise_type* p_;
203  
                bool await_ready() const noexcept { return false; }
203  
                bool await_ready() const noexcept { return false; }
204  
                auto await_suspend(std::coroutine_handle<> h) noexcept
204  
                auto await_suspend(std::coroutine_handle<> h) noexcept
205  
                {
205  
                {
206  
                    auto& core = p_->state_->core_;
206  
                    auto& core = p_->state_->core_;
207  
                    auto* counter = &core.remaining_count_;
207  
                    auto* counter = &core.remaining_count_;
208  
                    auto* caller_env = core.caller_env_;
208  
                    auto* caller_env = core.caller_env_;
209  
                    auto cont = core.continuation_;
209  
                    auto cont = core.continuation_;
210  

210  

211  
                    h.destroy();
211  
                    h.destroy();
212  

212  

213  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
213  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
214  
                    if(remaining == 1)
214  
                    if(remaining == 1)
215  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
215  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
216  
                    return detail::symmetric_transfer(std::noop_coroutine());
216  
                    return detail::symmetric_transfer(std::noop_coroutine());
217  
                }
217  
                }
218  
                void await_resume() const noexcept {}
218  
                void await_resume() const noexcept {}
219  
            };
219  
            };
220  
            return awaiter{this};
220  
            return awaiter{this};
221  
        }
221  
        }
222  

222  

223  
        void return_void() noexcept {}
223  
        void return_void() noexcept {}
224  

224  

225  
        void unhandled_exception()
225  
        void unhandled_exception()
226  
        {
226  
        {
227  
            state_->core_.capture_exception(std::current_exception());
227  
            state_->core_.capture_exception(std::current_exception());
228  
            state_->core_.stop_source_.request_stop();
228  
            state_->core_.stop_source_.request_stop();
229  
        }
229  
        }
230  

230  

231  
        template<class Awaitable>
231  
        template<class Awaitable>
232  
        struct transform_awaiter
232  
        struct transform_awaiter
233  
        {
233  
        {
234  
            std::decay_t<Awaitable> a_;
234  
            std::decay_t<Awaitable> a_;
235  
            promise_type* p_;
235  
            promise_type* p_;
236  

236  

237  
            bool await_ready() { return a_.await_ready(); }
237  
            bool await_ready() { return a_.await_ready(); }
238  
            decltype(auto) await_resume() { return a_.await_resume(); }
238  
            decltype(auto) await_resume() { return a_.await_resume(); }
239  

239  

240  
            template<class Promise>
240  
            template<class Promise>
241  
            auto await_suspend(std::coroutine_handle<Promise> h)
241  
            auto await_suspend(std::coroutine_handle<Promise> h)
242  
            {
242  
            {
243  
                using R = decltype(a_.await_suspend(h, &p_->env_));
243  
                using R = decltype(a_.await_suspend(h, &p_->env_));
244  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
244  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
245  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
245  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
246  
                else
246  
                else
247  
                    return a_.await_suspend(h, &p_->env_);
247  
                    return a_.await_suspend(h, &p_->env_);
248  
            }
248  
            }
249  
        };
249  
        };
250  

250  

251  
        template<class Awaitable>
251  
        template<class Awaitable>
252  
        auto await_transform(Awaitable&& a)
252  
        auto await_transform(Awaitable&& a)
253  
        {
253  
        {
254  
            using A = std::decay_t<Awaitable>;
254  
            using A = std::decay_t<Awaitable>;
255  
            if constexpr (IoAwaitable<A>)
255  
            if constexpr (IoAwaitable<A>)
256  
            {
256  
            {
257  
                return transform_awaiter<Awaitable>{
257  
                return transform_awaiter<Awaitable>{
258  
                    std::forward<Awaitable>(a), this};
258  
                    std::forward<Awaitable>(a), this};
259  
            }
259  
            }
260  
            else
260  
            else
261  
            {
261  
            {
262  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
262  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
263  
            }
263  
            }
264  
        }
264  
        }
265  
    };
265  
    };
266  

266  

267  
    std::coroutine_handle<promise_type> h_;
267  
    std::coroutine_handle<promise_type> h_;
268  

268  

269  
    explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
269  
    explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
270  
        : h_(h)
270  
        : h_(h)
271  
    {
271  
    {
272  
    }
272  
    }
273  

273  

274  
    // Enable move for all clang versions - some versions need it
274  
    // Enable move for all clang versions - some versions need it
275  
    when_all_runner(when_all_runner&& other) noexcept
275  
    when_all_runner(when_all_runner&& other) noexcept
276  
        : h_(std::exchange(other.h_, nullptr))
276  
        : h_(std::exchange(other.h_, nullptr))
277  
    {
277  
    {
278  
    }
278  
    }
279  

279  

280  
    when_all_runner(when_all_runner const&) = delete;
280  
    when_all_runner(when_all_runner const&) = delete;
281  
    when_all_runner& operator=(when_all_runner const&) = delete;
281  
    when_all_runner& operator=(when_all_runner const&) = delete;
282  
    when_all_runner& operator=(when_all_runner&&) = delete;
282  
    when_all_runner& operator=(when_all_runner&&) = delete;
283  

283  

284  
    auto release() noexcept
284  
    auto release() noexcept
285  
    {
285  
    {
286  
        return std::exchange(h_, nullptr);
286  
        return std::exchange(h_, nullptr);
287  
    }
287  
    }
288  
};
288  
};
289  

289  

290  
/** Create a runner coroutine for a single awaitable (variadic path).
290  
/** Create a runner coroutine for a single awaitable (variadic path).
291  

291  

292  
    Uses compile-time index for tuple-based result storage.
292  
    Uses compile-time index for tuple-based result storage.
293  
*/
293  
*/
294  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
294  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
295  
when_all_runner<when_all_state<Ts...>>
295  
when_all_runner<when_all_state<Ts...>>
296  
make_when_all_runner(Awaitable inner, when_all_state<Ts...>* state)
296  
make_when_all_runner(Awaitable inner, when_all_state<Ts...>* state)
297  
{
297  
{
298  
    using T = awaitable_result_t<Awaitable>;
298  
    using T = awaitable_result_t<Awaitable>;
299  
    if constexpr (std::is_void_v<T>)
299  
    if constexpr (std::is_void_v<T>)
300  
    {
300  
    {
301  
        co_await std::move(inner);
301  
        co_await std::move(inner);
302  
    }
302  
    }
303  
    else
303  
    else
304  
    {
304  
    {
305  
        std::get<Index>(state->results_).set(co_await std::move(inner));
305  
        std::get<Index>(state->results_).set(co_await std::move(inner));
306  
    }
306  
    }
307  
}
307  
}
308  

308  

309  
/** Create a runner coroutine for a single awaitable (range path).
309  
/** Create a runner coroutine for a single awaitable (range path).
310  

310  

311  
    Uses runtime index for vector-based result storage.
311  
    Uses runtime index for vector-based result storage.
312  
*/
312  
*/
313  
template<IoAwaitable Awaitable, typename StateType>
313  
template<IoAwaitable Awaitable, typename StateType>
314  
when_all_runner<StateType>
314  
when_all_runner<StateType>
315  
make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index)
315  
make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index)
316  
{
316  
{
317  
    using T = awaitable_result_t<Awaitable>;
317  
    using T = awaitable_result_t<Awaitable>;
318  
    if constexpr (std::is_void_v<T>)
318  
    if constexpr (std::is_void_v<T>)
319  
    {
319  
    {
320  
        co_await std::move(inner);
320  
        co_await std::move(inner);
321  
    }
321  
    }
322  
    else
322  
    else
323  
    {
323  
    {
324  
        state->set_result(index, co_await std::move(inner));
324  
        state->set_result(index, co_await std::move(inner));
325  
    }
325  
    }
326  
}
326  
}
327  

327  

328  
/** Internal awaitable that launches all variadic runner coroutines.
328  
/** Internal awaitable that launches all variadic runner coroutines.
329  

329  

330  
    CRITICAL: If the last task finishes synchronously then the parent
330  
    CRITICAL: If the last task finishes synchronously then the parent
331  
    coroutine resumes, destroying its frame, and destroying this object
331  
    coroutine resumes, destroying its frame, and destroying this object
332  
    prior to the completion of await_suspend. Therefore, await_suspend
332  
    prior to the completion of await_suspend. Therefore, await_suspend
333  
    must ensure `this` cannot be referenced after calling `launch_one`
333  
    must ensure `this` cannot be referenced after calling `launch_one`
334  
    for the last time.
334  
    for the last time.
335  
*/
335  
*/
336  
template<IoAwaitable... Awaitables>
336  
template<IoAwaitable... Awaitables>
337  
class when_all_launcher
337  
class when_all_launcher
338  
{
338  
{
339  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
339  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
340  

340  

341  
    std::tuple<Awaitables...>* awaitables_;
341  
    std::tuple<Awaitables...>* awaitables_;
342  
    state_type* state_;
342  
    state_type* state_;
343  

343  

344  
public:
344  
public:
345  
    when_all_launcher(
345  
    when_all_launcher(
346  
        std::tuple<Awaitables...>* awaitables,
346  
        std::tuple<Awaitables...>* awaitables,
347  
        state_type* state)
347  
        state_type* state)
348  
        : awaitables_(awaitables)
348  
        : awaitables_(awaitables)
349  
        , state_(state)
349  
        , state_(state)
350  
    {
350  
    {
351  
    }
351  
    }
352  

352  

353  
    bool await_ready() const noexcept
353  
    bool await_ready() const noexcept
354  
    {
354  
    {
355  
        return sizeof...(Awaitables) == 0;
355  
        return sizeof...(Awaitables) == 0;
356  
    }
356  
    }
357  

357  

358  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
358  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
359  
    {
359  
    {
360  
        state_->core_.continuation_ = continuation;
360  
        state_->core_.continuation_ = continuation;
361  
        state_->core_.caller_env_ = caller_env;
361  
        state_->core_.caller_env_ = caller_env;
362  

362  

363  
        if(caller_env->stop_token.stop_possible())
363  
        if(caller_env->stop_token.stop_possible())
364  
        {
364  
        {
365  
            state_->core_.parent_stop_callback_.emplace(
365  
            state_->core_.parent_stop_callback_.emplace(
366  
                caller_env->stop_token,
366  
                caller_env->stop_token,
367  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
367  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
368  

368  

369  
            if(caller_env->stop_token.stop_requested())
369  
            if(caller_env->stop_token.stop_requested())
370  
                state_->core_.stop_source_.request_stop();
370  
                state_->core_.stop_source_.request_stop();
371  
        }
371  
        }
372  

372  

373  
        auto token = state_->core_.stop_source_.get_token();
373  
        auto token = state_->core_.stop_source_.get_token();
374  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
374  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
375  
            (..., launch_one<Is>(caller_env->executor, token));
375  
            (..., launch_one<Is>(caller_env->executor, token));
376  
        }(std::index_sequence_for<Awaitables...>{});
376  
        }(std::index_sequence_for<Awaitables...>{});
377  

377  

378  
        return std::noop_coroutine();
378  
        return std::noop_coroutine();
379  
    }
379  
    }
380  

380  

381  
    void await_resume() const noexcept
381  
    void await_resume() const noexcept
382  
    {
382  
    {
383  
    }
383  
    }
384  

384  

385  
private:
385  
private:
386  
    template<std::size_t I>
386  
    template<std::size_t I>
387  
    void launch_one(executor_ref caller_ex, std::stop_token token)
387  
    void launch_one(executor_ref caller_ex, std::stop_token token)
388  
    {
388  
    {
389  
        auto runner = make_when_all_runner<I>(
389  
        auto runner = make_when_all_runner<I>(
390  
            std::move(std::get<I>(*awaitables_)), state_);
390  
            std::move(std::get<I>(*awaitables_)), state_);
391  

391  

392  
        auto h = runner.release();
392  
        auto h = runner.release();
393  
        h.promise().state_ = state_;
393  
        h.promise().state_ = state_;
394  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
394  
        h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator};
395  

395  

396  
        std::coroutine_handle<> ch{h};
396  
        std::coroutine_handle<> ch{h};
397  
        state_->runner_handles_[I] = ch;
397  
        state_->runner_handles_[I] = ch;
398  
        state_->core_.caller_env_->executor.post(ch);
398  
        state_->core_.caller_env_->executor.post(ch);
399  
    }
399  
    }
400  
};
400  
};
401  

401  

402  
/** Helper to extract a single result from state.
402  
/** Helper to extract a single result from state.
403  
    This is a separate function to work around a GCC-11 ICE that occurs
403  
    This is a separate function to work around a GCC-11 ICE that occurs
404  
    when using nested immediately-invoked lambdas with pack expansion.
404  
    when using nested immediately-invoked lambdas with pack expansion.
405  
*/
405  
*/
406  
template<std::size_t I, typename... Ts>
406  
template<std::size_t I, typename... Ts>
407  
auto extract_single_result(when_all_state<Ts...>& state)
407  
auto extract_single_result(when_all_state<Ts...>& state)
408  
{
408  
{
409  
    return std::move(std::get<I>(state.results_)).get();
409  
    return std::move(std::get<I>(state.results_)).get();
410  
}
410  
}
411  

411  

412  
/** Extract all results from state as a tuple.
412  
/** Extract all results from state as a tuple.
413  
*/
413  
*/
414  
template<typename... Ts>
414  
template<typename... Ts>
415  
auto extract_results(when_all_state<Ts...>& state)
415  
auto extract_results(when_all_state<Ts...>& state)
416  
{
416  
{
417  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
417  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
418  
        return std::tuple(extract_single_result<Is>(state)...);
418  
        return std::tuple(extract_single_result<Is>(state)...);
419  
    }(std::index_sequence_for<Ts...>{});
419  
    }(std::index_sequence_for<Ts...>{});
420  
}
420  
}
421  

421  

422  
/** Launches all homogeneous runners concurrently.
422  
/** Launches all homogeneous runners concurrently.
423  

423  

424  
    Two-phase approach: create all runners first, then post all.
424  
    Two-phase approach: create all runners first, then post all.
425  
    This avoids lifetime issues if a task completes synchronously.
425  
    This avoids lifetime issues if a task completes synchronously.
426  
*/
426  
*/
427  
template<typename Range>
427  
template<typename Range>
428  
class when_all_homogeneous_launcher
428  
class when_all_homogeneous_launcher
429  
{
429  
{
430  
    using Awaitable = std::ranges::range_value_t<Range>;
430  
    using Awaitable = std::ranges::range_value_t<Range>;
431  
    using T = awaitable_result_t<Awaitable>;
431  
    using T = awaitable_result_t<Awaitable>;
432  

432  

433  
    Range* range_;
433  
    Range* range_;
434  
    when_all_homogeneous_state<T>* state_;
434  
    when_all_homogeneous_state<T>* state_;
435  

435  

436  
public:
436  
public:
437  
    when_all_homogeneous_launcher(
437  
    when_all_homogeneous_launcher(
438  
        Range* range,
438  
        Range* range,
439  
        when_all_homogeneous_state<T>* state)
439  
        when_all_homogeneous_state<T>* state)
440  
        : range_(range)
440  
        : range_(range)
441  
        , state_(state)
441  
        , state_(state)
442  
    {
442  
    {
443  
    }
443  
    }
444  

444  

445  
    bool await_ready() const noexcept
445  
    bool await_ready() const noexcept
446  
    {
446  
    {
447  
        return std::ranges::empty(*range_);
447  
        return std::ranges::empty(*range_);
448  
    }
448  
    }
449  

449  

450  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
450  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
451  
    {
451  
    {
452  
        state_->core_.continuation_ = continuation;
452  
        state_->core_.continuation_ = continuation;
453  
        state_->core_.caller_env_ = caller_env;
453  
        state_->core_.caller_env_ = caller_env;
454  

454  

455  
        if(caller_env->stop_token.stop_possible())
455  
        if(caller_env->stop_token.stop_possible())
456  
        {
456  
        {
457  
            state_->core_.parent_stop_callback_.emplace(
457  
            state_->core_.parent_stop_callback_.emplace(
458  
                caller_env->stop_token,
458  
                caller_env->stop_token,
459  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
459  
                when_all_core::stop_callback_fn{&state_->core_.stop_source_});
460  

460  

461  
            if(caller_env->stop_token.stop_requested())
461  
            if(caller_env->stop_token.stop_requested())
462  
                state_->core_.stop_source_.request_stop();
462  
                state_->core_.stop_source_.request_stop();
463  
        }
463  
        }
464  

464  

465  
        auto token = state_->core_.stop_source_.get_token();
465  
        auto token = state_->core_.stop_source_.get_token();
466  

466  

467  
        // Phase 1: Create all runners without dispatching.
467  
        // Phase 1: Create all runners without dispatching.
468  
        std::size_t index = 0;
468  
        std::size_t index = 0;
469  
        for(auto&& a : *range_)
469  
        for(auto&& a : *range_)
470  
        {
470  
        {
471  
            auto runner = make_when_all_homogeneous_runner(
471  
            auto runner = make_when_all_homogeneous_runner(
472  
                std::move(a), state_, index);
472  
                std::move(a), state_, index);
473  

473  

474  
            auto h = runner.release();
474  
            auto h = runner.release();
475  
            h.promise().state_ = state_;
475  
            h.promise().state_ = state_;
476  
            h.promise().index_ = index;
476  
            h.promise().index_ = index;
477  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
477  
            h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
478  

478  

479  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
479  
            state_->runner_handles_[index] = std::coroutine_handle<>{h};
480  
            ++index;
480  
            ++index;
481  
        }
481  
        }
482  

482  

483  
        // Phase 2: Post all runners. Any may complete synchronously.
483  
        // Phase 2: Post all runners. Any may complete synchronously.
484  
        // After last post, state_ and this may be destroyed.
484  
        // After last post, state_ and this may be destroyed.
485  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
485  
        std::coroutine_handle<>* handles = state_->runner_handles_.data();
486  
        std::size_t count = state_->runner_handles_.size();
486  
        std::size_t count = state_->runner_handles_.size();
487  
        for(std::size_t i = 0; i < count; ++i)
487  
        for(std::size_t i = 0; i < count; ++i)
488  
            caller_env->executor.post(handles[i]);
488  
            caller_env->executor.post(handles[i]);
489  

489  

490  
        return std::noop_coroutine();
490  
        return std::noop_coroutine();
491  
    }
491  
    }
492  

492  

493  
    void await_resume() const noexcept
493  
    void await_resume() const noexcept
494  
    {
494  
    {
495  
    }
495  
    }
496  
};
496  
};
497  

497  

498  
} // namespace detail
498  
} // namespace detail
499  

499  

500  
/** Compute the when_all result tuple type.
500  
/** Compute the when_all result tuple type.
501  

501  

502  
    Void-returning tasks contribute std::monostate to preserve the
502  
    Void-returning tasks contribute std::monostate to preserve the
503  
    task-index-to-result-index mapping, matching when_any's approach.
503  
    task-index-to-result-index mapping, matching when_any's approach.
504  

504  

505  
    Example: when_all_result_t<int, void, string> = std::tuple<int, std::monostate, string>
505  
    Example: when_all_result_t<int, void, string> = std::tuple<int, std::monostate, string>
506  
    Example: when_all_result_t<void, void> = std::tuple<std::monostate, std::monostate>
506  
    Example: when_all_result_t<void, void> = std::tuple<std::monostate, std::monostate>
507  
*/
507  
*/
508  
template<typename... Ts>
508  
template<typename... Ts>
509  
using when_all_result_t = std::tuple<void_to_monostate_t<Ts>...>;
509  
using when_all_result_t = std::tuple<void_to_monostate_t<Ts>...>;
510  

510  

511  
/** Execute multiple awaitables concurrently and collect their results.
511  
/** Execute multiple awaitables concurrently and collect their results.
512  

512  

513  
    Launches all awaitables simultaneously and waits for all to complete
513  
    Launches all awaitables simultaneously and waits for all to complete
514  
    before returning. Results are collected in input order. If any
514  
    before returning. Results are collected in input order. If any
515  
    awaitable throws, cancellation is requested for siblings and the first
515  
    awaitable throws, cancellation is requested for siblings and the first
516  
    exception is rethrown after all awaitables complete.
516  
    exception is rethrown after all awaitables complete.
517  

517  

518  
    @li All child awaitables run concurrently on the caller's executor
518  
    @li All child awaitables run concurrently on the caller's executor
519  
    @li Results are returned as a tuple in input order
519  
    @li Results are returned as a tuple in input order
520  
    @li Void-returning awaitables contribute std::monostate to the
520  
    @li Void-returning awaitables contribute std::monostate to the
521  
        result tuple, preserving the task-index-to-result-index mapping
521  
        result tuple, preserving the task-index-to-result-index mapping
522  
    @li First exception wins; subsequent exceptions are discarded
522  
    @li First exception wins; subsequent exceptions are discarded
523  
    @li Stop is requested for siblings on first error
523  
    @li Stop is requested for siblings on first error
524  
    @li Completes only after all children have finished
524  
    @li Completes only after all children have finished
525  

525  

526  
    @par Thread Safety
526  
    @par Thread Safety
527  
    The returned task must be awaited from a single execution context.
527  
    The returned task must be awaited from a single execution context.
528  
    Child awaitables execute concurrently but complete through the caller's
528  
    Child awaitables execute concurrently but complete through the caller's
529  
    executor.
529  
    executor.
530  

530  

531  
    @param awaitables The awaitables to execute concurrently. Each must
531  
    @param awaitables The awaitables to execute concurrently. Each must
532  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
532  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
533  
        `when_all` is awaited.
533  
        `when_all` is awaited.
534  

534  

535  
    @return A task yielding a tuple of results in input order. Void tasks
535  
    @return A task yielding a tuple of results in input order. Void tasks
536  
        contribute std::monostate to preserve index correspondence.
536  
        contribute std::monostate to preserve index correspondence.
537  

537  

538  
    @par Example
538  
    @par Example
539  

539  

540  
    @code
540  
    @code
541  
    task<> example()
541  
    task<> example()
542  
    {
542  
    {
543  
        // Concurrent fetch, results collected in order
543  
        // Concurrent fetch, results collected in order
544  
        auto [user, posts] = co_await when_all(
544  
        auto [user, posts] = co_await when_all(
545  
            fetch_user( id ),      // task<User>
545  
            fetch_user( id ),      // task<User>
546  
            fetch_posts( id )      // task<std::vector<Post>>
546  
            fetch_posts( id )      // task<std::vector<Post>>
547  
        );
547  
        );
548  

548  

549  
        // Void awaitables contribute monostate
549  
        // Void awaitables contribute monostate
550  
        auto [a, _, b] = co_await when_all(
550  
        auto [a, _, b] = co_await when_all(
551  
            fetch_int(),           // task<int>
551  
            fetch_int(),           // task<int>
552  
            log_event( "start" ),  // task<void>  → monostate
552  
            log_event( "start" ),  // task<void>  → monostate
553  
            fetch_str()            // task<string>
553  
            fetch_str()            // task<string>
554  
        );
554  
        );
555  
        // a is int, _ is monostate, b is string
555  
        // a is int, _ is monostate, b is string
556  
    }
556  
    }
557  
    @endcode
557  
    @endcode
558  

558  

559  
    @see IoAwaitable, task
559  
    @see IoAwaitable, task
560  
*/
560  
*/
561  
template<IoAwaitable... As>
561  
template<IoAwaitable... As>
562  
[[nodiscard]] auto when_all(As... awaitables)
562  
[[nodiscard]] auto when_all(As... awaitables)
563  
    -> task<when_all_result_t<awaitable_result_t<As>...>>
563  
    -> task<when_all_result_t<awaitable_result_t<As>...>>
564  
{
564  
{
565  
    // State is stored in the coroutine frame, using the frame allocator
565  
    // State is stored in the coroutine frame, using the frame allocator
566  
    detail::when_all_state<awaitable_result_t<As>...> state;
566  
    detail::when_all_state<awaitable_result_t<As>...> state;
567  

567  

568  
    // Store awaitables in the frame
568  
    // Store awaitables in the frame
569  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
569  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
570  

570  

571  
    // Launch all awaitables and wait for completion
571  
    // Launch all awaitables and wait for completion
572  
    co_await detail::when_all_launcher<As...>(&awaitable_tuple, &state);
572  
    co_await detail::when_all_launcher<As...>(&awaitable_tuple, &state);
573  

573  

574  
    // Propagate first exception if any.
574  
    // Propagate first exception if any.
575  
    // Safe without explicit acquire: capture_exception() is sequenced-before
575  
    // Safe without explicit acquire: capture_exception() is sequenced-before
576  
    // signal_completion()'s acq_rel fetch_sub, which synchronizes-with the
576  
    // signal_completion()'s acq_rel fetch_sub, which synchronizes-with the
577  
    // last task's decrement that resumes this coroutine.
577  
    // last task's decrement that resumes this coroutine.
578  
    if(state.core_.first_exception_)
578  
    if(state.core_.first_exception_)
579  
        std::rethrow_exception(state.core_.first_exception_);
579  
        std::rethrow_exception(state.core_.first_exception_);
580  

580  

581  
    co_return detail::extract_results(state);
581  
    co_return detail::extract_results(state);
582  
}
582  
}
583  

583  

584  
/** Execute a range of awaitables concurrently and collect their results.
584  
/** Execute a range of awaitables concurrently and collect their results.
585  

585  

586  
    Launches all awaitables in the range simultaneously and waits for all
586  
    Launches all awaitables in the range simultaneously and waits for all
587  
    to complete. Results are collected in a vector preserving input order.
587  
    to complete. Results are collected in a vector preserving input order.
588  
    If any awaitable throws, cancellation is requested for siblings and
588  
    If any awaitable throws, cancellation is requested for siblings and
589  
    the first exception is rethrown after all awaitables complete.
589  
    the first exception is rethrown after all awaitables complete.
590  

590  

591  
    @li All child awaitables run concurrently on the caller's executor
591  
    @li All child awaitables run concurrently on the caller's executor
592  
    @li Results are returned as a vector in input order
592  
    @li Results are returned as a vector in input order
593  
    @li First exception wins; subsequent exceptions are discarded
593  
    @li First exception wins; subsequent exceptions are discarded
594  
    @li Stop is requested for siblings on first error
594  
    @li Stop is requested for siblings on first error
595  
    @li Completes only after all children have finished
595  
    @li Completes only after all children have finished
596  

596  

597  
    @par Thread Safety
597  
    @par Thread Safety
598  
    The returned task must be awaited from a single execution context.
598  
    The returned task must be awaited from a single execution context.
599  
    Child awaitables execute concurrently but complete through the caller's
599  
    Child awaitables execute concurrently but complete through the caller's
600  
    executor.
600  
    executor.
601  

601  

602  
    @param awaitables Range of awaitables to execute concurrently (must
602  
    @param awaitables Range of awaitables to execute concurrently (must
603  
        not be empty). Each element must satisfy @ref IoAwaitable and is
603  
        not be empty). Each element must satisfy @ref IoAwaitable and is
604  
        consumed (moved-from) when `when_all` is awaited.
604  
        consumed (moved-from) when `when_all` is awaited.
605  

605  

606  
    @return A task yielding a vector where each element is the result of
606  
    @return A task yielding a vector where each element is the result of
607  
        the corresponding awaitable, in input order.
607  
        the corresponding awaitable, in input order.
608  

608  

609  
    @throws std::invalid_argument if range is empty (thrown before
609  
    @throws std::invalid_argument if range is empty (thrown before
610  
        coroutine suspends).
610  
        coroutine suspends).
611  
    @throws Rethrows the first child exception after all children
611  
    @throws Rethrows the first child exception after all children
612  
        complete.
612  
        complete.
613  

613  

614  
    @par Example
614  
    @par Example
615  
    @code
615  
    @code
616  
    task<void> example()
616  
    task<void> example()
617  
    {
617  
    {
618  
        std::vector<task<Response>> requests;
618  
        std::vector<task<Response>> requests;
619  
        for (auto const& url : urls)
619  
        for (auto const& url : urls)
620  
            requests.push_back(fetch(url));
620  
            requests.push_back(fetch(url));
621  

621  

622  
        auto responses = co_await when_all(std::move(requests));
622  
        auto responses = co_await when_all(std::move(requests));
623  
    }
623  
    }
624  
    @endcode
624  
    @endcode
625  

625  

626  
    @see IoAwaitableRange, when_all
626  
    @see IoAwaitableRange, when_all
627  
*/
627  
*/
628  
template<IoAwaitableRange R>
628  
template<IoAwaitableRange R>
629  
    requires (!std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>)
629  
    requires (!std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>)
630  
[[nodiscard]] auto when_all(R&& awaitables)
630  
[[nodiscard]] auto when_all(R&& awaitables)
631  
    -> task<std::vector<awaitable_result_t<std::ranges::range_value_t<R>>>>
631  
    -> task<std::vector<awaitable_result_t<std::ranges::range_value_t<R>>>>
632  
{
632  
{
633  
    using Awaitable = std::ranges::range_value_t<R>;
633  
    using Awaitable = std::ranges::range_value_t<R>;
634  
    using T = awaitable_result_t<Awaitable>;
634  
    using T = awaitable_result_t<Awaitable>;
635  
    using OwnedRange = std::remove_cvref_t<R>;
635  
    using OwnedRange = std::remove_cvref_t<R>;
636  

636  

637  
    auto count = std::ranges::size(awaitables);
637  
    auto count = std::ranges::size(awaitables);
638  
    if(count == 0)
638  
    if(count == 0)
639  
        throw std::invalid_argument("when_all requires at least one awaitable");
639  
        throw std::invalid_argument("when_all requires at least one awaitable");
640  

640  

641  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
641  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
642  

642  

643  
    detail::when_all_homogeneous_state<T> state(count);
643  
    detail::when_all_homogeneous_state<T> state(count);
644  

644  

645  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
645  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
646  
        &owned_awaitables, &state);
646  
        &owned_awaitables, &state);
647  

647  

648  
    if(state.core_.first_exception_)
648  
    if(state.core_.first_exception_)
649  
        std::rethrow_exception(state.core_.first_exception_);
649  
        std::rethrow_exception(state.core_.first_exception_);
650  

650  

651  
    std::vector<T> results;
651  
    std::vector<T> results;
652  
    results.reserve(count);
652  
    results.reserve(count);
653  
    for(auto& opt : state.results_)
653  
    for(auto& opt : state.results_)
654  
        results.push_back(std::move(*opt));
654  
        results.push_back(std::move(*opt));
655  

655  

656  
    co_return results;
656  
    co_return results;
657  
}
657  
}
658  

658  

659  
/** Execute a range of void awaitables concurrently.
659  
/** Execute a range of void awaitables concurrently.
660  

660  

661  
    Launches all awaitables in the range simultaneously and waits for all
661  
    Launches all awaitables in the range simultaneously and waits for all
662  
    to complete. Since all awaitables return void, no results are collected.
662  
    to complete. Since all awaitables return void, no results are collected.
663  
    If any awaitable throws, cancellation is requested for siblings and
663  
    If any awaitable throws, cancellation is requested for siblings and
664  
    the first exception is rethrown after all awaitables complete.
664  
    the first exception is rethrown after all awaitables complete.
665  

665  

666  
    @li All child awaitables run concurrently on the caller's executor
666  
    @li All child awaitables run concurrently on the caller's executor
667  
    @li First exception wins; subsequent exceptions are discarded
667  
    @li First exception wins; subsequent exceptions are discarded
668  
    @li Stop is requested for siblings on first error
668  
    @li Stop is requested for siblings on first error
669  
    @li Completes only after all children have finished
669  
    @li Completes only after all children have finished
670  

670  

671  
    @par Thread Safety
671  
    @par Thread Safety
672  
    The returned task must be awaited from a single execution context.
672  
    The returned task must be awaited from a single execution context.
673  
    Child awaitables execute concurrently but complete through the caller's
673  
    Child awaitables execute concurrently but complete through the caller's
674  
    executor.
674  
    executor.
675  

675  

676  
    @param awaitables Range of void awaitables to execute concurrently
676  
    @param awaitables Range of void awaitables to execute concurrently
677  
        (must not be empty).
677  
        (must not be empty).
678  

678  

679  
    @throws std::invalid_argument if range is empty (thrown before
679  
    @throws std::invalid_argument if range is empty (thrown before
680  
        coroutine suspends).
680  
        coroutine suspends).
681  
    @throws Rethrows the first child exception after all children
681  
    @throws Rethrows the first child exception after all children
682  
        complete.
682  
        complete.
683  

683  

684  
    @par Example
684  
    @par Example
685  
    @code
685  
    @code
686  
    task<void> example()
686  
    task<void> example()
687  
    {
687  
    {
688  
        std::vector<task<void>> jobs;
688  
        std::vector<task<void>> jobs;
689  
        for (int i = 0; i < n; ++i)
689  
        for (int i = 0; i < n; ++i)
690  
            jobs.push_back(process(i));
690  
            jobs.push_back(process(i));
691  

691  

692  
        co_await when_all(std::move(jobs));
692  
        co_await when_all(std::move(jobs));
693  
    }
693  
    }
694  
    @endcode
694  
    @endcode
695  

695  

696  
    @see IoAwaitableRange, when_all
696  
    @see IoAwaitableRange, when_all
697  
*/
697  
*/
698  
template<IoAwaitableRange R>
698  
template<IoAwaitableRange R>
699  
    requires std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>
699  
    requires std::is_void_v<awaitable_result_t<std::ranges::range_value_t<R>>>
700  
[[nodiscard]] auto when_all(R&& awaitables) -> task<void>
700  
[[nodiscard]] auto when_all(R&& awaitables) -> task<void>
701  
{
701  
{
702  
    using OwnedRange = std::remove_cvref_t<R>;
702  
    using OwnedRange = std::remove_cvref_t<R>;
703  

703  

704  
    auto count = std::ranges::size(awaitables);
704  
    auto count = std::ranges::size(awaitables);
705  
    if(count == 0)
705  
    if(count == 0)
706  
        throw std::invalid_argument("when_all requires at least one awaitable");
706  
        throw std::invalid_argument("when_all requires at least one awaitable");
707  

707  

708  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
708  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
709  

709  

710  
    detail::when_all_homogeneous_state<void> state(count);
710  
    detail::when_all_homogeneous_state<void> state(count);
711  

711  

712  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
712  
    co_await detail::when_all_homogeneous_launcher<OwnedRange>(
713  
        &owned_awaitables, &state);
713  
        &owned_awaitables, &state);
714  

714  

715  
    if(state.core_.first_exception_)
715  
    if(state.core_.first_exception_)
716  
        std::rethrow_exception(state.core_.first_exception_);
716  
        std::rethrow_exception(state.core_.first_exception_);
717  
}
717  
}
718  

718  

719  
} // namespace capy
719  
} // namespace capy
720  
} // namespace boost
720  
} // namespace boost
721  

721  

722  
#endif
722  
#endif