1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/ex/frame_allocator.hpp>
14  
#include <boost/capy/ex/frame_allocator.hpp>
15  
#include <boost/capy/ex/io_env.hpp>
15  
#include <boost/capy/ex/io_env.hpp>
 
16 +
#include <boost/capy/ex/recycling_memory_resource.hpp>
16  
#include <boost/capy/ex/this_coro.hpp>
17  
#include <boost/capy/ex/this_coro.hpp>
17  

18  

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

25  

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

28  

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

30  

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

32  

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

35  

34  
    2. **Environment storage** — The mixin stores a pointer to the `io_env`
36  
    2. **Environment storage** — The mixin stores a pointer to the `io_env`
35  
       containing the executor, stop token, and allocator for this coroutine.
37  
       containing the executor, stop token, and allocator for this coroutine.
36  

38  

37  
    3. **Environment access** — Coroutine code can retrieve the environment
39  
    3. **Environment access** — Coroutine code can retrieve the environment
38  
       via `co_await this_coro::environment`, or individual fields via
40  
       via `co_await this_coro::environment`, or individual fields via
39  
       `co_await this_coro::executor`, `co_await this_coro::stop_token`,
41  
       `co_await this_coro::executor`, `co_await this_coro::stop_token`,
40  
       and `co_await this_coro::allocator`.
42  
       and `co_await this_coro::allocator`.
41  

43  

42  
    @tparam Derived The derived promise type (CRTP pattern).
44  
    @tparam Derived The derived promise type (CRTP pattern).
43  

45  

44  
    @par Basic Usage
46  
    @par Basic Usage
45  

47  

46  
    For coroutines that need to access their execution environment:
48  
    For coroutines that need to access their execution environment:
47  

49  

48  
    @code
50  
    @code
49  
    struct my_task
51  
    struct my_task
50  
    {
52  
    {
51  
        struct promise_type : io_awaitable_support<promise_type>
53  
        struct promise_type : io_awaitable_support<promise_type>
52  
        {
54  
        {
53  
            my_task get_return_object();
55  
            my_task get_return_object();
54  
            std::suspend_always initial_suspend() noexcept;
56  
            std::suspend_always initial_suspend() noexcept;
55  
            std::suspend_always final_suspend() noexcept;
57  
            std::suspend_always final_suspend() noexcept;
56  
            void return_void();
58  
            void return_void();
57  
            void unhandled_exception();
59  
            void unhandled_exception();
58  
        };
60  
        };
59  

61  

60  
        // ... awaitable interface ...
62  
        // ... awaitable interface ...
61  
    };
63  
    };
62  

64  

63  
    my_task example()
65  
    my_task example()
64  
    {
66  
    {
65  
        auto env = co_await this_coro::environment;
67  
        auto env = co_await this_coro::environment;
66  
        // Access env->executor, env->stop_token, env->allocator
68  
        // Access env->executor, env->stop_token, env->allocator
67  

69  

68  
        // Or use fine-grained accessors:
70  
        // Or use fine-grained accessors:
69  
        auto ex = co_await this_coro::executor;
71  
        auto ex = co_await this_coro::executor;
70  
        auto token = co_await this_coro::stop_token;
72  
        auto token = co_await this_coro::stop_token;
71  
        auto* alloc = co_await this_coro::allocator;
73  
        auto* alloc = co_await this_coro::allocator;
72  
    }
74  
    }
73  
    @endcode
75  
    @endcode
74  

76  

75  
    @par Custom Awaitable Transformation
77  
    @par Custom Awaitable Transformation
76  

78  

77  
    If your promise needs to transform awaitables (e.g., for affinity or
79  
    If your promise needs to transform awaitables (e.g., for affinity or
78  
    logging), override `transform_awaitable` instead of `await_transform`:
80  
    logging), override `transform_awaitable` instead of `await_transform`:
79  

81  

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

93  

92  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
94  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
93  
    and the fine-grained tag types (@ref this_coro::executor_tag,
95  
    and the fine-grained tag types (@ref this_coro::executor_tag,
94  
    @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag),
96  
    @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag),
95  
    then delegates all other awaitables to your `transform_awaitable`.
