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

20  

21  
#include <array>
21  
#include <array>
22  
#include <atomic>
22  
#include <atomic>
23  
#include <exception>
23  
#include <exception>
24  
#include <optional>
24  
#include <optional>
25  
#include <stop_token>
25  
#include <stop_token>
26  
#include <tuple>
26  
#include <tuple>
27  
#include <type_traits>
27  
#include <type_traits>
28  
#include <utility>
28  
#include <utility>
29  

29  

30  
namespace boost {
30  
namespace boost {
31  
namespace capy {
31  
namespace capy {
32  

32  

33  
namespace detail {
33  
namespace detail {
34  

34  

35  
/** Type trait to filter void types from a tuple.
35  
/** Type trait to filter void types from a tuple.
36  

36  

37  
    Void-returning tasks do not contribute a value to the result tuple.
37  
    Void-returning tasks do not contribute a value to the result tuple.
38  
    This trait computes the filtered result type.
38  
    This trait computes the filtered result type.
39  

39  

40  
    Example: filter_void_tuple_t<int, void, string> = tuple<int, string>
40  
    Example: filter_void_tuple_t<int, void, string> = tuple<int, string>
41  
*/
41  
*/
42  
template<typename T>
42  
template<typename T>
43  
using wrap_non_void_t = std::conditional_t<std::is_void_v<T>, std::tuple<>, std::tuple<T>>;
43  
using wrap_non_void_t = std::conditional_t<std::is_void_v<T>, std::tuple<>, std::tuple<T>>;
44  

44  

45  
template<typename... Ts>
45  
template<typename... Ts>
46  
using filter_void_tuple_t = decltype(std::tuple_cat(std::declval<wrap_non_void_t<Ts>>()...));
46  
using filter_void_tuple_t = decltype(std::tuple_cat(std::declval<wrap_non_void_t<Ts>>()...));
47  

47  

48  
/** Holds the result of a single task within when_all.
48  
/** Holds the result of a single task within when_all.
49  
*/
49  
*/
50  
template<typename T>
50  
template<typename T>
51  
struct result_holder
51  
struct result_holder
52  
{
52  
{
53  
    std::optional<T> value_;
53  
    std::optional<T> value_;
54  

54  

55  
    void set(T v)
55  
    void set(T v)
56  
    {
56  
    {
57  
        value_ = std::move(v);
57  
        value_ = std::move(v);
58  
    }
58  
    }
59  

59  

60  
    T get() &&
60  
    T get() &&
61  
    {
61  
    {
62  
        return std::move(*value_);
62  
        return std::move(*value_);
63  
    }
63  
    }
64  
};
64  
};
65  

65  

66  
/** Specialization for void tasks - no value storage needed.
66  
/** Specialization for void tasks - no value storage needed.
67  
*/
67  
*/
68  
template<>
68  
template<>
69  
struct result_holder<void>
69  
struct result_holder<void>
70  
{
70  
{
71  
};
71  
};
72  

72  

73  
/** Shared state for when_all operation.
73  
/** Shared state for when_all operation.
74  

74  

75  
    @tparam Ts The result types of the tasks.
75  
    @tparam Ts The result types of the tasks.
76  
*/
76  
*/
77  
template<typename... Ts>
77  
template<typename... Ts>
78  
struct when_all_state
78  
struct when_all_state
79  
{
79  
{
80  
    static constexpr std::size_t task_count = sizeof...(Ts);
80  
    static constexpr std::size_t task_count = sizeof...(Ts);
81  

81  

82  
    // Completion tracking - when_all waits for all children
82  
    // Completion tracking - when_all waits for all children
83  
    std::atomic<std::size_t> remaining_count_;
83  
    std::atomic<std::size_t> remaining_count_;
84  

84  

85  
    // Result storage in input order
85  
    // Result storage in input order
86  
    std::tuple<result_holder<Ts>...> results_;
86  
    std::tuple<result_holder<Ts>...> results_;
87  

87  

88  
    // Runner handles - destroyed in await_resume while allocator is valid
88  
    // Runner handles - destroyed in await_resume while allocator is valid
89  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
89  
    std::array<std::coroutine_handle<>, task_count> runner_handles_{};
90  

90  

91  
    // Exception storage - first error wins, others discarded
91  
    // Exception storage - first error wins, others discarded
92  
    std::atomic<bool> has_exception_{false};
92  
    std::atomic<bool> has_exception_{false};
93  
    std::exception_ptr first_exception_;
93  
    std::exception_ptr first_exception_;
94  

94  

95  
    // Stop propagation - on error, request stop for siblings
95  
    // Stop propagation - on error, request stop for siblings
96  
    std::stop_source stop_source_;
96  
    std::stop_source stop_source_;
97  

97  

98  
    // Connects parent's stop_token to our stop_source
98  
    // Connects parent's stop_token to our stop_source
99  
    struct stop_callback_fn
99  
    struct stop_callback_fn
100  
    {
100  
    {
101  
        std::stop_source* source_;
101  
        std::stop_source* source_;
102  
        void operator()() const { source_->request_stop(); }
102  
        void operator()() const { source_->request_stop(); }
103  
    };
103  
    };
104  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
104  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
105  
    std::optional<stop_callback_t> parent_stop_callback_;
105  
    std::optional<stop_callback_t> parent_stop_callback_;
106  

106  

107  
    // Parent resumption
107  
    // Parent resumption
108  
    std::coroutine_handle<> continuation_;
108  
    std::coroutine_handle<> continuation_;
109  
    io_env const* caller_env_ = nullptr;
109  
    io_env const* caller_env_ = nullptr;
110  

110  

111  
    when_all_state()
111  
    when_all_state()
112  
        : remaining_count_(task_count)
112  
        : remaining_count_(task_count)
113  
    {
113  
    {
114  
    }
114  
    }
115  

115  

116  
    // Runners self-destruct in final_suspend. No destruction needed here.
116  
    // Runners self-destruct in final_suspend. No destruction needed here.
117  

117  

118  
    /** Capture an exception (first one wins).
118  
    /** Capture an exception (first one wins).
119  
    */
119  
    */
120  
    void capture_exception(std::exception_ptr ep)
120  
    void capture_exception(std::exception_ptr ep)
121  
    {
121  
    {
122  
        bool expected = false;
122  
        bool expected = false;
123  
        if(has_exception_.compare_exchange_strong(
123  
        if(has_exception_.compare_exchange_strong(
124  
            expected, true, std::memory_order_relaxed))
124  
            expected, true, std::memory_order_relaxed))
125  
            first_exception_ = ep;
125  
            first_exception_ = ep;
126  
    }
126  
    }
127  

127  

128  
};
128  
};
129  

129  

130  
/** Wrapper coroutine that intercepts task completion.
130  
/** Wrapper coroutine that intercepts task completion.
131  

131  

132  
    This runner awaits its assigned task and stores the result in
132  
    This runner awaits its assigned task and stores the result in
133  
    the shared state, or captures the exception and requests stop.
133  
    the shared state, or captures the exception and requests stop.
134  
*/
134  
*/
135  
template<typename T, typename... Ts>
135  
template<typename T, typename... Ts>
136  
struct when_all_runner
136  
struct when_all_runner
137  
{
137  
{
138  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
138  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
139  
    {
139  
    {
140  
        when_all_state<Ts...>* state_ = nullptr;
140  
        when_all_state<Ts...>* state_ = nullptr;
141  
        io_env env_;
141  
        io_env env_;
142  

142  

143  
        when_all_runner get_return_object()
143  
        when_all_runner get_return_object()
144  
        {
144  
        {
145  
            return when_all_runner(std::coroutine_handle<promise_type>::from_promise(*this));
145  
            return when_all_runner(std::coroutine_handle<promise_type>::from_promise(*this));
146  
        }
146  
        }
147  

147  

148  
        std::suspend_always initial_suspend() noexcept
148  
        std::suspend_always initial_suspend() noexcept
149  
        {
149  
        {
150  
            return {};
150  
            return {};
151  
        }
151  
        }
152  

152  

153  
        auto final_suspend() noexcept
153  
        auto final_suspend() noexcept
154  
        {
154  
        {
155  
            struct awaiter
155  
            struct awaiter
156  
            {
156  
            {
157  
                promise_type* p_;
157  
                promise_type* p_;
158  

158  

159  
                bool await_ready() const noexcept
159  
                bool await_ready() const noexcept
160  
                {
160  
                {
161  
                    return false;
161  
                    return false;
162  
                }
162  
                }
163  

163  

164  
                std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept
164  
                std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept
165  
                {
165  
                {
166  
                    // Extract everything needed before self-destruction.
166  
                    // Extract everything needed before self-destruction.
167  
                    auto* state = p_->state_;
167  
                    auto* state = p_->state_;
168  
                    auto* counter = &state->remaining_count_;
168  
                    auto* counter = &state->remaining_count_;
169  
                    auto* caller_env = state->caller_env_;
169  
                    auto* caller_env = state->caller_env_;
170  
                    auto cont = state->continuation_;
170  
                    auto cont = state->continuation_;
171  

171  

172  
                    h.destroy();
172  
                    h.destroy();
173  

173  

174  
                    // If last runner, dispatch parent for symmetric transfer.
174  
                    // If last runner, dispatch parent for symmetric transfer.
175  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
175  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
176  
                    if(remaining == 1)
176  
                    if(remaining == 1)
177  
                        return caller_env->executor.dispatch(cont);
177  
                        return caller_env->executor.dispatch(cont);
178  
                    return std::noop_coroutine();
178  
                    return std::noop_coroutine();
179  
                }
179  
                }
180  

180  

181  
                void await_resume() const noexcept
181  
                void await_resume() const noexcept
182  
                {
182  
                {
183  
                }
183  
                }
184  
            };
184  
            };
185  
            return awaiter{this};
185  
            return awaiter{this};
186  
        }
186  
        }
187  

187  

188  
        void return_void()
188  
        void return_void()
189  
        {
189  
        {
190  
        }
190  
        }
191  

191  

192  
        void unhandled_exception()
192  
        void unhandled_exception()
193  
        {
193  
        {
194  
            state_->capture_exception(std::current_exception());
194  
            state_->capture_exception(std::current_exception());
195  
            // Request stop for sibling tasks
195  
            // Request stop for sibling tasks
196  
            state_->stop_source_.request_stop();
196  
            state_->stop_source_.request_stop();
197  
        }
197  
        }
198  

198  

199  
        template<class Awaitable>
199  
        template<class Awaitable>
200  
        struct transform_awaiter
200  
        struct transform_awaiter
201  
        {
201  
        {
202  
            std::decay_t<Awaitable> a_;
202  
            std::decay_t<Awaitable> a_;
203  
            promise_type* p_;
203  
            promise_type* p_;
204  

204  

205  
            bool await_ready()
205  
            bool await_ready()
206  
            {
206  
            {
207  
                return a_.await_ready();
207  
                return a_.await_ready();
208  
            }
208  
            }
209  

209  

210  
            decltype(auto) await_resume()
210  
            decltype(auto) await_resume()
211  
            {
211  
            {
212  
                return a_.await_resume();
212  
                return a_.await_resume();
213  
            }
213  
            }
214  

214  

215  
            template<class Promise>
215  
            template<class Promise>
216  
            auto await_suspend(std::coroutine_handle<Promise> h)
216  
            auto await_suspend(std::coroutine_handle<Promise> h)
217  
            {
217  
            {
 
218 +
#ifdef _MSC_VER
 
219 +
                using R = decltype(a_.await_suspend(h, &p_->env_));
 
220 +
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
 
221 +
                    a_.await_suspend(h, &p_->env_).resume();
 
222 +
                else
 
223 +
                    return a_.await_suspend(h, &p_->env_);
 
224 +
#else
218  
                return a_.await_suspend(h, &p_->env_);
225  
                return a_.await_suspend(h, &p_->env_);
 
226 +
#endif
219  
            }
227  
            }
220  
        };
228  
        };
221  

229  

222  
        template<class Awaitable>
230  
        template<class Awaitable>
223  
        auto await_transform(Awaitable&& a)
231  
        auto await_transform(Awaitable&& a)
224  
        {
232  
        {
225  
            using A = std::decay_t<Awaitable>;
233  
            using A = std::decay_t<Awaitable>;
226  
            if constexpr (IoAwaitable<A>)
234  
            if constexpr (IoAwaitable<A>)
227  
            {
235  
            {
228  
                return transform_awaiter<Awaitable>{
236  
                return transform_awaiter<Awaitable>{
229  
                    std::forward<Awaitable>(a), this};
237  
                    std::forward<Awaitable>(a), this};
230  
            }
238  
            }
231  
            else
239  
            else
232  
            {
240  
            {
233  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
241  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
234  
            }
242  
            }
235  
        }
243  
        }
236  
    };
244  
    };
237  

245  

238  
    std::coroutine_handle<promise_type> h_;
246  
    std::coroutine_handle<promise_type> h_;
239  

247  

240  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
248  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
241  
        : h_(h)
249  
        : h_(h)
242  
    {
250  
    {
243  
    }
251  
    }
244  

252  

245  
    // Enable move for all clang versions - some versions need it
253  
    // Enable move for all clang versions - some versions need it
246  
    when_all_runner(when_all_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
254  
    when_all_runner(when_all_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
247  

255  

248  
    // Non-copyable
256  
    // Non-copyable
249  
    when_all_runner(when_all_runner const&) = delete;
257  
    when_all_runner(when_all_runner const&) = delete;
250  
    when_all_runner& operator=(when_all_runner const&) = delete;
258  
    when_all_runner& operator=(when_all_runner const&) = delete;
251  
    when_all_runner& operator=(when_all_runner&&) = delete;
259  
    when_all_runner& operator=(when_all_runner&&) = delete;
252  

260  

253  
    auto release() noexcept
261  
    auto release() noexcept
254  
    {
262  
    {
255  
        return std::exchange(h_, nullptr);
263  
        return std::exchange(h_, nullptr);
256  
    }
264  
    }
257  
};
265  
};
258  

266  

259  
/** Create a runner coroutine for a single awaitable.
267  
/** Create a runner coroutine for a single awaitable.
260  

268  

261  
    Awaitable is passed directly to ensure proper coroutine frame storage.
269  
    Awaitable is passed directly to ensure proper coroutine frame storage.
262  
*/
270  
*/
263  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
271  
template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
264  
when_all_runner<awaitable_result_t<Awaitable>, Ts...>
272  
when_all_runner<awaitable_result_t<Awaitable>, Ts...>
265  
make_when_all_runner(Awaitable inner, when_all_state<Ts...>* state)
273  
make_when_all_runner(Awaitable inner, when_all_state<Ts...>* state)
266  
{
274  
{
267  
    using T = awaitable_result_t<Awaitable>;
275  
    using T = awaitable_result_t<Awaitable>;
268  
    if constexpr (std::is_void_v<T>)
276  
    if constexpr (std::is_void_v<T>)
269  
    {
277  
    {
270  
        co_await std::move(inner);
278  
        co_await std::move(inner);
271  
    }
279  
    }
272  
    else
280  
    else
273  
    {
281  
    {
274  
        std::get<Index>(state->results_).set(co_await std::move(inner));
282  
        std::get<Index>(state->results_).set(co_await std::move(inner));
275  
    }
283  
    }
276  
}
284  
}
277  

285  

278  
/** Internal awaitable that launches all runner coroutines and waits.
286  
/** Internal awaitable that launches all runner coroutines and waits.
279  

287  

280  
    This awaitable is used inside the when_all coroutine to handle
288  
    This awaitable is used inside the when_all coroutine to handle
281  
    the concurrent execution of child awaitables.
289  
    the concurrent execution of child awaitables.
282  
*/
290  
*/
283  
template<IoAwaitable... Awaitables>
291  
template<IoAwaitable... Awaitables>
284  
class when_all_launcher
292  
class when_all_launcher
285  
{
293  
{
286  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
294  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
287  

295  

288  
    std::tuple<Awaitables...>* awaitables_;
296  
    std::tuple<Awaitables...>* awaitables_;
289  
    state_type* state_;
297  
    state_type* state_;
290  

298  

291  
public:
299  
public:
292  
    when_all_launcher(
300  
    when_all_launcher(
293  
        std::tuple<Awaitables...>* awaitables,
301  
        std::tuple<Awaitables...>* awaitables,
294  
        state_type* state)
302  
        state_type* state)
295  
        : awaitables_(awaitables)
303  
        : awaitables_(awaitables)
296  
        , state_(state)
304  
        , state_(state)
297  
    {
305  
    {
298  
    }
306  
    }
299  

307  

300  
    bool await_ready() const noexcept
308  
    bool await_ready() const noexcept
301  
    {
309  
    {
302  
        return sizeof...(Awaitables) == 0;
310  
        return sizeof...(Awaitables) == 0;
303  
    }
311  
    }
304  

312  

305  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
313  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
306  
    {
314  
    {
307  
        state_->continuation_ = continuation;
315  
        state_->continuation_ = continuation;
308  
        state_->caller_env_ = caller_env;
316  
        state_->caller_env_ = caller_env;
309  

317  

310  
        // Forward parent's stop requests to children
318  
        // Forward parent's stop requests to children
311  
        if(caller_env->stop_token.stop_possible())
319  
        if(caller_env->stop_token.stop_possible())
312  
        {
320  
        {
313  
            state_->parent_stop_callback_.emplace(
321  
            state_->parent_stop_callback_.emplace(
314  
                caller_env->stop_token,
322  
                caller_env->stop_token,
315  
                typename state_type::stop_callback_fn{&state_->stop_source_});
323  
                typename state_type::stop_callback_fn{&state_->stop_source_});
316  

324  

317  
            if(caller_env->stop_token.stop_requested())
325  
            if(caller_env->stop_token.stop_requested())
318  
                state_->stop_source_.request_stop();
326  
                state_->stop_source_.request_stop();
319  
        }
327  
        }
320  

328  

321  
        // CRITICAL: If the last task finishes synchronously then the parent
329  
        // CRITICAL: If the last task finishes synchronously then the parent
322  
        // coroutine resumes, destroying its frame, and destroying this object
330  
        // coroutine resumes, destroying its frame, and destroying this object
323  
        // prior to the completion of await_suspend. Therefore, await_suspend
331  
        // prior to the completion of await_suspend. Therefore, await_suspend
324  
        // must ensure `this` cannot be referenced after calling `launch_one`
332  
        // must ensure `this` cannot be referenced after calling `launch_one`
325  
        // for the last time.
333  
        // for the last time.
326  
        auto token = state_->stop_source_.get_token();
334  
        auto token = state_->stop_source_.get_token();
327  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
335  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
328  
            (..., launch_one<Is>(caller_env->executor, token));
336  
            (..., launch_one<Is>(caller_env->executor, token));
329  
        }(std::index_sequence_for<Awaitables...>{});
337  
        }(std::index_sequence_for<Awaitables...>{});
330  

338  

331  
        // Let signal_completion() handle resumption
339  
        // Let signal_completion() handle resumption
332  
        return std::noop_coroutine();
340  
        return std::noop_coroutine();
333  
    }
341  
    }
334  

342  

335  
    void await_resume() const noexcept
343  
    void await_resume() const noexcept
336  
    {
344  
    {
337  
        // Results are extracted by the when_all coroutine from state
345  
        // Results are extracted by the when_all coroutine from state
338  
    }
346  
    }
339  

347  

340  
private:
348  
private:
341  
    template<std::size_t I>
349  
    template<std::size_t I>
342  
    void launch_one(executor_ref caller_ex, std::stop_token token)
350  
    void launch_one(executor_ref caller_ex, std::stop_token token)
343  
    {
351  
    {
344  
        auto runner = make_when_all_runner<I>(
352  
        auto runner = make_when_all_runner<I>(
345  
            std::move(std::get<I>(*awaitables_)), state_);
353  
            std::move(std::get<I>(*awaitables_)), state_);
346  

354  

347  
        auto h = runner.release();
355  
        auto h = runner.release();
348  
        h.promise().state_ = state_;
356  
        h.promise().state_ = state_;
349 -
        h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->frame_allocator};
357 +
        h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->allocator};
350  

358  

351  
        std::coroutine_handle<> ch{h};
359  
        std::coroutine_handle<> ch{h};
352  
        state_->runner_handles_[I] = ch;
360  
        state_->runner_handles_[I] = ch;
353  
        state_->caller_env_->executor.post(ch);
361  
        state_->caller_env_->executor.post(ch);
354  
    }
362  
    }
355  
};
363  
};
356  

364  

357  
/** Compute the result type for when_all.
365  
/** Compute the result type for when_all.
358  

366  

359  
    Returns void when all tasks are void (P2300 aligned),
367  
    Returns void when all tasks are void (P2300 aligned),
360  
    otherwise returns a tuple with void types filtered out.
368  
    otherwise returns a tuple with void types filtered out.
361  
*/
369  
*/
362  
template<typename... Ts>
370  
template<typename... Ts>
363  
using when_all_result_t = std::conditional_t<
371  
using when_all_result_t = std::conditional_t<
364  
    std::is_same_v<filter_void_tuple_t<Ts...>, std::tuple<>>,
372  
    std::is_same_v<filter_void_tuple_t<Ts...>, std::tuple<>>,
365  
    void,
373  
    void,
366  
    filter_void_tuple_t<Ts...>>;
374  
    filter_void_tuple_t<Ts...>>;
367  

375  

368  
/** Helper to extract a single result, returning empty tuple for void.
376  
/** Helper to extract a single result, returning empty tuple for void.
369  
    This is a separate function to work around a GCC-11 ICE that occurs
377  
    This is a separate function to work around a GCC-11 ICE that occurs
370  
    when using nested immediately-invoked lambdas with pack expansion.
378  
    when using nested immediately-invoked lambdas with pack expansion.
371  
*/
379  
*/
372  
template<std::size_t I, typename... Ts>
380  
template<std::size_t I, typename... Ts>
373  
auto extract_single_result(when_all_state<Ts...>& state)
381  
auto extract_single_result(when_all_state<Ts...>& state)
374  
{
382  
{
375  
    using T = std::tuple_element_t<I, std::tuple<Ts...>>;
383  
    using T = std::tuple_element_t<I, std::tuple<Ts...>>;
376  
    if constexpr (std::is_void_v<T>)
384  
    if constexpr (std::is_void_v<T>)
377  
        return std::tuple<>();
385  
        return std::tuple<>();
378  
    else
386  
    else
379  
        return std::make_tuple(std::move(std::get<I>(state.results_)).get());
387  
        return std::make_tuple(std::move(std::get<I>(state.results_)).get());
380  
}
388  
}
381  

389  

382  
/** Extract results from state, filtering void types.
390  
/** Extract results from state, filtering void types.
383  
*/
391  
*/
384  
template<typename... Ts>
392  
template<typename... Ts>
385  
auto extract_results(when_all_state<Ts...>& state)
393  
auto extract_results(when_all_state<Ts...>& state)
386  
{
394  
{
387  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
395  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
388  
        return std::tuple_cat(extract_single_result<Is>(state)...);
396  
        return std::tuple_cat(extract_single_result<Is>(state)...);
389  
    }(std::index_sequence_for<Ts...>{});
397  
    }(std::index_sequence_for<Ts...>{});
390  
}
398  
}
391  

399  

392  
} // namespace detail
400  
} // namespace detail
393  

