include/boost/capy/ex/execution_context.hpp

100.0% Lines (52/0/52) 100.0% List of functions (49/0/49)
execution_context.hpp
f(x) Functions (49)
Function Calls Lines Blocks
boost::capy::execution_context::service::~service() :161 52x 100.0% 100.0% boost::capy::execution_context::service::service() :165 52x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::base_service>() const :238 2x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::derived_service>() const :238 1x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::keyed_a>() const :238 1x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::keyed_b>() const :238 1x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::multi_arg_service>() const :238 1x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::nested_service>() const :238 1x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::simple_service>() const :238 6x 100.0% 100.0% bool boost::capy::execution_context::has_service<boost::capy::(anonymous namespace)::test_service>() const :238 3x 100.0% 100.0% boost::capy::(anonymous namespace)::base_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::base_service>() const :253 3x 100.0% 100.0% boost::capy::(anonymous namespace)::derived_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::derived_service>() const :253 2x 100.0% 100.0% boost::capy::(anonymous namespace)::keyed_a* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::keyed_a>() const :253 1x 100.0% 100.0% boost::capy::(anonymous namespace)::keyed_b* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::keyed_b>() const :253 1x 100.0% 100.0% boost::capy::(anonymous namespace)::multi_arg_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::multi_arg_service>() const :253 2x 100.0% 100.0% boost::capy::(anonymous namespace)::nested_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::nested_service>() const :253 1x 100.0% 100.0% boost::capy::(anonymous namespace)::simple_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::simple_service>() const :253 10x 100.0% 100.0% boost::capy::(anonymous namespace)::test_service* boost::capy::execution_context::find_service<boost::capy::(anonymous namespace)::test_service>() const :253 5x 100.0% 100.0% boost::capy::(anonymous namespace)::derived_service& boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::derived_service>() :281 1x 100.0% 100.0% boost::capy::(anonymous namespace)::nested_service& boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::nested_service>() :281 1x 100.0% 100.0% boost::capy::(anonymous namespace)::simple_service& boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::simple_service>() :281 13x 100.0% 100.0% boost::capy::(anonymous namespace)::test_service& boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::test_service>() :281 4x 100.0% 100.0% boost::capy::detail::strand_service_impl& boost::capy::execution_context::use_service<boost::capy::detail::strand_service_impl>() :281 11446x 100.0% 100.0% boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::test_service>()::impl::impl() :290 4x 100.0% 100.0% boost::capy::execution_context::use_service<boost::capy::detail::strand_service_impl>()::impl::impl() :290 11446x 100.0% 100.0% boost::capy::execution_context::use_service<boost::capy::(anonymous namespace)::test_service>()::impl::create(boost::capy::execution_context&) :299 2x 100.0% 100.0% boost::capy::execution_context::use_service<boost::capy::detail::strand_service_impl>()::impl::create(boost::capy::execution_context&) :299 34x 100.0% 71.0% boost::capy::(anonymous namespace)::derived_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::derived_service, int>(int&&) :337 1x 100.0% 100.0% boost::capy::(anonymous namespace)::keyed_a& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::keyed_a>() :337 1x 100.0% 50.0% boost::capy::(anonymous namespace)::keyed_b& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::keyed_b>() :337 1x 100.0% 100.0% boost::capy::(anonymous namespace)::multi_arg_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::multi_arg_service, int, char const (&) [5], double>(int&&, char const (&) [5], double&&) :337 1x 100.0% 100.0% boost::capy::(anonymous namespace)::multi_arg_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::multi_arg_service, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>(int&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, double&&) :337 1x 100.0% 100.0% boost::capy::(anonymous namespace)::other_derived_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::other_derived_service>() :337 1x 100.0% 50.0% boost::capy::(anonymous namespace)::simple_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::simple_service, int>(int&&) :337 3x 100.0% 100.0% boost::capy::(anonymous namespace)::test_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::test_service, int>(int&&) :337 2x 100.0% 100.0% boost::capy::(anonymous namespace)::tracking_service& boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::tracking_service, bool&>(bool&) :337 1x 100.0% 100.0% boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::test_service, int>(int&&)::impl::impl(int&&) :352 2x 100.0% 100.0% boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::test_service, int>(int&&)::impl::create(boost::capy::execution_context&) :362 1x 100.0% 100.0% auto boost::capy::execution_context::make_service<boost::capy::(anonymous namespace)::test_service, int>(int&&)::impl::create(boost::capy::execution_context&)::{lambda((auto:1&&)...)#1}::operator()<int>(int&&) const :364 1x 100.0% 100.0% boost::capy::execution_context::get_frame_allocator() const :387 1924x 100.0% 100.0% boost::capy::execution_context::set_frame_allocator(std::pmr::memory_resource*) :407 1x 100.0% 100.0% void boost::capy::execution_context::set_frame_allocator<std::allocator<int> >(std::allocator<int> const&) :434 1x 100.0% 100.0% void boost::capy::execution_context::set_frame_allocator<std::allocator<void> >(std::allocator<void> const&) :434 388x 100.0% 100.0% boost::capy::execution_context_test::testTarget()::other_context const* boost::capy::execution_context::target<boost::capy::execution_context_test::testTarget()::other_context>() const :463 1x 75.0% 80.0% boost::capy::test_io_context const* boost::capy::execution_context::target<boost::capy::test_io_context>() const :463 1x 75.0% 80.0% boost::capy::execution_context_test::testTarget()::other_context* boost::capy::execution_context::target<boost::capy::execution_context_test::testTarget()::other_context>() :472 1x 75.0% 80.0% boost::capy::test_io_context* boost::capy::execution_context::target<boost::capy::test_io_context>() :472 1x 75.0% 80.0% boost::capy::execution_context::factory::factory(std::type_info const&, std::type_info const&) :538 11477x 100.0% 100.0% boost::capy::execution_context::execution_context<boost::capy::test_io_context>(boost::capy::test_io_context*) :567 29x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/capy
9 //
10
11 #ifndef BOOST_CAPY_EXECUTION_CONTEXT_HPP
12 #define BOOST_CAPY_EXECUTION_CONTEXT_HPP
13
14 #include <boost/capy/detail/config.hpp>
15 #include <boost/capy/detail/frame_memory_resource.hpp>
16 #include <boost/capy/detail/type_id.hpp>
17 #include <boost/capy/concept/executor.hpp>
18 #include <concepts>
19 #include <memory>
20 #include <memory_resource>
21 #include <mutex>
22 #include <tuple>
23 #include <type_traits>
24 #include <utility>
25
26 namespace boost {
27 namespace capy {
28
29 /** Registers, looks up, and shuts down `service` objects owned by a derived context.
30
31 An execution context represents a place where function objects are
32 executed. It provides a service registry where polymorphic services
33 can be stored and retrieved by type. Each service type may be stored
34 at most once. Services may specify a nested `key_type` to enable
35 lookup by a base class type.
36
37 Derived classes such as `io_context` extend this to provide
38 execution facilities like event loops and thread pools. Derived
39 class destructors must call `shutdown()` and `destroy()` to ensure
40 proper service cleanup before member destruction.
41
42 @par Service Lifecycle
43 Services are created on first use via `use_service()` or explicitly
44 via `make_service()`. During destruction, `shutdown()` is called on
45 each service in reverse order of creation, then `destroy()` deletes
46 them. Both functions are idempotent.
47
48 @par Thread Safety
49 Service registration and lookup functions are thread-safe.
50 The `shutdown()` and `destroy()` functions are not thread-safe
51 and must only be called during destruction.
52
53 @par Example
54 @code
55 struct file_service : execution_context::service
56 {
57 protected:
58 void shutdown() override {}
59 };
60
61 struct posix_file_service : file_service
62 {
63 using key_type = file_service;
64
65 explicit posix_file_service(execution_context&) {}
66 };
67
68 class io_context : public execution_context
69 {
70 public:
71 ~io_context()
72 {
73 shutdown();
74 destroy();
75 }
76 };
77
78 io_context ctx;
79 ctx.make_service<posix_file_service>();
80 ctx.find_service<file_service>(); // returns posix_file_service*
81 ctx.find_service<posix_file_service>(); // also works
82 @endcode
83
84 @see service, ExecutionContext
85 */
86 class BOOST_CAPY_DECL
87 execution_context
88 {
89 detail::type_info const* ti_ = nullptr;
90
91 template<class T, class = void>
92 struct get_key : std::false_type
93 {};
94
95 template<class T>
96 struct get_key<T, std::void_t<typename T::key_type>> : std::true_type
97 {
98 using type = typename T::key_type;
99 };
100 protected:
101 /** Construct from the most-derived context type.
102
103 Records the dynamic type of the context so that
104 @ref target can later downcast `this` to the
105 requested derived type. Derived classes must pass
106 `this` typed as the most-derived type (i.e. invoke
107 this constructor from the most-derived class with
108 `this` of that type). Passing a pointer typed as a
109 base class records the wrong type and causes
110 `target<Derived>()` to return `nullptr`.
111
112 @tparam Derived The most-derived context type.
113
114 @param self `this`, typed as the most-derived context type.
115 Only its type is recorded; the pointer is not stored.
116 */
117 template< typename Derived >
118 explicit execution_context( Derived* self ) noexcept;
119
120 public:
121 //------------------------------------------------
122
123 /** Gives a derived service a `shutdown()` hook, run when its owning `execution_context` is destroyed.
124
125 Services provide extensible functionality to an execution context.
126 Each service type can be registered at most once. Services are
127 created via `use_service()` or `make_service()` and are owned by
128 the execution context for their lifetime.
129
130 Derived classes must implement the pure virtual `shutdown()` member
131 function, which is called when the owning execution context is
132 being destroyed. The `shutdown()` function should release resources
133 and cancel outstanding operations without blocking.
134
135 @par Deriving from service
136 @li Implement `shutdown()` to perform cleanup.
137 @li Accept `execution_context&` as the first constructor parameter.
138 @li Optionally define `key_type` to enable base-class lookup.
139
140 @par Example
141 @code
142 struct my_service : execution_context::service
143 {
144 explicit my_service(execution_context&) {}
145
146 protected:
147 void shutdown() override
148 {
149 // Cancel pending operations, release resources
150 }
151 };
152 @endcode
153
154 @see execution_context
155 */
156 class BOOST_CAPY_DECL
157 service
158 {
159 public:
160 /// Destructor.
161 52x virtual ~service() = default;
162
163 protected:
164 /// Construct a service. Only derived classes may do so.
165 52x service() = default;
166
167 /** Called when the owning execution context shuts down.
168
169 Implementations should release resources and cancel any
170 outstanding asynchronous operations. This function must
171 not block and must not throw exceptions. Services are
172 shut down in reverse order of creation.
173
174 @par Exception Safety
175 No-throw guarantee.
176 */
177 virtual void shutdown() = 0;
178
179 private:
180 friend class execution_context;
181
182 service* next_ = nullptr;
183
184 // warning C4251: 'std::type_index' needs to have dll-interface
185 BOOST_CAPY_MSVC_WARNING_PUSH
186 BOOST_CAPY_MSVC_WARNING_DISABLE(4251)
187 detail::type_index t0_{detail::type_id<void>()};
188 detail::type_index t1_{detail::type_id<void>()};
189 BOOST_CAPY_MSVC_WARNING_POP
190 };
191
192 //------------------------------------------------
193
194 /** Copy construction is disabled; a context owns its services.
195
196 @param other The context that would be copied.
197 */
198 execution_context(execution_context const& other) = delete;
199
200 /** Copy assignment is disabled; a context owns its services.
201
202 @param other The context that would be assigned from.
203
204 @return A reference to `*this`.
205 */
206 execution_context& operator=(execution_context const& other) = delete;
207
208 /** Destructor.
209
210 Calls `shutdown()` then `destroy()` to clean up all services.
211
212 @par Effects
213 All services are shut down and deleted in reverse order
214 of creation.
215
216 @par Exception Safety
217 No-throw guarantee.
218 */
219 ~execution_context();
220
221 /** Construct a default instance.
222
223 @par Exception Safety
224 Strong guarantee.
225 */
226 execution_context();
227
228 /** Return true if a service of type T exists.
229
230 @par Thread Safety
231 Thread-safe.
232
233 @tparam T The type of service to check.
234
235 @return `true` if the service exists.
236 */
237 template<class T>
238 16x bool has_service() const noexcept
239 {
240 16x return find_service<T>() != nullptr;
241 }
242
243 /** Return a pointer to the service of type T, or nullptr.
244
245 @par Thread Safety
246 Thread-safe.
247
248 @tparam T The type of service to find.
249
250 @return A pointer to the service, or `nullptr` if not present.
251 */
252 template<class T>
253 25x T* find_service() const noexcept
254 {
255 25x std::lock_guard<std::mutex> lock(mutex_);
256 25x return static_cast<T*>(find_impl(detail::type_id<T>()));
257 25x }
258
259 /** Return a reference to the service of type T, creating it if needed.
260
261 If no service of type T exists, one is created by calling
262 `T(execution_context&)`. If T has a nested `key_type`, the
263 service is also indexed under that type.
264
265 @par Constraints
266 @li `T` must derive from `service`.
267 @li `T` must be constructible from `execution_context&`.
268
269 @par Exception Safety
270 Strong guarantee. If service creation throws, the container
271 is unchanged.
272
273 @par Thread Safety
274 Thread-safe.
275
276 @tparam T The type of service to retrieve or create.
277
278 @return A reference to the service.
279 */
280 template<class T>
281 11465x T& use_service()
282 {
283 static_assert(std::is_base_of<service, T>::value,
284 "T must derive from service");
285 static_assert(std::is_constructible<T, execution_context&>::value,
286 "T must be constructible from execution_context&");
287
288 struct impl : factory
289 {
290 11450x impl()
291 : factory(
292 detail::type_id<T>(),
293 get_key<T>::value
294 ? detail::type_id<typename get_key<T>::type>()
295 11450x : detail::type_id<T>())
296 {
297 11450x }
298
299 36x service* create(execution_context& ctx) override
300 {
301 36x return new T(ctx);
302 }
303 };
304
305 11465x impl f;
306 22930x return static_cast<T&>(use_service_impl(f));
307 }
308
309 /** Construct and add a service.
310
311 A new service of type T is constructed using the provided
312 arguments and added to the container. If T has a nested
313 `key_type`, the service is also indexed under that type.
314
315 @par Constraints
316 @li `T` must derive from `service`.
317 @li `T` must be constructible from `execution_context&, Args...`.
318 @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
319
320 @par Exception Safety
321 Strong guarantee. If service creation throws, the container
322 is unchanged.
323
324 @par Thread Safety
325 Thread-safe.
326
327 @throws std::invalid_argument if a service of the same type
328 or `key_type` already exists.
329
330 @tparam T The type of service to create.
331
332 @param args Arguments forwarded to the constructor of T.
333
334 @return A reference to the created service.
335 */
336 template<class T, class... Args>
337 12x T& make_service(Args&&... args)
338 {
339 static_assert(std::is_base_of<service, T>::value,
340 "T must derive from service");
341 if constexpr(get_key<T>::value)
342 {
343 static_assert(
344 std::is_convertible<T&, typename get_key<T>::type&>::value,
345 "T& must be convertible to key_type&");
346 }
347
348 struct impl : factory
349 {
350 std::tuple<Args&&...> args_;
351
352 2x explicit impl(Args&&... a)
353 : factory(
354 detail::type_id<T>(),
355 get_key<T>::value
356 ? detail::type_id<typename get_key<T>::type>()
357 : detail::type_id<T>())
358 2x , args_(std::forward<Args>(a)...)
359 {
360 2x }
361
362 1x service* create(execution_context& ctx) override
363 {
364 2x return std::apply([&ctx](auto&&... a) {
365 1x return new T(ctx, std::forward<decltype(a)>(a)...);
366 3x }, std::move(args_));
367 }
368 };
369
370 12x impl f(std::forward<Args>(args)...);
371 20x return static_cast<T&>(make_service_impl(f));
372 }
373
374 //------------------------------------------------
375
376 /** Return the memory resource used for coroutine frame allocation.
377
378 The returned pointer is valid for the lifetime of this context.
379 By default, this returns a pointer to the recycling memory
380 resource which pools frame allocations for reuse.
381
382 @return Pointer to the frame allocator.
383
384 @see set_frame_allocator
385 */
386 std::pmr::memory_resource*
387 1924x get_frame_allocator() const noexcept
388 {
389 1924x return frame_alloc_;
390 }
391
392 /** Set the memory resource used for coroutine frame allocation.
393
394 The caller is responsible for ensuring the memory resource
395 remains valid for the lifetime of all coroutines started
396 using this context's executor.
397
398 @par Thread Safety
399 Not thread-safe. Must not be called while any thread may
400 be referencing this execution context or its executor.
401
402 @param mr Pointer to the memory resource.
403
404 @see get_frame_allocator
405 */
406 void
407 1x set_frame_allocator(std::pmr::memory_resource* mr) noexcept
408 {
409 1x owned_.reset();
410 1x frame_alloc_ = mr;
411 1x }
412
413 /** Set the frame allocator from a standard Allocator.
414
415 The allocator is wrapped in an internal memory resource
416 adapter owned by this context. The wrapper remains valid
417 for the lifetime of this context or until a subsequent
418 call to set_frame_allocator.
419
420 @par Thread Safety
421 Not thread-safe. Must not be called while any thread may
422 be referencing this execution context or its executor.
423
424 @tparam Allocator The allocator type satisfying the
425 standard Allocator requirements.
426
427 @param a The allocator to use.
428
429 @see get_frame_allocator
430 */
431 template<class Allocator>
432 requires (!std::is_pointer_v<Allocator>)
433 void
434 389x set_frame_allocator(Allocator const& a)
435 {
436 static_assert(
437 requires { typename std::allocator_traits<Allocator>::value_type; },
438 "Allocator must satisfy allocator requirements");
439 static_assert(
440 std::is_copy_constructible_v<Allocator>,
441 "Allocator must be copy constructible");
442
443 389x auto p = std::make_shared<
444 detail::frame_memory_resource<Allocator>>(a);
445 389x frame_alloc_ = p.get();
446 389x owned_ = std::move(p);
447 389x }
448
449 /** Return a pointer to this context if it matches the
450 requested type.
451
452 Performs a type check and downcasts `this` when the
453 types match, or returns `nullptr` otherwise. Analogous
454 to `std::any_cast< ExecutionContext >( &a )`.
455
456 @tparam ExecutionContext The derived context type to
457 retrieve.
458
459 @return A pointer to this context as the requested
460 type, or `nullptr` if the type does not match.
461 */
462 template< typename ExecutionContext >
463 2x const ExecutionContext* target() const
464 {
465 2x if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
466 1x return static_cast< ExecutionContext const* >( this );
467 1x return nullptr;
468 }
469
470 /// @copydoc target() const
471 template< typename ExecutionContext >
472 2x ExecutionContext* target()
473 {
474 2x if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
475 1x return static_cast< ExecutionContext* >( this );
476 1x return nullptr;
477 }
478
479 protected:
480 /** Shut down all services.
481
482 Calls `shutdown()` on each service in reverse order of creation.
483 After this call, services remain allocated but are in a stopped
484 state. Derived classes should call this in their destructor
485 before any members are destroyed. This function is idempotent;
486 subsequent calls have no effect.
487
488 @par Effects
489 Each service's `shutdown()` member function is invoked once.
490
491 @par Postconditions
492 @li All services are in a stopped state.
493
494 @par Exception Safety
495 No-throw guarantee.
496
497 @par Thread Safety
498 Not thread-safe. Must not be called concurrently with other
499 operations on this execution_context.
500 */
501 void shutdown() noexcept;
502
503 /** Destroy all services.
504
505 Deletes all services in reverse order of creation. Derived
506 classes should call this as the final step of destruction.
507 This function is idempotent; subsequent calls have no effect.
508
509 @par Preconditions
510 @li `shutdown()` was called.
511
512 @par Effects
513 All services are deleted and removed from the container.
514
515 @par Postconditions
516 @li The service container is empty.
517
518 @par Exception Safety
519 No-throw guarantee.
520
521 @par Thread Safety
522 Not thread-safe. Must not be called concurrently with other
523 operations on this execution_context.
524 */
525 void destroy() noexcept;
526
527 private:
528 struct BOOST_CAPY_DECL
529 factory
530 {
531 // warning C4251: 'std::type_index' needs to have dll-interface
532 BOOST_CAPY_MSVC_WARNING_PUSH
533 BOOST_CAPY_MSVC_WARNING_DISABLE(4251)
534 detail::type_index t0;
535 detail::type_index t1;
536 BOOST_CAPY_MSVC_WARNING_POP
537
538 11477x factory(
539 detail::type_info const& t0_,
540 detail::type_info const& t1_)
541 11477x : t0(t0_), t1(t1_)
542 {
543 11477x }
544
545 virtual service* create(execution_context&) = 0;
546
547 protected:
548 ~factory() = default;
549 };
550
551 service* find_impl(detail::type_index ti) const noexcept;
552 service& use_service_impl(factory& f);
553 service& make_service_impl(factory& f);
554
555 // warning C4251: std::mutex, std::shared_ptr need dll-interface
556 BOOST_CAPY_MSVC_WARNING_PUSH
557 BOOST_CAPY_MSVC_WARNING_DISABLE(4251)
558 mutable std::mutex mutex_;
559 std::shared_ptr<void> owned_;
560 BOOST_CAPY_MSVC_WARNING_POP
561 std::pmr::memory_resource* frame_alloc_ = nullptr;
562 service* head_ = nullptr;
563 bool shutdown_ = false;
564 };
565
566 template< typename Derived >
567 29x execution_context::
568 execution_context( Derived* ) noexcept
569 29x : execution_context()
570 {
571 29x ti_ = &detail::type_id< Derived >();
572 29x }
573
574 } // namespace capy
575 } // namespace boost
576
577 #endif
578