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_awaitable.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  
    // 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  
        executor_ref ex_;
141  
        executor_ref ex_;
142  
        std::stop_token stop_token_;
142  
        std::stop_token stop_token_;
143  

143  

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

148  

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

153  

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

159  

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

164  

165  
                void await_suspend(coro h) noexcept
165  
                void await_suspend(coro h) noexcept
166  
                {
166  
                {
167  
                    // Extract everything needed for signaling before
167  
                    // Extract everything needed for signaling before
168  
                    // self-destruction. Inline dispatch may destroy
168  
                    // self-destruction. Inline dispatch may destroy
169  
                    // when_all_state, so we can't access members after.
169  
                    // when_all_state, so we can't access members after.
170  
                    auto* state = p_->state_;
170  
                    auto* state = p_->state_;
171  
                    auto* counter = &state->remaining_count_;
171  
                    auto* counter = &state->remaining_count_;
172  
                    auto caller_ex = state->caller_ex_;
172  
                    auto caller_ex = state->caller_ex_;
173  
                    auto cont = state->continuation_;
173  
                    auto cont = state->continuation_;
174  

174  

175  
                    // Self-destruct first - state no longer destroys runners
175  
                    // Self-destruct first - state no longer destroys runners
176  
                    h.destroy();
176  
                    h.destroy();
177  

177  

178  
                    // Signal completion. If last, dispatch parent.
178  
                    // Signal completion. If last, dispatch parent.
179  
                    // Uses only local copies - safe even if state
179  
                    // Uses only local copies - safe even if state
180  
                    // is destroyed during inline dispatch.
180  
                    // is destroyed during inline dispatch.
181  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
181  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
182  
                    if(remaining == 1)
182  
                    if(remaining == 1)
183  
                        caller_ex.dispatch(cont);
183  
                        caller_ex.dispatch(cont);
184  
                }
184  
                }
185  

185  

186  
                void await_resume() const noexcept
186  
                void await_resume() const noexcept
187  
                {
187  
                {
188  
                }
188  
                }
189  
            };
189  
            };
190  
            return awaiter{this};
190  
            return awaiter{this};
191  
        }
191  
        }
192  

192  

193  
        void return_void()
193  
        void return_void()
194  
        {
194  
        {
195  
        }
195  
        }
196  

196  

197  
        void unhandled_exception()
197  
        void unhandled_exception()
198  
        {
198  
        {
199  
            state_->capture_exception(std::current_exception());
199  
            state_->capture_exception(std::current_exception());
200  
            // Request stop for sibling tasks
200  
            // Request stop for sibling tasks
201  
            state_->stop_source_.request_stop();
201  
            state_->stop_source_.request_stop();
202  
        }
202  
        }
203  

203  

204  
        template<class Awaitable>
204  
        template<class Awaitable>
205  
        struct transform_awaiter
205  
        struct transform_awaiter
206  
        {
206  
        {
207  
            std::decay_t<Awaitable> a_;
207  
            std::decay_t<Awaitable> a_;
208  
            promise_type* p_;
208  
            promise_type* p_;
209  

209  

210  
            bool await_ready()
210  
            bool await_ready()
211  
            {
211  
            {
212  
                return a_.await_ready();
212  
                return a_.await_ready();
213  
            }
213  
            }
214  

214  

215  
            decltype(auto) await_resume()
215  
            decltype(auto) await_resume()
216  
            {
216  
            {
217  
                return a_.await_resume();
217  
                return a_.await_resume();
218  
            }
218  
            }
219  

219  

220  
            template<class Promise>
220  
            template<class Promise>
221  
            auto await_suspend(std::coroutine_handle<Promise> h)
221  
            auto await_suspend(std::coroutine_handle<Promise> h)
222  
            {
222  
            {
223  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
223  
                return a_.await_suspend(h, p_->ex_, p_->stop_token_);
224  
            }
224  
            }
225  
        };
225  
        };
226  

226  

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

242  