401  

394  
/** Execute multiple awaitables concurrently and collect their results.
402  
/** Execute multiple awaitables concurrently and collect their results.
395  

403  

396  
    Launches all awaitables simultaneously and waits for all to complete
404  
    Launches all awaitables simultaneously and waits for all to complete
397  
    before returning. Results are collected in input order. If any
405  
    before returning. Results are collected in input order. If any
398  
    awaitable throws, cancellation is requested for siblings and the first
406  
    awaitable throws, cancellation is requested for siblings and the first
399  
    exception is rethrown after all awaitables complete.
407  
    exception is rethrown after all awaitables complete.
400  

408  

401  
    @li All child awaitables run concurrently on the caller's executor
409  
    @li All child awaitables run concurrently on the caller's executor
402  
    @li Results are returned as a tuple in input order
410  
    @li Results are returned as a tuple in input order
403  
    @li Void-returning awaitables do not contribute to the result tuple
411  
    @li Void-returning awaitables do not contribute to the result tuple
404  
    @li If all awaitables return void, `when_all` returns `task<void>`
412  
    @li If all awaitables return void, `when_all` returns `task<void>`
405  
    @li First exception wins; subsequent exceptions are discarded
413  
    @li First exception wins; subsequent exceptions are discarded
406  
    @li Stop is requested for siblings on first error
414  
    @li Stop is requested for siblings on first error
407  
    @li Completes only after all children have finished
415  
    @li Completes only after all children have finished
408  

416  

409  
    @par Thread Safety
417  
    @par Thread Safety
410  
    The returned task must be awaited from a single execution context.
418  
    The returned task must be awaited from a single execution context.
411  
    Child awaitables execute concurrently but complete through the caller's
419  
    Child awaitables execute concurrently but complete through the caller's
412  
    executor.
420  
    executor.
413  

421  

414  
    @param awaitables The awaitables to execute concurrently. Each must
422  
    @param awaitables The awaitables to execute concurrently. Each must
415  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
423  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
416  
        `when_all` is awaited.
424  
        `when_all` is awaited.
417  

425  

418  
    @return A task yielding a tuple of non-void results. Returns
426  
    @return A task yielding a tuple of non-void results. Returns
419  
        `task<void>` when all input awaitables return void.
427  
        `task<void>` when all input awaitables return void.
420  

428  

421  
    @par Example
429  
    @par Example
422  

430  

423  
    @code
431  
    @code
424  
    task<> example()
432  
    task<> example()
425  
    {
433  
    {
426  
        // Concurrent fetch, results collected in order
434  
        // Concurrent fetch, results collected in order
427  
        auto [user, posts] = co_await when_all(
435  
        auto [user, posts] = co_await when_all(
428  
            fetch_user( id ),      // task<User>
436  
            fetch_user( id ),      // task<User>
429  
            fetch_posts( id )      // task<std::vector<Post>>
437  
            fetch_posts( id )      // task<std::vector<Post>>
430  
        );
438  
        );
431  

439  

432  
        // Void awaitables don't contribute to result
440  
        // Void awaitables don't contribute to result
433  
        co_await when_all(
441  
        co_await when_all(
434  
            log_event( "start" ),  // task<void>
442  
            log_event( "start" ),  // task<void>
435  
            notify_user( id )      // task<void>
443  
            notify_user( id )      // task<void>
436  
        );
444  
        );
437  
        // Returns task<void>, no result tuple
445  
        // Returns task<void>, no result tuple
438  
    }
446  
    }
439  
    @endcode
447  
    @endcode
440  

448  

441  
    @see IoAwaitable, task
449  
    @see IoAwaitable, task
442  
*/
450  
*/
443  
template<IoAwaitable... As>
451  
template<IoAwaitable... As>
444  
[[nodiscard]] auto when_all(As... awaitables)
452  
[[nodiscard]] auto when_all(As... awaitables)
445  
    -> task<detail::when_all_result_t<detail::awaitable_result_t<As>...>>
