1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
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_EX_IO_AWAITABLE_SUPPORT_HPP
10  
#ifndef BOOST_CAPY_EX_IO_AWAITABLE_SUPPORT_HPP
11  
#define BOOST_CAPY_EX_IO_AWAITABLE_SUPPORT_HPP
11  
#define BOOST_CAPY_EX_IO_AWAITABLE_SUPPORT_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/coro.hpp>
14  
#include <boost/capy/coro.hpp>
15  
#include <boost/capy/ex/executor_ref.hpp>
15  
#include <boost/capy/ex/executor_ref.hpp>
16  
#include <boost/capy/ex/frame_allocator.hpp>
16  
#include <boost/capy/ex/frame_allocator.hpp>
17  
#include <boost/capy/ex/this_coro.hpp>
17  
#include <boost/capy/ex/this_coro.hpp>
18  

18  

19  
#include <coroutine>
19  
#include <coroutine>
20  
#include <cstddef>
20  
#include <cstddef>
21  
#include <memory_resource>
21  
#include <memory_resource>
22  
#include <stop_token>
22  
#include <stop_token>
23  
#include <type_traits>
23  
#include <type_traits>
24  

24  

25  
namespace boost {
25  
namespace boost {
26  
namespace capy {
26  
namespace capy {
27  

27  

28  
/** CRTP mixin that adds I/O awaitable support to a promise type.
28  
/** CRTP mixin that adds I/O awaitable support to a promise type.
29  

29  

30  
    Inherit from this class to enable these capabilities in your coroutine:
30  
    Inherit from this class to enable these capabilities in your coroutine:
31  

31  

32  
    1. **Frame allocation** — The mixin provides `operator new/delete` that
32  
    1. **Frame allocation** — The mixin provides `operator new/delete` that
33  
       use the thread-local frame allocator set by `run_async`.
33  
       use the thread-local frame allocator set by `run_async`.
34  

34  

35  
    2. **Frame allocator storage** — The mixin stores the allocator pointer
35  
    2. **Frame allocator storage** — The mixin stores the allocator pointer
36  
       for propagation to child tasks.
36  
       for propagation to child tasks.
37  

37  

38  
    3. **Stop token storage** — The mixin stores the `std::stop_token`
38  
    3. **Stop token storage** — The mixin stores the `std::stop_token`
39  
       that was passed when your coroutine was awaited.
39  
       that was passed when your coroutine was awaited.
40  

40  

41  
    4. **Stop token access** — Coroutine code can retrieve the token via
41  
    4. **Stop token access** — Coroutine code can retrieve the token via
42  
       `co_await this_coro::stop_token`.
42  
       `co_await this_coro::stop_token`.
43  

43  

44  
    5. **Executor storage** — The mixin stores the `executor_ref`
44  
    5. **Executor storage** — The mixin stores the `executor_ref`
45  
       that this coroutine is bound to.
45  
       that this coroutine is bound to.
46  

46  

47  
    6. **Executor access** — Coroutine code can retrieve the executor via
47  
    6. **Executor access** — Coroutine code can retrieve the executor via
48  
       `co_await this_coro::executor`.
48  
       `co_await this_coro::executor`.
49  

49  

50  
    @tparam Derived The derived promise type (CRTP pattern).
50  
    @tparam Derived The derived promise type (CRTP pattern).
51  

51  

52  
    @par Basic Usage
52  
    @par Basic Usage
53  

53  

54  
    For coroutines that need to access their stop token or executor:
54  
    For coroutines that need to access their stop token or executor:
55  

55  

56  
    @code
56  
    @code
57  
    struct my_task
57  
    struct my_task
58  
    {
58  
    {
59  
        struct promise_type : io_awaitable_support<promise_type>
59  
        struct promise_type : io_awaitable_support<promise_type>
60  
        {
60  
        {
61  
            my_task get_return_object();
61  
            my_task get_return_object();
62  
            std::suspend_always initial_suspend() noexcept;
62  
            std::suspend_always initial_suspend() noexcept;
63  
            std::suspend_always final_suspend() noexcept;
63  
            std::suspend_always final_suspend() noexcept;
64  
            void return_void();
64  
            void return_void();
65  
            void unhandled_exception();
65  
            void unhandled_exception();
66  
        };
66  
        };
67  

67  

68  
        // ... awaitable interface ...
68  
        // ... awaitable interface ...
69  
    };
69  
    };
70  

70  

71  
    my_task example()
71  
    my_task example()
72  
    {
72  
    {
73  
        auto token = co_await this_coro::stop_token;
73  
        auto token = co_await this_coro::stop_token;
74  
        auto ex = co_await this_coro::executor;
74  
        auto ex = co_await this_coro::executor;
75  
        // Use token and ex...
75  
        // Use token and ex...
76  
    }
76  
    }
77  
    @endcode
77  
    @endcode
78  

78  

79  
    @par Custom Awaitable Transformation
79  
    @par Custom Awaitable Transformation
80  

80  

81  
    If your promise needs to transform awaitables (e.g., for affinity or
81  
    If your promise needs to transform awaitables (e.g., for affinity or
82  
    logging), override `transform_awaitable` instead of `await_transform`:
82  
    logging), override `transform_awaitable` instead of `await_transform`:
83  

83  

84  
    @code
84  
    @code
85  
    struct promise_type : io_awaitable_support<promise_type>
85  
    struct promise_type : io_awaitable_support<promise_type>
86  
    {
86  
    {
87  
        template<typename A>
87  
        template<typename A>
88  
        auto transform_awaitable(A&& a)
88  
        auto transform_awaitable(A&& a)
89  
        {
89  
        {
90  
            // Your custom transformation logic
90  
            // Your custom transformation logic
91  
            return std::forward<A>(a);
91  
            return std::forward<A>(a);
92  
        }
92  
        }
93  
    };
93  
    };
94  
    @endcode
94  
    @endcode
95  

95  

96  
    The mixin's `await_transform` intercepts @ref this_coro::stop_token_tag and
96  
    The mixin's `await_transform` intercepts @ref this_coro::stop_token_tag and
97  
    @ref this_coro::executor_tag, then delegates all other awaitables to your
97  
    @ref this_coro::executor_tag, then delegates all other awaitables to your
98  
    `transform_awaitable`.
98  
    `transform_awaitable`.
99  

99  

100  
    @par Making Your Coroutine an IoAwaitable
100  
    @par Making Your Coroutine an IoAwaitable
101  

101  

102  
    The mixin handles the "inside the coroutine" part—accessing the token
102  
    The mixin handles the "inside the coroutine" part—accessing the token
103  
    and executor. To receive these when your coroutine is awaited (satisfying
103  
    and executor. To receive these when your coroutine is awaited (satisfying
104  
    @ref IoAwaitable), implement the `await_suspend` overload on your
104  
    @ref IoAwaitable), implement the `await_suspend` overload on your
105  
    coroutine return type:
105  
    coroutine return type:
106  

106  

107  
    @code
107  
    @code
108  
    struct my_task
108  
    struct my_task
109  
    {
109  
    {
110  
        struct promise_type : io_awaitable_support<promise_type> { ... };
110  
        struct promise_type : io_awaitable_support<promise_type> { ... };
111  

111  

112  
        std::coroutine_handle<promise_type> h_;
112  
        std::coroutine_handle<promise_type> h_;
113  

113  

114  
        // IoAwaitable await_suspend receives and stores the token and executor
114  
        // IoAwaitable await_suspend receives and stores the token and executor
115  
        coro await_suspend(coro cont, executor_ref ex, std::stop_token token)
115  
        coro await_suspend(coro cont, executor_ref ex, std::stop_token token)
116  
        {
116  
        {
117  
            h_.promise().set_stop_token(token);
117  
            h_.promise().set_stop_token(token);
118  
            h_.promise().set_executor(ex);
118  
            h_.promise().set_executor(ex);
119  
            // ... rest of suspend logic ...
119  
            // ... rest of suspend logic ...
120  
        }
120  
        }
121  
    };
121  
    };
122  
    @endcode
122  
    @endcode
123  

123  

124  
    @par Thread Safety
124  
    @par Thread Safety
125  
    The stop token and executor are stored during `await_suspend` and read
125  
    The stop token and executor are stored during `await_suspend` and read
126  
    during `co_await this_coro::stop_token` or `co_await this_coro::executor`.
126  
    during `co_await this_coro::stop_token` or `co_await this_coro::executor`.
127  
    These occur on the same logical thread of execution, so no synchronization
127  
    These occur on the same logical thread of execution, so no synchronization
128  
    is required.
128  
    is required.
129  

129  

130  
    @see this_coro::stop_token
130  
    @see this_coro::stop_token
131  
    @see this_coro::executor
131  
    @see this_coro::executor
132  
    @see IoAwaitable
132  
    @see IoAwaitable
133  
*/
133  
*/
134  
template<typename Derived>
134  
template<typename Derived>
135  
class io_awaitable_support
135  
class io_awaitable_support
136  
{
136  
{
137  
    executor_ref executor_;
137  
    executor_ref executor_;
138  
    std::stop_token stop_token_;
138  
    std::stop_token stop_token_;
139  
    std::pmr::memory_resource* alloc_ = nullptr;
139  
    std::pmr::memory_resource* alloc_ = nullptr;
140  
    executor_ref caller_ex_;
140  
    executor_ref caller_ex_;
141  
    mutable coro cont_{nullptr};
141  
    mutable coro cont_{nullptr};
142  

142  

143  
public:
143  
public:
144  
    //----------------------------------------------------------
144  
    //----------------------------------------------------------
145  
    // Frame allocation support
145  
    // Frame allocation support
146  
    //----------------------------------------------------------
146  
    //----------------------------------------------------------
147  

147  

148  
private:
148  
private:
149  
    static constexpr std::size_t ptr_alignment = alignof(void*);
149  
    static constexpr std::size_t ptr_alignment = alignof(void*);
150  

150  

151  
    static std::size_t
151  
    static std::size_t
152  
    aligned_offset(std::size_t n) noexcept
152  
    aligned_offset(std::size_t n) noexcept
153  
    {
153  
    {
154  
        return (n + ptr_alignment - 1) & ~(ptr_alignment - 1);
154  
        return (n + ptr_alignment - 1) & ~(ptr_alignment - 1);
155  
    }
155  
    }
156  

156  

157  
public:
157  
public:
158  
    /** Allocate a coroutine frame.
158  
    /** Allocate a coroutine frame.
159  

159  

160  
        Uses the thread-local frame allocator set by run_async.
160  
        Uses the thread-local frame allocator set by run_async.
161  
        Falls back to default memory resource if not set.
161  
        Falls back to default memory resource if not set.
162  
        Stores the allocator pointer at the end of each frame for
162  
        Stores the allocator pointer at the end of each frame for
163  
        correct deallocation even when TLS changes.
163  
        correct deallocation even when TLS changes.
164  
    */
164  
    */
165  
    static void*
165  
    static void*
166  
    operator new(std::size_t size)
166  
    operator new(std::size_t size)
167  
    {
167  
    {
168  
        auto* mr = current_frame_allocator();
168  
        auto* mr = current_frame_allocator();
169  
        if(!mr)
169  
        if(!mr)
170  
            mr = std::pmr::get_default_resource();
170  
            mr = std::pmr::get_default_resource();
171  

171  

172  
        // Allocate extra space for memory_resource pointer
172  
        // Allocate extra space for memory_resource pointer
173  
        std::size_t ptr_offset = aligned_offset(size);
173  
        std::size_t ptr_offset = aligned_offset(size);
174  
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
174  
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
175  
        void* raw = mr->allocate(total, alignof(std::max_align_t));
175  
        void* raw = mr->allocate(total, alignof(std::max_align_t));
176  

176  

177  
        // Store the allocator pointer at the end
177  
        // Store the allocator pointer at the end
178  
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
178  
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
179  
            static_cast<char*>(raw) + ptr_offset);
179  
            static_cast<char*>(raw) + ptr_offset);
180  
        *ptr_loc = mr;
180  
        *ptr_loc = mr;
181  

181  

182  
        return raw;
182  
        return raw;
183  
    }
183  
    }
184  

184  

185  
    /** Deallocate a coroutine frame.
185  
    /** Deallocate a coroutine frame.
186  

186  

187  
        Reads the allocator pointer stored at the end of the frame
187  
        Reads the allocator pointer stored at the end of the frame
188  
        to ensure correct deallocation regardless of current TLS.
188  
        to ensure correct deallocation regardless of current TLS.
189  
    */
189  
    */
190  
    static void
190  
    static void
191  
    operator delete(void* ptr, std::size_t size)
191  
    operator delete(void* ptr, std::size_t size)
192  
    {
192  
    {
193  
        // Read the allocator pointer from the end of the frame
193  
        // Read the allocator pointer from the end of the frame
194  
        std::size_t ptr_offset = aligned_offset(size);
194  
        std::size_t ptr_offset = aligned_offset(size);
195  
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
195  
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
196  
            static_cast<char*>(ptr) + ptr_offset);
196  
            static_cast<char*>(ptr) + ptr_offset);
197  
        auto* mr = *ptr_loc;
197  
        auto* mr = *ptr_loc;
198  

198  

199  
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
199  
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
200  
        mr->deallocate(ptr, total, alignof(std::max_align_t));
200  
        mr->deallocate(ptr, total, alignof(std::max_align_t));
201  
    }
201  
    }
202  

202  

203  
    ~io_awaitable_support()
203  
    ~io_awaitable_support()
204  
    {
204  
    {
205  
        if (cont_)
205  
        if (cont_)
206  
            cont_.destroy();
206  
            cont_.destroy();
207  
    }
207  
    }
208  

208  

209  
    /** Store a frame allocator for later retrieval.
209  
    /** Store a frame allocator for later retrieval.
210  

210  

211  
        Call this from initial_suspend to capture the current
211  
        Call this from initial_suspend to capture the current
212  
        TLS allocator for propagation to child tasks.
212  
        TLS allocator for propagation to child tasks.
213  

213  

214  
        @param alloc The allocator to store.
214  
        @param alloc The allocator to store.
215  
    */
215  
    */
216  
    void
216  
    void
217  
    set_frame_allocator(std::pmr::memory_resource* alloc) noexcept
217  
    set_frame_allocator(std::pmr::memory_resource* alloc) noexcept
218  
    {
218  
    {
219  
        alloc_ = alloc;
219  
        alloc_ = alloc;
220  
    }
220  
    }
221  

221  

222  
    /** Return the stored frame allocator.
222  
    /** Return the stored frame allocator.
223  

223  

224  
        @return The allocator, or nullptr if none was set.
224  
        @return The allocator, or nullptr if none was set.
225  
    */
225  
    */
226  
    std::pmr::memory_resource*
226  
    std::pmr::memory_resource*
227  
    frame_allocator() const noexcept
227  
    frame_allocator() const noexcept
228  
    {
228  
    {
229  
        return alloc_;
229  
        return alloc_;
230  
    }
230  
    }
231  

231  

232  
    //----------------------------------------------------------
232  
    //----------------------------------------------------------
233  
    // Continuation support
233  
    // Continuation support
234  
    //----------------------------------------------------------
234  
    //----------------------------------------------------------
235  

235  

236  
    /** Store continuation and caller's executor for completion dispatch.
236  
    /** Store continuation and caller's executor for completion dispatch.
237  

237  

238  
        Call this from your coroutine type's `await_suspend` overload to
238  
        Call this from your coroutine type's `await_suspend` overload to
239  
        set up the completion path. On completion, the coroutine will
239  
        set up the completion path. On completion, the coroutine will
240  
        resume the continuation, dispatching through the caller's executor
240  
        resume the continuation, dispatching through the caller's executor
241  
        if it differs from this coroutine's executor.
241  
        if it differs from this coroutine's executor.
242  

242  

243  
        @param cont The continuation to resume on completion.
243  
        @param cont The continuation to resume on completion.
244  
        @param caller_ex The caller's executor for completion dispatch.
244  
        @param caller_ex The caller's executor for completion dispatch.
245  
    */
245  
    */
246  
    void set_continuation(coro cont, executor_ref caller_ex) noexcept
246  
    void set_continuation(coro cont, executor_ref caller_ex) noexcept
247  
    {
247  
    {
248  
        cont_ = cont;
248  
        cont_ = cont;
249  
        caller_ex_ = caller_ex;
249  
        caller_ex_ = caller_ex;
250  
    }
250  
    }
251  

251  

252  
    /** Return the handle to resume on completion with dispatch-awareness.
252  
    /** Return the handle to resume on completion with dispatch-awareness.
253  

253  

254  
        If no continuation was set, returns `std::noop_coroutine()`.
254  
        If no continuation was set, returns `std::noop_coroutine()`.
255  
        If the coroutine's executor matches the caller's executor, returns
255  
        If the coroutine's executor matches the caller's executor, returns
256  
        the continuation directly for symmetric transfer.
256  
        the continuation directly for symmetric transfer.
257  
        Otherwise, dispatches through the caller's executor and returns
257  
        Otherwise, dispatches through the caller's executor and returns
258  
        `std::noop_coroutine()`.
258  
        `std::noop_coroutine()`.
259  

259  

260  
        Call this from your `final_suspend` awaiter's `await_suspend`.
260  
        Call this from your `final_suspend` awaiter's `await_suspend`.
261  

261  

262  
        @return A coroutine handle for symmetric transfer.
262  
        @return A coroutine handle for symmetric transfer.
263  
    */
263  
    */
264  
    coro complete() const noexcept
264  
    coro complete() const noexcept
265  
    {
265  
    {
266  
        if(!cont_)
266  
        if(!cont_)
267  
            return std::noop_coroutine();
267  
            return std::noop_coroutine();
268  
        if(executor_ == caller_ex_)
268  
        if(executor_ == caller_ex_)
269  
            return std::exchange(cont_, nullptr);
269  
            return std::exchange(cont_, nullptr);
270  
        caller_ex_.dispatch(std::exchange(cont_, nullptr));
270  
        caller_ex_.dispatch(std::exchange(cont_, nullptr));
271  
        return std::noop_coroutine();
271  
        return std::noop_coroutine();
272  
    }
272  
    }
273  

273  

274  
    /** Store a stop token for later retrieval.
274  
    /** Store a stop token for later retrieval.
275  

275  

276  
        Call this from your coroutine type's `await_suspend`
276  
        Call this from your coroutine type's `await_suspend`
277  
        overload to make the token available via
277  
        overload to make the token available via
278  
        `co_await this_coro::stop_token`.
278  
        `co_await this_coro::stop_token`.
279  

279  

280  
        @param token The stop token to store.
280  
        @param token The stop token to store.
281  
    */
281  
    */
282  
    void set_stop_token(std::stop_token token) noexcept
282  
    void set_stop_token(std::stop_token token) noexcept
283  
    {
283  
    {
284  
        stop_token_ = token;
284  
        stop_token_ = token;
285  
    }
285  
    }
286  

286  

287  
    /** Return the stored stop token.
287  
    /** Return the stored stop token.
288  

288  

289  
        @return The stop token, or a default-constructed token if none was set.
289  
        @return The stop token, or a default-constructed token if none was set.
290  
    */
290  
    */
291  
    std::stop_token const& stop_token() const noexcept
291  
    std::stop_token const& stop_token() const noexcept
292  
    {
292  
    {
293  
        return stop_token_;
293  
        return stop_token_;
294  
    }
294  
    }
295  

295  

296  
    /** Store an executor for later retrieval.
296  
    /** Store an executor for later retrieval.
297  

297  

298  
        Call this from your coroutine type's `await_suspend`
298  
        Call this from your coroutine type's `await_suspend`
299  
        overload to make the executor available via
299  
        overload to make the executor available via
300  
        `co_await this_coro::executor`.
300  
        `co_await this_coro::executor`.
301  

301  

302  
        @param ex The executor to store.
302  
        @param ex The executor to store.
303  
    */
303  
    */
304  
    void set_executor(executor_ref ex) noexcept
304  
    void set_executor(executor_ref ex) noexcept
305  
    {
305  
    {
306  
        executor_ = ex;
306  
        executor_ = ex;
307  
    }
307  
    }
308  

308  

309  
    /** Return the stored executor.
309  
    /** Return the stored executor.
310  

310  

311  
        @return The executor, or a default-constructed executor_ref if none was set.
311  
        @return The executor, or a default-constructed executor_ref if none was set.
312  
    */
312  
    */
313  
    executor_ref executor() const noexcept
313  
    executor_ref executor() const noexcept
314  
    {
314  
    {
315  
        return executor_;
315  
        return executor_;
316  
    }
316  
    }
317  

317  

318  
    /** Transform an awaitable before co_await.
318  
    /** Transform an awaitable before co_await.
319  

319  

320  
        Override this in your derived promise type to customize how
320  
        Override this in your derived promise type to customize how
321  
        awaitables are transformed. The default implementation passes
321  
        awaitables are transformed. The default implementation passes
322  
        the awaitable through unchanged.
322  
        the awaitable through unchanged.
323  

323  

324  
        @param a The awaitable expression from `co_await a`.
324  
        @param a The awaitable expression from `co_await a`.
325  

325  

326  
        @return The transformed awaitable.
326  
        @return The transformed awaitable.
327  
    */
327  
    */
328  
    template<typename A>
328  
    template<typename A>
329  
    decltype(auto) transform_awaitable(A&& a)
329  
    decltype(auto) transform_awaitable(A&& a)
330  
    {
330  
    {
331  
        return std::forward<A>(a);
331  
        return std::forward<A>(a);
332  
    }
332  
    }
333  

333  

334  
    /** Intercept co_await expressions.
334  
    /** Intercept co_await expressions.
335  

335  

336  
        This function handles @ref this_coro::stop_token_tag and
336  
        This function handles @ref this_coro::stop_token_tag and
337  
        @ref this_coro::executor_tag specially, returning an awaiter that
337  
        @ref this_coro::executor_tag specially, returning an awaiter that
338  
        yields the stored value. All other awaitables are delegated to
338  
        yields the stored value. All other awaitables are delegated to
339  
        @ref transform_awaitable.
339  
        @ref transform_awaitable.
340  

340  

341  
        @param t The awaited expression.
341  
        @param t The awaited expression.
342  

342  

343  
        @return An awaiter for the expression.
343  
        @return An awaiter for the expression.
344  
    */
344  
    */
345  
    template<typename T>
345  
    template<typename T>
346  
    auto await_transform(T&& t)
346  
    auto await_transform(T&& t)
347  
    {
347  
    {
348  
        if constexpr (std::is_same_v<std::decay_t<T>, this_coro::stop_token_tag>)
348  
        if constexpr (std::is_same_v<std::decay_t<T>, this_coro::stop_token_tag>)
349  
        {
349  
        {
350  
            struct awaiter
350  
            struct awaiter
351  
            {
351  
            {
352  
                std::stop_token token_;
352  
                std::stop_token token_;
353  

353  

354  
                bool await_ready() const noexcept
354  
                bool await_ready() const noexcept
355  
                {
355  
                {
356  
                    return true;
356  
                    return true;
357  
                }
357  
                }
358  

358  

359  
                void await_suspend(coro) const noexcept
359  
                void await_suspend(coro) const noexcept
360  
                {
360  
                {
361  
                }
361  
                }
362  

362  

363  
                std::stop_token await_resume() const noexcept
363  
                std::stop_token await_resume() const noexcept
364  
                {
364  
                {
365  
                    return token_;
365  
                    return token_;
366  
                }
366  
                }
367  
            };
367  
            };
368  
            return awaiter{stop_token_};
368  
            return awaiter{stop_token_};
369  
        }
369  
        }
370  
        else if constexpr (std::is_same_v<std::decay_t<T>, this_coro::executor_tag>)
370  
        else if constexpr (std::is_same_v<std::decay_t<T>, this_coro::executor_tag>)
371  
        {
371  
        {
372  
            struct awaiter
372  
            struct awaiter
373  
            {
373  
            {
374  
                executor_ref executor_;
374  
                executor_ref executor_;
375  

375  

376  
                bool await_ready() const noexcept
376  
                bool await_ready() const noexcept
377  
                {
377  
                {
378  
                    return true;
378  
                    return true;
379  
                }
379  
                }
380  

380  

381  
                void await_suspend(coro) const noexcept
381  
                void await_suspend(coro) const noexcept
382  
                {
382  
                {
383  
                }
383  
                }
384  

384  

385  
                executor_ref await_resume() const noexcept
385  
                executor_ref await_resume() const noexcept
386  
                {
386  
                {
387  
                    return executor_;
387  
                    return executor_;
388  
                }
388  
                }
389  
            };
389  
            };
390  
            return awaiter{executor_};
390  
            return awaiter{executor_};
391  
        }
391  
        }
392  
        else
392  
        else
393  
        {
393  
        {
394  
            return static_cast<Derived*>(this)->transform_awaitable(
394  
            return static_cast<Derived*>(this)->transform_awaitable(
395  
                std::forward<T>(t));
395  
                std::forward<T>(t));
396  
        }
396  
        }
397  
    }
397  
    }
398  
};
398  
};
399  

399  

400  
} // namespace capy
400  
} // namespace capy
401  
} // namespace boost
401  
} // namespace boost
402  

402  

403  
#endif
403  
#endif