1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_WHEN_ALL_HPP
10  
#ifndef BOOST_CAPY_WHEN_ALL_HPP
11  
#define BOOST_CAPY_WHEN_ALL_HPP
11  
#define BOOST_CAPY_WHEN_ALL_HPP
12  

12  

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

21  

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

30  

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

33  

33  
namespace detail {
34  
namespace detail {
34 -
/** Type trait to filter void types from a tuple.
 
35 -

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

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

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

 
47  

35  

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

42  

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

47  

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

53  

66 -
/** Specialization for void tasks - no value storage needed.
54 +
/** Specialization for void tasks - returns monostate to preserve index mapping.
67  
*/
55  
*/
68  
template<>
56  
template<>
69  
struct result_holder<void>
57  
struct result_holder<void>
70  
{
58  
{
 
59 +
    std::monostate get() && { return {}; }
71  
};
60  
};
72  

61  

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

63  

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

70  

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

73  

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

76  

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

79  

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

83  

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

86  

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

95  

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

99  

111  
    when_all_state()
100  
    when_all_state()
112  
        : remaining_count_(task_count)
101  
        : remaining_count_(task_count)
113  
    {
102  
    {
114  
    }
103  
    }
115  

104  

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

106  

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

116  

128  
};
117  
};
129  

118  

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

120  

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

131  

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

136  

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

141  

153  
        auto final_suspend() noexcept
142  
        auto final_suspend() noexcept
154  
        {
143  
        {
155  
            struct awaiter
144  
            struct awaiter
156  
            {
145  
            {
157  
                promise_type* p_;
146  
                promise_type* p_;
158  

147  

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

152  

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

160  

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

162  

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

169  

181  
                void await_resume() const noexcept
170  
                void await_resume() const noexcept
182  
                {
171  
                {
183  
                }
172  
                }
184  
            };
173  
            };
185  
            return awaiter{this};
174  
            return awaiter{this};
186  
        }
175  
        }
187  

176  

188  
        void return_void()
177  
        void return_void()
189  
        {
178  
        {
190  
        }
179  
        }
191  

180  

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

187  

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

193  

205  
            bool await_ready()
194  
            bool await_ready()
206  
            {
195  
            {
207  
                return a_.await_ready();
196  
                return a_.await_ready();
208  
            }
197  
            }
209  

198  

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

203  

215  
            template<class Promise>
204  
            template<class Promise>
216  
            auto await_suspend(std::coroutine_handle<Promise> h)
205  
            auto await_suspend(std::coroutine_handle<Promise> h)
217  
            {
206  
            {
218  
                using R = decltype(a_.await_suspend(h, &p_->env_));
207  
                using R = decltype(a_.await_suspend(h, &p_->env_));
219  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
208  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
220  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
209  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
221  
                else
210  
                else
222  
                    return a_.await_suspend(h, &p_->env_);
211  
                    return a_.await_suspend(h, &p_->env_);
223  
            }
212  
            }
224  
        };
213  
        };
225  

214  

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

230  

242  
    std::coroutine_handle<promise_type> h_;
231  
    std::coroutine_handle<promise_type> h_;
243  

232  

244  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
233  
    explicit when_all_runner(std::coroutine_handle<promise_type> h)
245  
        : h_(h)
234  
        : h_(h)
246  
    {
235  
    {
247  
    }
236  
    }
248  

237  

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

240  

252  
    // Non-copyable
241  
    // Non-copyable
253  
    when_all_runner(when_all_runner const&) = delete;
242  
    when_all_runner(when_all_runner const&) = delete;
254  
    when_all_runner& operator=(when_all_runner const&) = delete;
243  
    when_all_runner& operator=(when_all_runner const&) = delete;
255  
    when_all_runner& operator=(when_all_runner&&) = delete;
244  
    when_all_runner& operator=(when_all_runner&&) = delete;
256  

245  

257  
    auto release() noexcept
246  
    auto release() noexcept
258  
    {
247  
    {
259  
        return std::exchange(h_, nullptr);
248  
        return std::exchange(h_, nullptr);
260  
    }
249  
    }
261  
};
250  
};
262  

251  