243  
    std::coroutine_handle<promise_type> h_;
243  
    std::coroutine_handle<promise_type> h_;
244  

244  

245  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
245  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
246  
        : h_(h)
246  
        : h_(h)
247  
    {
247  
    {
248  
    }
248  
    }
249  

249  

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

252  

253  
    // Non-copyable
253  
    // Non-copyable
254  
    when_all_runner(when_all_runner const&) = delete;
254  
    when_all_runner(when_all_runner const&) = delete;
255  
    when_all_runner& operator=(when_all_runner const&) = delete;
255  
    when_all_runner& operator=(when_all_runner const&) = delete;
256  
    when_all_runner& operator=(when_all_runner&&) = delete;
256  
    when_all_runner& operator=(when_all_runner&&) = delete;
257  

257  

258  
    auto release() noexcept
258  
    auto release() noexcept
259  
    {
259  
    {
260  
        return std::exchange(h_, nullptr);
260  
        return std::exchange(h_, nullptr);
261  
    }
261  
    }
262  
};
262  
};
263  

263  

264 -
/** Create a runner coroutine for a single task.
264 +
/** Create a runner coroutine for a single awaitable.
265  

265  

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

282  

282  
/** Internal awaitable that launches all runner coroutines and waits.
283  
/** Internal awaitable that launches all runner coroutines and waits.
283  

284  

284  
    This awaitable is used inside the when_all coroutine to handle
285  
    This awaitable is used inside the when_all coroutine to handle
285 -
    the concurrent execution of child tasks.
286 +
    the concurrent execution of child awaitables.
286  
*/
287  
*/
287 -
template<typename... Ts>
288 +
template<IoAwaitable... Awaitables>
288  
class when_all_launcher
289  
class when_all_launcher
289  
{
290  
{
290 -
    std::tuple<task<Ts>...>* tasks_;
291 +
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
291 -
    when_all_state<Ts...>* state_;
292 +

 
293 +
    std::tuple<Awaitables...>* awaitables_;
 
294 +
    state_type* state_;
292  

295  

293  
public:
296  
public:
294  
    when_all_launcher(
297  
    when_all_launcher(
295 -
        std::tuple<task<Ts>...>* tasks,
298 +
        std::tuple<Awaitables...>* awaitables,
296 -
        when_all_state<Ts...>* state)
299 +
        state_type* state)
297 -
        : tasks_(tasks)
300 +
        : awaitables_(awaitables)
298  
        , state_(state)
301  
        , state_(state)
299  
    {
302  
    {
300  
    }
303  
    }
301  

304  

302  
    bool await_ready() const noexcept
305  
    bool await_ready() const noexcept
303  
    {
306  
    {
304 -
        return sizeof...(Ts) == 0;
307 +
        return sizeof...(Awaitables) == 0;
305  
    }
308  
    }
306  

309  

307  
    coro await_suspend(coro continuation, executor_ref const& caller_ex, std::stop_token const& parent_token = {})
310  
    coro await_suspend(coro continuation, executor_ref const& caller_ex, std::stop_token const& parent_token = {})
308  
    {
311  
    {
309  
        state_->continuation_ = continuation;
312  
        state_->continuation_ = continuation;
310  
        state_->caller_ex_ = caller_ex;
313  
        state_->caller_ex_ = caller_ex;
311  

314  

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

321  

319  
            if(parent_token.stop_requested())
322  
            if(parent_token.stop_requested())
320  
                state_->stop_source_.request_stop();
323  
                state_->stop_source_.request_stop();
321  
        }
324  
        }
322  

325  

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

335  

333  
        // Let signal_completion() handle resumption
336  
        // Let signal_completion() handle resumption
334  
        return std::noop_coroutine();
337  
        return std::noop_coroutine();
335  
    }
338  
    }
336  

339  

337  
    void await_resume() const noexcept
340  
    void await_resume() const noexcept
338  
    {
341  
    {
339  
        // Results are extracted by the when_all coroutine from state
342  
        // Results are extracted by the when_all coroutine from state
340  
    }
343  
    }