453  
    -> task<detail::when_all_result_t<detail::awaitable_result_t<As>...>>
446  
{
454  
{
447  
    using result_type = detail::when_all_result_t<detail::awaitable_result_t<As>...>;
455  
    using result_type = detail::when_all_result_t<detail::awaitable_result_t<As>...>;
448  

456  

449  
    // State is stored in the coroutine frame, using the frame allocator
457  
    // State is stored in the coroutine frame, using the frame allocator
450  
    detail::when_all_state<detail::awaitable_result_t<As>...> state;
458  
    detail::when_all_state<detail::awaitable_result_t<As>...> state;
451  

459  

452  
    // Store awaitables in the frame
460  
    // Store awaitables in the frame
453  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
461  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
454  

462  

455  
    // Launch all awaitables and wait for completion
463  
    // Launch all awaitables and wait for completion
456  
    co_await detail::when_all_launcher<As...>(&awaitable_tuple, &state);
464  
    co_await detail::when_all_launcher<As...>(&awaitable_tuple, &state);
457  

465  

458  
    // Propagate first exception if any.
466  
    // Propagate first exception if any.
459  
    // Safe without explicit acquire: capture_exception() is sequenced-before
467  
    // Safe without explicit acquire: capture_exception() is sequenced-before
460  
    // signal_completion()'s acq_rel fetch_sub, which synchronizes-with the
468  
    // signal_completion()'s acq_rel fetch_sub, which synchronizes-with the
461  
    // last task's decrement that resumes this coroutine.
469  
    // last task's decrement that resumes this coroutine.
462  
    if(state.first_exception_)
470  
    if(state.first_exception_)
463  
        std::rethrow_exception(state.first_exception_);
471  
        std::rethrow_exception(state.first_exception_);
464  

472  

465  
    // Extract and return results
473  
    // Extract and return results
466  
    if constexpr (std::is_void_v<result_type>)
474  
    if constexpr (std::is_void_v<result_type>)
467  
        co_return;
475  
        co_return;
468  
    else
476  
    else
469  
        co_return detail::extract_results(state);
477  
        co_return detail::extract_results(state);
470  
}
478  
}
471  

479  

472  
/// Compute the result type of `when_all` for the given task types.
480  
/// Compute the result type of `when_all` for the given task types.
473  
template<typename... Ts>
481  
template<typename... Ts>
474  
using when_all_result_type = detail::when_all_result_t<Ts...>;
482  
using when_all_result_type = detail::when_all_result_t<Ts...>;
475  

483  

476  
} // namespace capy
484  
} // namespace capy
477  
} // namespace boost
485  
} // namespace boost
478  

486  

479  
#endif
487  
#endif