263  
/** Create a runner coroutine for a single awaitable.
252  
/** Create a runner coroutine for a single awaitable.
264  

253  

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

270  

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

272  

284  
    This awaitable is used inside the when_all coroutine to handle
273  
    This awaitable is used inside the when_all coroutine to handle
285  
    the concurrent execution of child awaitables.
274  
    the concurrent execution of child awaitables.
286  
*/
275  
*/
287  
template<IoAwaitable... Awaitables>
276  
template<IoAwaitable... Awaitables>
288  
class when_all_launcher
277  
class when_all_launcher
289  
{
278  
{
290  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
279  
    using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
291  

280  

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

283  

295  
public:
284  
public:
296  
    when_all_launcher(
285  
    when_all_launcher(
297  
        std::tuple<Awaitables...>* awaitables,
286  
        std::tuple<Awaitables...>* awaitables,
298  
        state_type* state)
287  
        state_type* state)
299  
        : awaitables_(awaitables)
288  
        : awaitables_(awaitables)
300  
        , state_(state)
289  
        , state_(state)
301  
    {
290  
    {
302  
    }
291  
    }
303  

292  

304  
    bool await_ready() const noexcept
293  
    bool await_ready() const noexcept
305  
    {
294  
    {
306  
        return sizeof...(Awaitables) == 0;
295  
        return sizeof...(Awaitables) == 0;
307  
    }
296  
    }
308  

297  

309  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
298  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
310  
    {
299  
    {
311  
        state_->continuation_ = continuation;
300  
        state_->continuation_ = continuation;
312  
        state_->caller_env_ = caller_env;
301  
        state_->caller_env_ = caller_env;
313  

302  

314  
        // Forward parent's stop requests to children
303  
        // Forward parent's stop requests to children
315  
        if(caller_env->stop_token.stop_possible())
304  
        if(caller_env->stop_token.stop_possible())
316  
        {
305  
        {
317  
            state_->parent_stop_callback_.emplace(
306  
            state_->parent_stop_callback_.emplace(
318  
                caller_env->stop_token,
307  
                caller_env->stop_token,
319  
                typename state_type::stop_callback_fn{&state_->stop_source_});
308  
                typename state_type::stop_callback_fn{&state_->stop_source_});
320  

309  

321  
            if(caller_env->stop_token.stop_requested())
310  
            if(caller_env->stop_token.stop_requested())
322  
                state_->stop_source_.request_stop();
311  
                state_->stop_source_.request_stop();
323  
        }
312  
        }
324  

313  

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

323  

335  
        // Let signal_completion() handle resumption
324  
        // Let signal_completion() handle resumption
336  
        return std::noop_coroutine();
325  
        return std::noop_coroutine();
337  
    }
326  
    }
338  

327  

339  
    void await_resume() const noexcept
328  
    void await_resume() const noexcept
340  
    {
329  
    {
341  
        // Results are extracted by the when_all coroutine from state
330  
        // Results are extracted by the when_all coroutine from state
342  
    }
331  
    }
343  

332  

344  
private:
333  
private:
345  
    template<std::size_t I>
334  
    template<std::size_t I>
346  
    void launch_one(executor_ref caller_ex, std::stop_token token)
335  
    void launch_one(executor_ref caller_ex, std::stop_token token)
347  
    {
336  
    {
348  
        auto runner = make_when_all_runner<I>(
337  
        auto runner = make_when_all_runner<I>(
349  
            std::move(std::get<I>(*awaitables_)), state_);
338  
            std::move(std::get<I>(*awaitables_)), state_);
350  

339  

351  
        auto h = runner.release();
340  
        auto h = runner.release();
352  
        h.promise().state_ = state_;
341  
        h.promise().state_ = state_;
353  
        h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->frame_allocator};
342  
        h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->frame_allocator};
354  

343  

355  
        std::coroutine_handle<> ch{h};
344  
        std::coroutine_handle<> ch{h};
356  
        state_->runner_handles_[I] = ch;
345  
        state_->runner_handles_[I] = ch;
357  
        state_->caller_env_->executor.post(ch);
346  
        state_->caller_env_->executor.post(ch);
358  
    }
347  
    }
359  
};
348  
};
360  

349  

361 -
/** Helper to extract a single result, returning empty tuple for void.
350 +
/** Helper to extract a single result from state.
362  
    This is a separate function to work around a GCC-11 ICE that occurs
351  
    This is a separate function to work around a GCC-11 ICE that occurs
363  
    when using nested immediately-invoked lambdas with pack expansion.
352  
    when using nested immediately-invoked lambdas with pack expansion.
364  
*/
353  
*/
365  
template<std::size_t I, typename... Ts>
354  
template<std::size_t I, typename... Ts>
366  
auto extract_single_result(when_all_state<Ts...>& state)
355  
auto extract_single_result(when_all_state<Ts...>& state)
367  
{
356  
{
368 -
    using T = std::tuple_element_t<I, std::tuple<Ts...>>;
357 +
    return std::move(std::get<I>(state.results_)).get();
369 -
    if constexpr (std::is_void_v<T>)
 
370 -
        return std::tuple<>();
 
371 -
    else
 
372 -
        return std::make_tuple(std::move(std::get<I>(state.results_)).get());
 
373  
}
358  
}
374  