341  

344  

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

351  

349  
        auto h = runner.release();
352  
        auto h = runner.release();
350  
        h.promise().state_ = state_;
353  
        h.promise().state_ = state_;
351  
        h.promise().ex_ = caller_ex;
354  
        h.promise().ex_ = caller_ex;
352  
        h.promise().stop_token_ = token;
355  
        h.promise().stop_token_ = token;
353  

356  

354  
        coro ch{h};
357  
        coro ch{h};
355  
        state_->runner_handles_[I] = ch;
358  
        state_->runner_handles_[I] = ch;
356  
        state_->caller_ex_.dispatch(ch);
359  
        state_->caller_ex_.dispatch(ch);
357  
    }
360  
    }
358  
};
361  
};
359  

362  

360  
/** Compute the result type for when_all.
363  
/** Compute the result type for when_all.
361  

364  

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

373  

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

387  

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

397  

395  
} // namespace detail
398  
} // namespace detail
396  

399  

397 -
/** Execute multiple tasks concurrently and collect their results.
400 +
/** Execute multiple awaitables concurrently and collect their results.
398  

401  

399 -
    Launches all tasks simultaneously and waits for all to complete
402 +
    Launches all awaitables simultaneously and waits for all to complete
400  
    before returning. Results are collected in input order. If any
403  
    before returning. Results are collected in input order. If any
401 -
    task throws, cancellation is requested for siblings and the first
404 +
    awaitable throws, cancellation is requested for siblings and the first
402 -
    exception is rethrown after all tasks complete.
405 +
    exception is rethrown after all awaitables complete.
403  

406  

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

414  

412  
    @par Thread Safety
415  
    @par Thread Safety
413  
    The returned task must be awaited from a single execution context.
416  
    The returned task must be awaited from a single execution context.
414 -
    Child tasks execute concurrently but complete through the caller's
417 +
    Child awaitables execute concurrently but complete through the caller's
415  
    executor.
418  
    executor.
416  

419  

417 -
    @param tasks The tasks to execute concurrently. Each task is
420 +
    @param awaitables The awaitables to execute concurrently. Each must
418 -
        consumed (moved-from) when `when_all` is awaited.
421 +
        satisfy @ref IoAwaitable and is consumed (moved-from) when
 
422 +
        `when_all` is awaited.
419  

423  

420  
    @return A task yielding a tuple of non-void results. Returns
424  
    @return A task yielding a tuple of non-void results. Returns
421 -
        `task<void>` when all input tasks return void.
425 +
        `task<void>` when all input awaitables return void.
422  

426  

423  
    @par Example
427  
    @par Example
424  

428  

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

437  

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

446  

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

454  

451  
    // State is stored in the coroutine frame, using the frame allocator
455  
    // State is stored in the coroutine frame, using the frame allocator
452 -
    detail::when_all_state<Ts...> state;
456 +
    detail::when_all_state<detail::awaitable_result_t<As>...> state;
453  

457  

454 -
    // Store tasks in the frame
458 +
    // Store awaitables in the frame
455 -
    std::tuple<task<Ts>...> task_tuple(std::move(tasks)...);
459 +
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
456  

460  

457 -
    // Launch all tasks and wait for completion
461 +
    // Launch all awaitables and wait for completion
458 -
    co_await detail::when_all_launcher<Ts...>(&task_tuple, &state);
462 +
    co_await detail::when_all_launcher<As...>(&awaitable_tuple, &state);
459  

463  

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

470  

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

477  

474  
/// 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.
475  
template<typename... Ts>
479  
template<typename... Ts>
476  
using when_all_result_type = detail::when_all_result_t<Ts...>;
480  
using when_all_result_type = detail::when_all_result_t<Ts...>;
477  

481  

478  
} // namespace capy
482  
} // namespace capy
479  
} // namespace boost
483  
} // namespace boost
480  

484  

481  
#endif
485  
#endif