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_launchable_task.hpp>
15  
#include <boost/capy/concept/io_launchable_task.hpp>
16  
#include <boost/capy/coro.hpp>
16  
#include <boost/capy/coro.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.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<coro, task_count> runner_handles_{};
89  
    std::array<coro, 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  
    coro continuation_;
108  
    coro continuation_;
109  
    executor_ref caller_ex_;
109  
    executor_ref caller_ex_;
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  
    ~when_all_state()
116  
    ~when_all_state()
117  
    {
117  
    {
118  
        for(auto h : runner_handles_)
118  
        for(auto h : runner_handles_)
119  
            if(h)
119  
            if(h)
120  
                h.destroy();
120  
                h.destroy();
121  
    }
121  
    }
122  

122  

123  
    /** Capture an exception (first one wins).
123  
    /** Capture an exception (first one wins).
124  
    */
124  
    */
125  
    void capture_exception(std::exception_ptr ep)
125  
    void capture_exception(std::exception_ptr ep)
126  
    {
126  
    {
127  
        bool expected = false;
127  
        bool expected = false;
128  
        if(has_exception_.compare_exchange_strong(
128  
        if(has_exception_.compare_exchange_strong(
129  
            expected, true, std::memory_order_relaxed))
129  
            expected, true, std::memory_order_relaxed))
130  
            first_exception_ = ep;
130  
            first_exception_ = ep;
131  
    }
131  
    }
132  

132  

133  
    /** Signal that a task has completed.
133  
    /** Signal that a task has completed.
134  

134  

135  
        The last child to complete triggers resumption of the parent.
135  
        The last child to complete triggers resumption of the parent.
136  
        Dispatch handles thread affinity: resumes inline if on same
136  
        Dispatch handles thread affinity: resumes inline if on same
137  
        thread, otherwise posts to the caller's executor.
137  
        thread, otherwise posts to the caller's executor.
138  
    */
138  
    */
139  
    coro signal_completion()
139  
    coro signal_completion()
140  
    {
140  
    {
141  
        auto remaining = remaining_count_.fetch_sub(1, std::memory_order_acq_rel);
141  
        auto remaining = remaining_count_.fetch_sub(1, std::memory_order_acq_rel);
142  
        if(remaining == 1)
142  
        if(remaining == 1)
143  
            caller_ex_.dispatch(continuation_);
143  
            caller_ex_.dispatch(continuation_);
144  
        return std::noop_coroutine();
144  
        return std::noop_coroutine();
145  
    }
145  
    }
146  

146  

147  
};
147  
};
148  

148  