359  

375 -
/** Extract results from state, filtering void types.
360 +
/** Extract all results from state as a tuple.
376  
*/
361  
*/
377  
template<typename... Ts>
362  
template<typename... Ts>
378  
auto extract_results(when_all_state<Ts...>& state)
363  
auto extract_results(when_all_state<Ts...>& state)
379  
{
364  
{
380  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
365  
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
381 -
        return std::tuple_cat(extract_single_result<Is>(state)...);
366 +
        return std::tuple(extract_single_result<Is>(state)...);
382  
    }(std::index_sequence_for<Ts...>{});
367  
    }(std::index_sequence_for<Ts...>{});
383  
}
368  
}
384  

369  

385  
} // namespace detail
370  
} // namespace detail
386  

371  

387 -
/** Compute a tuple type with void types filtered out.
372 +
/** Compute the when_all result tuple type.
388  

373  

389 -
    Returns void when all types are void (P2300 aligned),
374 +
    Void-returning tasks contribute std::monostate to preserve the
390 -
    otherwise returns a std::tuple with void types removed.
375 +
    task-index-to-result-index mapping, matching when_any's approach.
391  

376  

392 -
    Example: non_void_tuple_t<int, void, string> = std::tuple<int, string>
377 +
    Example: when_all_result_t<int, void, string> = std::tuple<int, std::monostate, string>
393 -
    Example: non_void_tuple_t<void, void> = void
378 +
    Example: when_all_result_t<void, void> = std::tuple<std::monostate, std::monostate>
394  
*/
379  
*/
395  
template<typename... Ts>
380  
template<typename... Ts>
396 -
using non_void_tuple_t = std::conditional_t<
381 +
using when_all_result_t = std::tuple<void_to_monostate_t<Ts>...>;
397 -
    std::is_same_v<detail::filter_void_tuple_t<Ts...>, std::tuple<>>,
 
398 -
    void,
 
399 -
    detail::filter_void_tuple_t<Ts...>>;
 
400  

382  

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

384  

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

389  

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

397  

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

402  

421  
    @param awaitables The awaitables to execute concurrently. Each must
403  
    @param awaitables The awaitables to execute concurrently. Each must
422  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
404  
        satisfy @ref IoAwaitable and is consumed (moved-from) when
423  
        `when_all` is awaited.
405  
        `when_all` is awaited.
424  

406  

425 -
    @return A task yielding a tuple of non-void results. Returns
407 +
    @return A task yielding a tuple of results in input order. Void tasks
426 -
        `task<void>` when all input awaitables return void.
408 +
        contribute std::monostate to preserve index correspondence.
427  

409  

428  
    @par Example
410  
    @par Example
429  

411  

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

420  

439 -
        // Void awaitables don't contribute to result
421 +
        // Void awaitables contribute monostate
440 -
        co_await when_all(
422 +
        auto [a, _, b] = co_await when_all(
441 -
            log_event( "start" ),  // task<void>
423 +
            fetch_int(),           // task<int>
442 -
            notify_user( id )      // task<void>
424 +
            log_event( "start" ),  // task<void>  → monostate
 
425 +
            fetch_str()            // task<string>
443  
        );
426  
        );
444 -
        // Returns task<void>, no result tuple
427 +
        // a is int, _ is monostate, b is string
445  
    }
428  
    }
446  
    @endcode
429  
    @endcode
447  

430  

448  
    @see IoAwaitable, task
431  
    @see IoAwaitable, task
449  
*/
432  
*/
450  
template<IoAwaitable... As>
433  
template<IoAwaitable... As>
451  
[[nodiscard]] auto when_all(As... awaitables)
434  
[[nodiscard]] auto when_all(As... awaitables)
452 -
    -> task<non_void_tuple_t<awaitable_result_t<As>...>>
435 +
    -> task<when_all_result_t<awaitable_result_t<As>...>>
453 -
    using result_type = non_void_tuple_t<awaitable_result_t<As>...>;
 
454 -

 
455  
{
436  
{
456  
    // State is stored in the coroutine frame, using the frame allocator
437  
    // State is stored in the coroutine frame, using the frame allocator
457  
    detail::when_all_state<awaitable_result_t<As>...> state;
438  
    detail::when_all_state<awaitable_result_t<As>...> state;
458  

439  

459  
    // Store awaitables in the frame
440  
    // Store awaitables in the frame
460  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
441  
    std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
461  

442  

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

445  

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

452  

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

455  

479  
} // namespace capy
456  
} // namespace capy
480  
} // namespace boost
457  
} // namespace boost
481  

458  

482  
#endif
459  
#endif