97  
    then delegates all other awaitables to your `transform_awaitable`.
96  

98  

97  
    @par Making Your Coroutine an IoAwaitable
99  
    @par Making Your Coroutine an IoAwaitable
98  

100  

99  
    The mixin handles the "inside the coroutine" part—accessing the
101  
    The mixin handles the "inside the coroutine" part—accessing the
100  
    environment. To receive the environment when your coroutine is awaited
102  
    environment. To receive the environment when your coroutine is awaited
101  
    (satisfying @ref IoAwaitable), implement the `await_suspend` overload
103  
    (satisfying @ref IoAwaitable), implement the `await_suspend` overload
102  
    on your coroutine return type:
104  
    on your coroutine return type:
103  

105  

104  
    @code
106  
    @code
105  
    struct my_task
107  
    struct my_task
106  
    {
108  
    {
107  
        struct promise_type : io_awaitable_support<promise_type> { ... };
109  
        struct promise_type : io_awaitable_support<promise_type> { ... };
108  

110  

109  
        std::coroutine_handle<promise_type> h_;
111  
        std::coroutine_handle<promise_type> h_;
110  

112  

111  
        // IoAwaitable await_suspend receives and stores the environment
113  
        // IoAwaitable await_suspend receives and stores the environment
112  
        std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
114  
        std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
113  
        {
115  
        {
114  
            h_.promise().set_environment(env);
116  
            h_.promise().set_environment(env);
115  
            // ... rest of suspend logic ...
117  
            // ... rest of suspend logic ...
116  
        }
118  
        }
117  
    };
119  
    };
118  
    @endcode
120  
    @endcode
119  

121  

120  
    @par Thread Safety
122  
    @par Thread Safety
121  
    The environment is stored during `await_suspend` and read during
123  
    The environment is stored during `await_suspend` and read during
122  
    `co_await this_coro::environment`. These occur on the same logical
124  
    `co_await this_coro::environment`. These occur on the same logical
123  
    thread of execution, so no synchronization is required.
125  
    thread of execution, so no synchronization is required.
124  

126  

125  
    @see this_coro::environment, this_coro::executor,
127  
    @see this_coro::environment, this_coro::executor,
126  
         this_coro::stop_token, this_coro::allocator
128  
         this_coro::stop_token, this_coro::allocator
127  
    @see io_env
129  
    @see io_env
128  
    @see IoAwaitable
130  
    @see IoAwaitable