149  
/** Wrapper coroutine that intercepts task completion.
149  
/** Wrapper coroutine that intercepts task completion.
150  

150  

151  
    This runner awaits its assigned task and stores the result in
151  
    This runner awaits its assigned task and stores the result in
152  
    the shared state, or captures the exception and requests stop.
152  
    the shared state, or captures the exception and requests stop.
153  
*/
153  
*/
154  
template<typename T, typename... Ts>
154  
template<typename T, typename... Ts>
155  
struct when_all_runner
155  
struct when_all_runner
156  
{
156  
{
157  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
157  
    struct promise_type // : frame_allocating_base  // DISABLED FOR TESTING
158  
    {
158  
    {
159  
        when_all_state<Ts...>* state_ = nullptr;
159  
        when_all_state<Ts...>* state_ = nullptr;
160  
        executor_ref ex_;
160  
        executor_ref ex_;
161  
        std::stop_token stop_token_;
161  
        std::stop_token stop_token_;
162  

162  

163  
        when_all_runner get_return_object()
163  
        when_all_runner get_return_object()
164  
        {
164  
        {
165  
            return when_all_runner(std::coroutine_handle<promise_type>::from_promise(*this));
165  
            return when_all_runner(std::coroutine_handle<promise_type>::from_promise(*this));
166  
        }
166  
        }
167  

167  

168  
        std::suspend_always initial_suspend() noexcept
168  
        std::suspend_always initial_suspend() noexcept
169  
        {
169  
        {
170  
            return {};
170  
            return {};
171  
        }
171  
        }
172  

172  

173  
        auto final_suspend() noexcept
173  
        auto final_suspend() noexcept
174  
        {
174  
        {
175  
            struct awaiter
175  
            struct awaiter
176  
            {
176  
            {
177  
                promise_type* p_;
177  
                promise_type* p_;
178  

178  

179  
                bool await_ready() const noexcept
179  
                bool await_ready() const noexcept
180  
                {
180  
                {
181  
                    return false;
181  
                    return false;
182  
                }
182  
                }
183  

183  

184  
                coro await_suspend(coro) noexcept
184  
                coro await_suspend(coro) noexcept
185  
                {
185  
                {
186  
                    // Signal completion; last task resumes parent
186  
                    // Signal completion; last task resumes parent
187  
                    return p_->state_->signal_completion();
187  
                    return p_->state_->signal_completion();
188  
                }
188  
                }
189  

189  

190  
                void await_resume() const noexcept
190  
                void await_resume() const noexcept
191  
                {
191  
                {
192  
                }
192  
                }
193  
            };
193  
            };
194  
            return awaiter{this};
194  
            return awaiter{this};
195  
        }
195  
        }
196  

196  

197  
        void return_void()
197  
        void return_void()
198  
        {
198  
        {
199  
        }
199  
        }
200  

200  

201  
        void unhandled_exception()
201  
        void unhandled_exception()
202  
        {
202  
        {
203  
            state_->capture_exception(std::current_exception());
203  
            state_->capture_exception(std::current_exception());
204  
            // Request stop for sibling tasks
204  
            // Request stop for sibling tasks
205  
            state_->stop_source_.request_stop();
205  
            state_->stop_source_.request_stop();
206  
        }
206  
        }
207  

207  

208  
        template<class Awaitable>
208  
        template<class Awaitable>
209  
        struct transform_awaiter
209  
        struct transform_awaiter
210  
        {
210  
        {
211  
            std::decay_t<Awaitable> a_;
211  
            std::decay_t<Awaitable> a_;
212  
            promise_type* p_;
212  
            promise_type* p_;
213  

213  

214  
            bool await_ready()
214  
            bool await_ready()
215  
            {
215  
            {
216  
                return a_.await_ready();
216  
                return a_.await_ready();
217  
            }
217  
            }
218  

218  

219  
            decltype(auto) await_resume()
219  
            decltype(auto) await_resume()
220  
            {
220  
            {
221  
                return a_.await_resume();
221  
                return a_.await_resume();
222  
            }
222  
            }
223  

223  

224  
            template<class Promise>
224  
            template<class Promise>
225  
            auto await_suspend(std::coroutine_handle<Promise> h)
225  
            auto await_suspend(std::coroutine_handle<Promise> h)
226  
            {
226  
            {
227  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
227  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
228  
            }
228  
            }
229  
        };
229  
        };
230  

230  

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

246  

247  
    std::coroutine_handle<promise_type> h_;
247  
    std::coroutine_handle<promise_type> h_;
248  

248  

249  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
249  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
250  
        : h_(h)
250  
        : h_(h)
251  
    {
251  
    {
252  
    }
252  
    }
253  

253  

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

256  

257  
    // Non-copyable
257  
    // Non-copyable
258  
    when_all_runner(when_all_runner const&) = delete;
258  
    when_all_runner(when_all_runner const&) = delete;
259  
    when_all_runner& operator=(when_all_runner const&) = delete;
259  
    when_all_runner& operator=(when_all_runner const&) = delete;
260  
    when_all_runner& operator=(when_all_runner&&) = delete;
260  
    when_all_runner& operator=(when_all_runner&&) = delete;
261  

261  

262  
    auto release() noexcept
262  
    auto release() noexcept
263  
    {
263  
    {
264  
        return std::exchange(h_, nullptr);
264  
        return std::exchange(h_, nullptr);
265  
    }
265  
    }
266  
};
266  
};
267  

267  

268  
/** Create a runner coroutine for a single task.
268  
/** Create a runner coroutine for a single task.
269  

269  

270  
    Task is passed directly to ensure proper coroutine frame storage.
270  
    Task is passed directly to ensure proper coroutine frame storage.
271  
*/
271  
*/
272  
template<std::size_t Index, typename T, typename... Ts>
272  
template<std::size_t Index, typename T, typename... Ts>
273  
when_all_runner<T, Ts...>
273  
when_all_runner<T, Ts...>
274  
make_when_all_runner(task<T> inner, when_all_state<Ts...>* state)
274  
make_when_all_runner(task<T> inner, when_all_state<Ts...>* state)
275  
{
275  
{
276  
    if constexpr (std::is_void_v<T>)
276  
    if constexpr (std::is_void_v<T>)
277  
    {
277  
    {
278  
        co_await std::move(inner);
278  
        co_await std::move(inner);
279  
    }
279  
    }
280  
    else
280  
    else
281  
    {
281  
    {
282  
        std::get<Index>(state->results_).set(co_await std::move(inner));
282  
        std::get<Index>(state->results_).set(co_await std::move(inner));
283  
    }
283  
    }
284  
}
284  
}
285  

285  

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

287  

288  
    This awaitable is used inside the when_all coroutine to handle
288  
    This awaitable is used inside the when_all coroutine to handle
289  
    the concurrent execution of child tasks.
289  
    the concurrent execution of child tasks.
290  
*/
290  
*/
291  
template<typename... Ts>
291  
template<typename... Ts>
292  
class when_all_launcher
292  
class when_all_launcher
293  
{
293  
{
294  
    std::tuple<task<Ts>...>* tasks_;
294  
    std::tuple<task<Ts>...>* tasks_;
295  
    when_all_state<Ts...>* state_;
295  
    when_all_state<Ts...>* state_;
296  

296  

297  
public:
297  
public:
298  
    when_all_launcher(
298  
    when_all_launcher(
299  
        std::tuple<task<Ts>...>* tasks,
299  
        std::tuple<task<Ts>...>* tasks,
300  
        when_all_state<Ts...>* state)
300  
        when_all_state<Ts...>* state)
301  
        : tasks_(tasks)
301  
        : tasks_(tasks)
302  
        , state_(state)
302  
        , state_(state)
303  
    {
303  
    {
304  
    }
304  
    }
305  

305  

306  
    bool await_ready() const noexcept
306  
    bool await_ready() const noexcept
307  
    {
307  
    {
308  
        return sizeof...(Ts) == 0;
308  
        return sizeof...(Ts) == 0;
309  
    }
309  
    }
310  

310  

311  
    coro await_suspend(coro continuation, executor_ref caller_ex, std::stop_token parent_token = {})
311  
    coro await_suspend(coro continuation, executor_ref caller_ex, std::stop_token parent_token = {})
312  
    {
312  
    {
313  
        state_->continuation_ = continuation;
313  
        state_->continuation_ = continuation;
314  
        state_->caller_ex_ = caller_ex;
314  
        state_->caller_ex_ = caller_ex;
315  

315  

316  
        // Forward parent's stop requests to children
316  
        // Forward parent's stop requests to children
317  
        if(parent_token.stop_possible())
317  
        if(parent_token.stop_possible())
318  
        {
318  
        {
319  
            state_->parent_stop_callback_.emplace(
319  
            state_->parent_stop_callback_.emplace(
320  
                parent_token,
320  
                parent_token,
321  
                typename when_all_state<Ts...>::stop_callback_fn{&state_->stop_source_});
321  
                typename when_all_state<Ts...>::stop_callback_fn{&state_->stop_source_});
322  

322  

323  
            if(parent_token.stop_requested())
323  
            if(parent_token.stop_requested())
324  
                state_->stop_source_.request_stop();
324  
                state_->stop_source_.request_stop();
325  
        }
325  
        }
326  

326  

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

336  

337  
        // Let signal_completion() handle resumption
337  
        // Let signal_completion() handle resumption
338  
        return std::noop_coroutine();
338  
        return std::noop_coroutine();
339  
    }
339  
    }
340  

340  

341  
    void await_resume() const noexcept
341  
    void await_resume() const noexcept
342  
    {
342  
    {
343  
        // Results are extracted by the when_all coroutine from state
343  
        // Results are extracted by the when_all coroutine from state
344  
    }
344  
    }
345  

345  

346  
private:
346  
private:
347  
    template<std::size_t I>
347  
    template<std::size_t I>
348  
    void launch_one(executor_ref caller_ex, std::stop_token token)
348  
    void launch_one(executor_ref caller_ex, std::stop_token token)
349  
    {
349  
    {
350  
        auto runner = make_when_all_runner<I>(
350  
        auto runner = make_when_all_runner<I>(
351  
            std::move(std::get<I>(*tasks_)), state_);
351  
            std::move(std::get<I>(*tasks_)), state_);
352  

352  

353  
        auto h = runner.release();
353  
        auto h = runner.release();
354  
        h.promise().state_ = state_;
354  
        h.promise().state_ = state_;
355  
        h.promise().ex_ = caller_ex;
355  
        h.promise().ex_ = caller_ex;
356  
        h.promise().stop_token_ = token;
356  
        h.promise().stop_token_ = token;
357  

357  

358  
        coro ch{h};
358  
        coro ch{h};
359  
        state_->runner_handles_[I] = ch;
359  
        state_->runner_handles_[I] = ch;
360  
        state_->caller_ex_.dispatch(ch);
360  
        state_->caller_ex_.dispatch(ch);
361  
    }
361  
    }
362  
};
362  
};
363  

363  

364  
/** Compute the result type for when_all.
364  
/** Compute the result type for when_all.
365  

365  

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

374  

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

388  

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

398  

399  
} // namespace detail
399  
} // namespace detail
400  

400  

401  
/** Execute multiple tasks concurrently and collect their results.
401  
/** Execute multiple tasks concurrently and collect their results.
402  

402  

403  
    Launches all tasks simultaneously and waits for all to complete
403  
    Launches all tasks simultaneously and waits for all to complete
404  
    before returning. Results are collected in input order. If any
404  
    before returning. Results are collected in input order. If any
405  
    task throws, cancellation is requested for siblings and the first
405  
    task throws, cancellation is requested for siblings and the first
406  
    exception is rethrown after all tasks complete.
406  
    exception is rethrown after all tasks complete.
407  

407  

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

415  

416  
    @par Thread Safety
416  
    @par Thread Safety
417  
    The returned task must be awaited from a single execution context.
417  
    The returned task must be awaited from a single execution context.
418  
    Child tasks execute concurrently but complete through the caller's
418  
    Child tasks execute concurrently but complete through the caller's
419  
    executor.
419  
    executor.
420  

420  

421  
    @param tasks The tasks to execute concurrently. Each task is
421  
    @param tasks The tasks to execute concurrently. Each task is
422  
        consumed (moved-from) when `when_all` is awaited.
422  
        consumed (moved-from) when `when_all` is awaited.
423  

423  

424  
    @return A task yielding a tuple of non-void results. Returns
424  
    @return A task yielding a tuple of non-void results. Returns
425  
        `task<void>` when all input tasks return void.
425  
        `task<void>` when all input tasks return void.
426  

426  

427  
    @par Example
427  
    @par Example
428  

428  

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

437  

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

446  

447  
    @see task
447  
    @see task
448  
*/
448  
*/
449  
template<typename... Ts>
449  
template<typename... Ts>
450  
[[nodiscard]] task<detail::when_all_result_t<Ts...>>
450  
[[nodiscard]] task<detail::when_all_result_t<Ts...>>
451  
when_all(task<Ts>... tasks)
451  
when_all(task<Ts>... tasks)
452  
{
452  
{
453  
    using result_type = detail::when_all_result_t<Ts...>;
453  
    using result_type = detail::when_all_result_t<Ts...>;
454  

454  

455  
    // State is stored in the coroutine frame, using the frame allocator
455  
    // State is stored in the coroutine frame, using the frame allocator
456  
    detail::when_all_state<Ts...> state;
456  
    detail::when_all_state<Ts...> state;
457  

457  

458  
    // Store tasks in the frame
458  
    // Store tasks in the frame
459  
    std::tuple<task<Ts>...> task_tuple(std::move(tasks)...);
459  
    std::tuple<task<Ts>...> task_tuple(std::move(tasks)...);
460  

460  

461  
    // Launch all tasks and wait for completion
461  
    // Launch all tasks and wait for completion
462  
    co_await detail::when_all_launcher<Ts...>(&task_tuple, &state);
462  
    co_await detail::when_all_launcher<Ts...>(&task_tuple, &state);
463  

463  

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

470  

471  
    // Extract and return results
471  
    // Extract and return results
472  
    if constexpr (std::is_void_v<result_type>)
472  
    if constexpr (std::is_void_v<result_type>)
473  
        co_return;
473  
        co_return;
474  
    else
474  
    else
475  
        co_return detail::extract_results(state);
475  
        co_return detail::extract_results(state);
476  
}
476  
}
477  

477  

478  
/// Compute the result type of `when_all` for the given task types.
478  
/// Compute the result type of `when_all` for the given task types.
479  
template<typename... Ts>
479  
template<typename... Ts>
480  
using when_all_result_type = detail::when_all_result_t<Ts...>;
480  
using when_all_result_type = detail::when_all_result_t<Ts...>;
481  

481  

482  
} // namespace capy
482  
} // namespace capy
483  
} // namespace boost
483  
} // namespace boost
484  

484  

485  
#endif
485  
#endif