129  
*/
131  
*/
130  
template<typename Derived>
132  
template<typename Derived>
131  
class io_awaitable_support
133  
class io_awaitable_support
132  
{
134  
{
133  
    io_env const* env_ = &detail::empty_io_env;
135  
    io_env const* env_ = &detail::empty_io_env;
134  
    mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
136  
    mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
135  

137  

136  
public:
138  
public:
137  
    //----------------------------------------------------------
139  
    //----------------------------------------------------------
138  
    // Frame allocation support
140  
    // Frame allocation support
139  
    //----------------------------------------------------------
141  
    //----------------------------------------------------------
140 -
private:
 
141 -
    static constexpr std::size_t ptr_alignment = alignof(void*);
 
142 -

 
143 -
    static std::size_t
 
144 -
    aligned_offset(std::size_t n) noexcept
 
145 -
    {
 
146 -
        return (n + ptr_alignment - 1) & ~(ptr_alignment - 1);
 
147 -
    }
 
148 -

 
149  

142  

150  
public:
143  
public:
151  
    /** Allocate a coroutine frame.
144  
    /** Allocate a coroutine frame.
152  

145  

153  
        Uses the thread-local frame allocator set by run_async.
146  
        Uses the thread-local frame allocator set by run_async.
154  
        Falls back to default memory resource if not set.
147  
        Falls back to default memory resource if not set.
155  
        Stores the allocator pointer at the end of each frame for
148  
        Stores the allocator pointer at the end of each frame for
156 -
        correct deallocation even when TLS changes.
149 +
        correct deallocation even when TLS changes. Uses memcpy
 
150 +
        to avoid alignment requirements on the trailing pointer.
 
151 +
        Bypasses virtual dispatch for the recycling allocator.
157  
    */
152  
    */
158  
    static void*
153  
    static void*
159  
    operator new(std::size_t size)
154  
    operator new(std::size_t size)
160  
    {
155  
    {
 
156 +
        static auto* const rmr = get_recycling_memory_resource();
 
157 +

161  
        auto* mr = current_frame_allocator();
158  
        auto* mr = current_frame_allocator();
162  
        if(!mr)
159  
        if(!mr)
163  
            mr = std::pmr::get_default_resource();
160  
            mr = std::pmr::get_default_resource();
164  

161  

165 -
        // Allocate extra space for memory_resource pointer
162 +
        auto total = size + sizeof(std::pmr::memory_resource*);
166 -
        std::size_t ptr_offset = aligned_offset(size);
163 +
        void* raw;
167 -
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
164 +
        if(mr == rmr)
168 -
        void* raw = mr->allocate(total, alignof(std::max_align_t));
165 +
            raw = static_cast<recycling_memory_resource*>(mr)
169 -

166 +
                ->allocate_fast(total, alignof(std::max_align_t));
170 -
        // Store the allocator pointer at the end
167 +
        else
171 -
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
168 +
            raw = mr->allocate(total, alignof(std::max_align_t));
172 -
            static_cast<char*>(raw) + ptr_offset);
169 +
        std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
173 -
        *ptr_loc = mr;
 
174 -

 
175  
        return raw;
170  
        return raw;
176  
    }
171  
    }
177  

172  

178  
    /** Deallocate a coroutine frame.
173  
    /** Deallocate a coroutine frame.
179  

174  

180  
        Reads the allocator pointer stored at the end of the frame
175  
        Reads the allocator pointer stored at the end of the frame
181  
        to ensure correct deallocation regardless of current TLS.
176  
        to ensure correct deallocation regardless of current TLS.
 
177 +
        Bypasses virtual dispatch for the recycling allocator.
182  
    */
178  
    */
183  
    static void
179  
    static void
184  
    operator delete(void* ptr, std::size_t size)
180  
    operator delete(void* ptr, std::size_t size)
185  
    {
181  
    {
186 -
        // Read the allocator pointer from the end of the frame
182 +
        static auto* const rmr = get_recycling_memory_resource();
187 -
        std::size_t ptr_offset = aligned_offset(size);
 
188 -
        auto* ptr_loc = reinterpret_cast<std::pmr::memory_resource**>(
 
189 -
            static_cast<char*>(ptr) + ptr_offset);
 
190 -
        auto* mr = *ptr_loc;
 
191  

183  

192 -
        std::size_t total = ptr_offset + sizeof(std::pmr::memory_resource*);
184 +
        std::pmr::memory_resource* mr;
193 -
        mr->deallocate(ptr, total, alignof(std::max_align_t));
185 +
        std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
 
186 +
        auto total = size + sizeof(std::pmr::memory_resource*);
 
187 +
        if(mr == rmr)
 
188 +
            static_cast<recycling_memory_resource*>(mr)
 
189 +
                ->deallocate_fast(ptr, total, alignof(std::max_align_t));
 
190 +
        else
 
191 +
            mr->deallocate(ptr, total, alignof(std::max_align_t));
194  
    }
192  
    }
195  

193  

196  
    ~io_awaitable_support()
194  
    ~io_awaitable_support()
197  
    {
195  
    {
198  
        // Abnormal teardown: destroy orphaned continuation
196  
        // Abnormal teardown: destroy orphaned continuation
199  
        if(cont_ != std::noop_coroutine())
197  
        if(cont_ != std::noop_coroutine())
200  
            cont_.destroy();
198  
            cont_.destroy();
201  
    }
199  
    }
202  

200  

203  
    //----------------------------------------------------------
201  
    //----------------------------------------------------------
204  
    // Continuation support
202  
    // Continuation support
205  
    //----------------------------------------------------------
203  
    //----------------------------------------------------------
206  

204  

207  
    /** Store the continuation to resume on completion.
205  
    /** Store the continuation to resume on completion.
208  

206  

209  
        Call this from your coroutine type's `await_suspend` overload
207  
        Call this from your coroutine type's `await_suspend` overload
210  
        to set up the completion path. The `final_suspend` awaiter
208  
        to set up the completion path. The `final_suspend` awaiter
211  
        returns this handle via unconditional symmetric transfer.
209  
        returns this handle via unconditional symmetric transfer.
212  

210  

213  
        @param cont The continuation to resume on completion.
211  
        @param cont The continuation to resume on completion.
214  
    */
212  
    */
215  
    void set_continuation(std::coroutine_handle<> cont) noexcept
213  
    void set_continuation(std::coroutine_handle<> cont) noexcept
216  
    {
214  
    {
217  
        cont_ = cont;
215  
        cont_ = cont;
218  
    }
216  
    }
219  

217  

220  
    /** Return and consume the stored continuation handle.
218  
    /** Return and consume the stored continuation handle.
221  

219  

222  
        Resets the stored handle to `noop_coroutine()` so the
220  
        Resets the stored handle to `noop_coroutine()` so the
223  
        destructor will not double-destroy it.
221  
        destructor will not double-destroy it.
224  

222  

225  
        @return The continuation for symmetric transfer.
223  
        @return The continuation for symmetric transfer.
226  
    */
224  
    */
227  
    std::coroutine_handle<> continuation() const noexcept
225  
    std::coroutine_handle<> continuation() const noexcept
228  
    {
226  
    {
229  
        return std::exchange(cont_, std::noop_coroutine());
227  
        return std::exchange(cont_, std::noop_coroutine());
230  
    }
228  
    }
231  

229  

232  
    //----------------------------------------------------------
230  
    //----------------------------------------------------------
233  
    // Environment support
231  
    // Environment support
234  
    //----------------------------------------------------------
232  
    //----------------------------------------------------------
235  

233  

236  
    /** Store a pointer to the execution environment.
234  
    /** Store a pointer to the execution environment.
237  

235  

238  
        Call this from your coroutine type's `await_suspend`
236  
        Call this from your coroutine type's `await_suspend`
239  
        overload to make the environment available via
237  
        overload to make the environment available via
240  
        `co_await this_coro::environment`. The pointed-to
238  
        `co_await this_coro::environment`. The pointed-to
241  
        `io_env` must outlive this coroutine.
239  
        `io_env` must outlive this coroutine.
242  

240  

243  
        @param env The environment to store.
241  
        @param env The environment to store.
244  
    */
242  
    */
245  
    void set_environment(io_env const* env) noexcept
243  
    void set_environment(io_env const* env) noexcept
246  
    {
244  
    {
247  
        env_ = env;
245  
        env_ = env;
248  
    }
246  
    }
249  

247  

250  
    /** Return the stored execution environment.
248  
    /** Return the stored execution environment.
251  

249  

252  
        @return The environment.
250  
        @return The environment.
253  
    */
251  
    */
254  
    io_env const* environment() const noexcept
252  
    io_env const* environment() const noexcept
255  
    {
253  
    {
256  
        return env_;
254  
        return env_;
257  
    }
255  
    }
258  

256  

259  
    /** Transform an awaitable before co_await.
257  
    /** Transform an awaitable before co_await.
260  

258  

261  
        Override this in your derived promise type to customize how
259  
        Override this in your derived promise type to customize how
262  
        awaitables are transformed. The default implementation passes
260  
        awaitables are transformed. The default implementation passes
263  
        the awaitable through unchanged.
261  
        the awaitable through unchanged.
264  

262  

265  
        @param a The awaitable expression from `co_await a`.
263  
        @param a The awaitable expression from `co_await a`.
266  

264  

267  
        @return The transformed awaitable.
265  
        @return The transformed awaitable.
268  
    */
266  
    */
269  
    template<typename A>
267  
    template<typename A>
270  
    decltype(auto) transform_awaitable(A&& a)
268  
    decltype(auto) transform_awaitable(A&& a)
271  
    {
269  
    {
272  
        return std::forward<A>(a);
270  
        return std::forward<A>(a);
273  
    }
271  
    }
274  

272  

275  
    /** Intercept co_await expressions.
273  
    /** Intercept co_await expressions.
276  

274  

277  
        This function handles @ref this_coro::environment_tag and
275  
        This function handles @ref this_coro::environment_tag and
278  
        the fine-grained tags (@ref this_coro::executor_tag,
276  
        the fine-grained tags (@ref this_coro::executor_tag,
279  
        @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag)
277  
        @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag)
280  
        specially, returning an awaiter that yields the stored value.
278  
        specially, returning an awaiter that yields the stored value.
281  
        All other awaitables are delegated to @ref transform_awaitable.
279  
        All other awaitables are delegated to @ref transform_awaitable.
282  

280  

283  
        @param t The awaited expression.
281  
        @param t The awaited expression.
284  

282  

285  
        @return An awaiter for the expression.
283  
        @return An awaiter for the expression.
286  
    */
284  
    */
287  
    template<typename T>
285  
    template<typename T>
288  
    auto await_transform(T&& t)
286  
    auto await_transform(T&& t)
289  
    {
287  
    {
290  
        using Tag = std::decay_t<T>;
288  
        using Tag = std::decay_t<T>;
291  

289  

292  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
290  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
293  
        {
291  
        {
294  
            struct awaiter
292  
            struct awaiter
295  
            {
293  
            {
296  
                io_env const* env_;
294  
                io_env const* env_;
297  
                bool await_ready() const noexcept { return true; }
295  
                bool await_ready() const noexcept { return true; }
298  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
296  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
299  
                io_env const* await_resume() const noexcept { return env_; }
297  
                io_env const* await_resume() const noexcept { return env_; }
300  
            };
298  
            };
301  
            return awaiter{env_};
299  
            return awaiter{env_};
302  
        }
300  
        }
303  
        else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
301  
        else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
304  
        {
302  
        {
305  
            struct awaiter
303  
            struct awaiter
306  
            {
304  
            {
307  
                executor_ref executor_;
305  
                executor_ref executor_;
308  
                bool await_ready() const noexcept { return true; }
306  
                bool await_ready() const noexcept { return true; }
309  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
307  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
310  
                executor_ref await_resume() const noexcept { return executor_; }
308  
                executor_ref await_resume() const noexcept { return executor_; }
311  
            };
309  
            };
312  
            return awaiter{env_->executor};
310  
            return awaiter{env_->executor};
313  
        }
311  
        }
314  
        else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
312  
        else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
315  
        {
313  
        {
316  
            struct awaiter
314  
            struct awaiter
317  
            {
315  
            {
318  
                std::stop_token token_;
316  
                std::stop_token token_;
319  
                bool await_ready() const noexcept { return true; }
317  
                bool await_ready() const noexcept { return true; }
320  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
318  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
321  
                std::stop_token await_resume() const noexcept { return token_; }
319  
                std::stop_token await_resume() const noexcept { return token_; }
322  
            };
320  
            };
323  
            return awaiter{env_->stop_token};
321  
            return awaiter{env_->stop_token};
324  
        }
322  
        }
325  
        else if constexpr (std::is_same_v<Tag, this_coro::allocator_tag>)
323  
        else if constexpr (std::is_same_v<Tag, this_coro::allocator_tag>)
326  
        {
324  
        {
327  
            struct awaiter
325  
            struct awaiter
328  
            {
326  
            {
329  
                std::pmr::memory_resource* allocator_;
327  
                std::pmr::memory_resource* allocator_;
330  
                bool await_ready() const noexcept { return true; }
328  
                bool await_ready() const noexcept { return true; }
331  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
329  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
332  
                std::pmr::memory_resource* await_resume() const noexcept { return allocator_; }
330  
                std::pmr::memory_resource* await_resume() const noexcept { return allocator_; }
333  
            };
331  
            };
334  
            return awaiter{env_->allocator};
332  
            return awaiter{env_->allocator};
335  
        }
333  
        }
336  
        else
334  
        else
337  
        {
335  
        {
338  
            return static_cast<Derived*>(this)->transform_awaitable(
336  
            return static_cast<Derived*>(this)->transform_awaitable(
339  
                std::forward<T>(t));
337  
                std::forward<T>(t));
340  
        }
338  
        }
341  
    }
339  
    }
342  
};
340  
};
343  

341  

344  
} // namespace capy
342  
} // namespace capy
345  
} // namespace boost
343  
} // namespace boost
346  

344  

347  
#endif
345  
#endif