GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/polystore.hpp
Date: 2025-12-15 05:33:30
Exec Total Coverage
Lines: 76 89 85.4%
Functions: 130 193 67.4%
Branches: 23 24 95.8%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
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)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_POLYSTORE_HPP
11 #define BOOST_CAPY_POLYSTORE_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/detail/call_traits.hpp>
15 #include <boost/capy/detail/except.hpp>
16 #include <boost/capy/detail/type_traits.hpp>
17 #include <boost/core/typeinfo.hpp>
18 #include <boost/core/detail/static_assert.hpp>
19 #include <cstring>
20 #include <memory>
21 #include <type_traits>
22 #include <unordered_map>
23 #include <vector>
24
25 #if ! defined( BOOST_NO_TYPEID )
26 #include <typeindex>
27 #endif
28
29 namespace boost {
30 namespace capy {
31
32 namespace detail {
33
34 #if defined( BOOST_NO_TYPEID )
35
36 struct typeindex
37 {
38 typeindex(
39 core::typeinfo const& ti) noexcept
40 : n_(std::strlen(ti.name()))
41 , ti_(&ti)
42 {
43 }
44
45 std::size_t hash_code() const noexcept
46 {
47 constexpr std::size_t offset_basis =
48 (sizeof(std::size_t) == 8)
49 ? 1469598103934665603ull
50 : 2166136261u;
51 constexpr std::size_t prime =
52 (sizeof(std::size_t) == 8)
53 ? 1099511628211ull
54 : 16777619u;
55 auto const s = ti_->name();
56 std::size_t h = offset_basis;
57 for(std::size_t i = 0; i < n_; ++i)
58 h = (h ^ static_cast<unsigned char>(s[i])) * prime;
59 return h;
60 }
61
62 bool operator==(typeindex const& other) const noexcept
63 {
64 return n_ == other.n_ && *ti_ == *other.ti_;
65 }
66
67 private:
68 std::size_t n_;
69 core::typeinfo const* ti_;
70 };
71
72 } // detail
73 } // capy
74 } // boost
75 namespace std {
76 template<>
77 struct hash< boost::capy::detail::typeindex >
78 {
79 std::size_t operator()(
80 boost::capy::detail::typeindex const& t) const noexcept
81 {
82 return t.hash_code();
83 }
84 };
85 } // std
86 namespace boost {
87 namespace capy {
88 namespace detail {
89
90 #else
91
92 using typeindex = std::type_index;
93
94 #endif
95
96 } // detail
97
98 /** A container of type-erased objects
99
100 Objects are stored and retrieved by their type.
101 Each type may be stored at most once. Types
102 may specify a nested `key_type` to be used
103 as the unique identifier instead of the type
104 itself. In this case, a reference to the type
105 must be convertible to a reference to the key type.
106
107 @par Example
108 @code
109 struct A
110 {
111 int i = 1;
112 };
113 struct B
114 {
115 char c = '2';
116 };
117 struct C
118 {
119 double d;
120 };
121 struct D : C
122 {
123 using key_type = C;
124 D()
125 {
126 d = 3.14;
127 }
128 };
129 polystore ps;
130 A& a = ps.emplace<A>();
131 B& b = ps.insert(B{});
132 C& c = ps.emplace<C>();
133 assert(ps.get<A>().i == 1);
134 assert(ps.get<B>().c == '2');
135 assert(ps.get<C>().d == 3.14);
136 invoke(ps, [](A& a){ a.i = 0; });
137 invoke(ps, [](A const&, B& b){ b.c = 0; });
138 assert(ps.get<A>().i == 0);
139 assert(ps.get<B>().c == 0);
140 @endcode
141 */
142 class polystore
143 {
144 template<class T, class = void>
145 struct get_key : std::false_type
146 {
147 };
148
149 template<class T>
150 struct get_key<T, typename std::enable_if<
151 ! std::is_same<T, typename T::key_type>::value>::type>
152 : std::true_type
153 {
154 using type = typename T::key_type;
155 };
156
157 public:
158 /** Destructor
159
160 All objects stored in the container are destroyed in
161 the reverse order of construction.
162 */
163 BOOST_CAPY_DECL
164 ~polystore();
165
166 /** Constructor
167 The moved-from container will be empty.
168 */
169 BOOST_CAPY_DECL
170 polystore(polystore&& other) noexcept;
171
172 /** Assignment operator
173 The moved-from container will be empty.
174 @return A reference to `*this`.
175 */
176 BOOST_CAPY_DECL
177 polystore& operator=(polystore&& other) noexcept;
178
179 /** Constructor
180 The container is initially empty.
181 */
182 12 polystore() = default;
183
184 /** Return a pointer to the object associated with type `T`, or `nullptr`
185
186 If no object associated with `T` exists in the container,
187 `nullptr` is returned.
188
189 @par Thread Safety
190 `const` member function calls are thread-safe.
191 Calls to non-`const` member functions must not run concurrently
192 with other member functions on the same object.
193
194 @tparam T The type of object to find.
195 @return A pointer to the associated object, or `nullptr` if none exists.
196 */
197 template<class T>
198 129 T* find() const noexcept
199 {
200 129 return static_cast<T*>(find(BOOST_CORE_TYPEID(T)));
201 }
202
203 /** Assign the pointer for the object associated with `T`, or `nullptr`.
204
205 If no object of type `T` is stored, @p t is set to `nullptr`.
206
207 @par Thread Safety
208 `const` member functions are thread-safe. Non-`const` functions
209 must not run concurrently with any other member function on the
210 same instance.
211
212 @param t The pointer to assign.
213 @return `true` if an object of type `T` is present, otherwise `false`.
214 */
215 template<class T>
216 bool find(T*& t) const noexcept
217 {
218 t = find<T>();
219 return t != nullptr;
220 }
221
222 /** Return a reference to the object associated with type T
223
224 If no such object exists in the container, an exception is thrown.
225
226 @par Exception Safety
227 Strong guarantee.
228
229 @par Thread Safety
230 Calls to `const` member functions are thread-safe.
231 Calls to non-`const` member functions must not run concurrently
232 with other member functions on the same object.
233
234 @throws std::bad_typeid
235 If no object associated with type `T` is present.
236 @tparam T The type of object to retrieve.
237 @return A reference to the associated object.
238 */
239 template<class T>
240 68 T& get() const
241 {
242
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 1 times.
68 if(auto t = find<T>())
243 66 return *t;
244 2 detail::throw_bad_typeid();
245 }
246
247 /** Construct and insert an anonymous object into the container
248
249 A new object of type `T` is constructed in place using the provided
250 arguments and inserted into the container without associating it
251 with any key. A reference to the stored object is returned.
252
253 @par Exception Safety
254 Strong guarantee.
255
256 @par Thread Safety
257 Not thread-safe.
258
259 @tparam T The type of object to construct and insert.
260 @param args Arguments forwarded to the constructor of `T`.
261 @return A reference to the inserted object.
262 */
263 template<class T, class... Args>
264 2 T& emplace_anon(Args&&... args)
265 {
266
2/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
4 return *static_cast<T*>(insert_impl(
267 4 make_any<T>(std::forward<Args>(args)...)));
268 }
269
270 /** Insert an anonymous object by moving or copying it into the container
271
272 A new object of type `T` is inserted into the container without
273 associating it with any key. The object is move-constructed or
274 copy-constructed from the provided argument, and a reference to
275 the stored object is returned.
276
277 @par Exception Safety
278 Strong guarantee.
279
280 @par Thread Safety
281 Not thread-safe.
282
283 @tparam T The type of object to insert.
284 @param t The object to insert.
285 @return A reference to the inserted object.
286 */
287 template<class T>
288 T& insert_anon(T&& t)
289 {
290 return emplace_anon<typename
291 std::remove_cv<T>::type>(
292 std::forward<T>(t));
293 }
294
295 /** Construct and insert an object with a nested key type
296
297 A new object of type `T` is constructed in place using the provided
298 arguments and inserted into the container. The type `T` must define
299 a nested type `key_type`, which is used as the key for insertion.
300 No additional key types may be specified. The type `T&` must be
301 convertible to a reference to `key_type`.
302
303 @par Constraints
304 `T::key_type` must name a type.
305
306 @par Exception Safety
307 Strong guarantee.
308
309 @par Thread Safety
310 Not thread-safe.
311
312 @throws std::invalid_argument On duplicate insertion.
313 @tparam T The type of object to construct and insert.
314 @param args Arguments forwarded to the constructor of `T`.
315 @return A reference to the inserted object.
316 */
317 template<class T, class... Keys, class... Args>
318 8 auto emplace(Args&&... args) ->
319 typename std::enable_if<get_key<T>::value, T&>::type
320 {
321 // Can't have Keys with nested key_type
322 BOOST_CORE_STATIC_ASSERT(sizeof...(Keys) == 0);
323 // T& must be convertible to key_type&
324 BOOST_CORE_STATIC_ASSERT(std::is_convertible<
325 T&, typename get_key<T>::type&>::value);
326
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
8 auto p = make_any<T>(std::forward<Args>(args)...);
327 8 keyset<T, typename get_key<T>::type> ks(
328 8 *static_cast<T*>(p->get()));
329
1/1
✓ Branch 2 taken 4 times.
16 return *static_cast<T*>(insert_impl(
330 20 std::move(p), ks.kn, ks.N));
331 8 }
332
333 /** Construct and insert an object into the container
334
335 A new object of type `T` is constructed in place using the provided
336 arguments and inserted into the container. The type `T` must not
337 already exist in the container, nor may any of the additional key
338 types refer to an existing object. The type `T&` must be convertible
339 to a reference to each specified key type.
340
341 @par Constraints
342 `T::key_type` must not name a type.
343
344 @par Exception Safety
345 Strong guarantee.
346
347 @par Thread Safety
348 Not thread-safe.
349
350 @throws std::invalid_argument On duplicate insertion.
351 @tparam T The type of object to construct and insert.
352 @tparam Keys Optional key types associated with the object.
353 @param args Arguments forwarded to the constructor of `T`.
354 @return A reference to the inserted object.
355 */
356 template<class T, class... Keys, class... Args>
357 17 auto emplace(Args&&... args) ->
358 typename std::enable_if<! get_key<T>::value, T&>::type
359 {
360 // T& must be convertible to each of Keys&
361 BOOST_CORE_STATIC_ASSERT(all_true<std::is_convertible<
362 T&, Keys&>::value...>::value);
363
1/1
✓ Branch 1 taken 8 times.
17 auto p = make_any<T>(std::forward<Args>(args)...);
364 17 keyset<T, Keys...> ks(*static_cast<T*>(p->get()));
365
1/1
✓ Branch 2 taken 6 times.
34 return *static_cast<T*>(insert_impl(
366 39 std::move(p), ks.kn, ks.N));
367 17 }
368
369 /** Return an existing object, creating it if necessary
370
371 If an object of the exact type `T` already exists in the container,
372 a reference to that object is returned. Otherwise, a new object is
373 constructed in place using the provided arguments, and a reference
374 to the newly created object is returned. The type `T` must not
375 already exist in the container, nor may any of the additional key
376 types refer to an existing object. The type `T` must be convertible
377 to a reference to each additional key type.
378
379 @par Exception Safety
380 Strong guarantee.
381
382 @par Thread Safety
383 Not thread-safe.
384
385 @throws std::invalid_argument On duplicate insertion.
386 @tparam T The type of object to return or create.
387 @tparam Keys Optional key types associated with the object.
388 @param args Arguments forwarded to the constructor of `T`.
389 @return A reference to the existing or newly created object.
390 */
391 template<class T, class... Keys, class... Args>
392 2 auto try_emplace(Args&&... args) ->
393 typename std::enable_if<get_key<T>::value, T&>::type
394 {
395 // Can't have Keys with nested key_type
396 BOOST_CORE_STATIC_ASSERT(sizeof...(Keys) == 0);
397 // T& must be convertible to key_type&
398 BOOST_CORE_STATIC_ASSERT(std::is_convertible<
399 T&, typename get_key<T>::type&>::value);
400
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if(auto t = find<T>())
401 1 return *t;
402
1/1
✓ Branch 1 taken 1 times.
1 auto p = make_any<T>(std::forward<Args>(args)...);
403 1 keyset<T, typename get_key<T>::type> ks(
404 1 *static_cast<T*>(p->get()));
405
1/1
✓ Branch 2 taken 1 times.
2 return *static_cast<T*>(insert_impl(
406 2 std::move(p), ks.kn, ks.N));
407 1 }
408
409 /** Return an existing object, creating it if necessary
410
411 If an object of the exact type `T` already exists in the container,
412 a reference to that object is returned. Otherwise, a new object is
413 constructed in place using the provided arguments, and a reference
414 to the newly created object is returned. The type `T` must not
415 already exist in the container, nor may any of the additional key
416 types refer to an existing object. The type `T` must be convertible
417 to a reference to each additional key type.
418
419 @par Exception Safety
420 Strong guarantee.
421
422 @par Thread Safety
423 `const` member function calls are thread-safe.
424 Calls to non-`const` member functions must not run concurrently
425 with other member functions on the same object.
426
427 @throws std::invalid_argument On duplicate insertion.
428 @tparam T The type of object to return or create.
429 @tparam Keys Optional key types associated with the object.
430 @param args Arguments forwarded to the constructor of `T`.
431 @return A reference to the existing or newly created object.
432 */
433 template<class T, class... Keys, class... Args>
434 2 auto try_emplace(Args&&... args) ->
435 typename std::enable_if<! get_key<T>::value, T&>::type
436 {
437 // T& must be convertible to each of Keys&
438 BOOST_CORE_STATIC_ASSERT(all_true<std::is_convertible<
439 T&, Keys&>::value...>::value);
440
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if(auto t = find<T>())
441 1 return *t;
442
1/1
✓ Branch 1 taken 1 times.
1 auto p = make_any<T>(std::forward<Args>(args)...);
443 1 keyset<T, Keys...> ks(*static_cast<T*>(p->get()));
444
1/1
✓ Branch 2 taken 1 times.
2 return *static_cast<T*>(insert_impl(
445 2 std::move(p), ks.kn, ks.N));
446 1 }
447
448 /** Insert an object by moving or copying it into the container
449
450 If an object of the same type `T` already exists in the container,
451 or if any of the additional key types would refer to an existing
452 object, an exception is thrown. Otherwise, the object is inserted
453 by move or copy construction, and a reference to the stored object
454 is returned. The type `T` must be convertible to a reference to each
455 additional key type.
456
457 @par Exception Safety
458 Strong guarantee.
459
460 @par Thread Safety
461 Not thread-safe.
462
463 @throws std::invalid_argument On duplicate insertion.
464 @tparam T The type of object to insert.
465 @tparam Keys Optional key types associated with the object.
466 @param t The object to insert.
467 @return A reference to the inserted object.
468 */
469 template<class T, class... Keys>
470 1 T& insert(T&& t)
471 {
472 return emplace<typename
473 1 std::remove_cv<T>::type, Keys...>(
474 1 std::forward<T>(t));
475 }
476
477 /** Return an existing object or create a new one
478
479 If an object of the exact type `T` already exists in the container,
480 a reference to that object is returned. Otherwise, a new object of
481 type `T` is default-constructed in the container, and a reference
482 to the newly created object is returned. This function ignores
483 nested key types and cannot be used to specify additional keys.
484
485 @par Constraints
486 `T` must be default-constructible.
487
488 @par Exception Safety
489 Strong guarantee.
490
491 @par Thread Safety
492 Not thread-safe.
493
494 @tparam T The type of object to retrieve or create.
495 @return A reference to the stored object.
496 */
497 template<class T>
498 4 T& use()
499 {
500 // T must be default constructible
501 BOOST_CORE_STATIC_ASSERT(
502 std::is_default_constructible<T>::value);
503
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
4 if(auto t = find<T>())
504 return *t;
505 4 return emplace<T>();
506 }
507
508 protected:
509 struct any;
510 class elements;
511
512 /** Remove and destroy all objects in the container.
513
514 All stored objects are destroyed in the reverse order
515 of construction. The container is left empty.
516 */
517 BOOST_CAPY_DECL
518 void
519 clear() noexcept;
520
521 /** Return a range of all stored elements
522 @par Thread Safety
523 `const` member function calls are thread-safe.
524 Calls to non-`const` member functions must not run concurrently
525 with other member functions on the same object.
526 @return An object representing the range of stored elements.
527 */
528 BOOST_CAPY_DECL
529 elements
530 get_elements() noexcept;
531
532 private:
533 template<bool...> struct bool_pack {};
534 template<bool... Bs>
535 struct all_true : std::is_same<bool_pack<
536 true, Bs...>, bool_pack<Bs..., true>> {};
537
538 template<class T, class = void>
539 struct has_start : std::false_type {};
540
541 template<class T>
542 struct has_start<T, typename std::enable_if<
543 std::is_same<decltype(std::declval<T>().start()),
544 void>::value>::type> : std::true_type {};
545
546 template<class T, class = void>
547 struct has_stop : std::false_type {};
548
549 template<class T>
550 struct has_stop<T, typename std::enable_if<
551 std::is_same<decltype(std::declval<T>().stop()),
552 void>::value>::type> : std::true_type {};
553
554 struct key
555 {
556 detail::typeindex ti =
557 detail::typeindex(BOOST_CORE_TYPEID(void));
558 void* p = nullptr;
559
560 8 key() = default;
561 26 key(detail::typeindex const& ti_,
562 26 void* p_) noexcept : ti(ti_) , p(p_) {}
563 };
564
565 template<class T, class... Key>
566 struct keyset;
567
568 template<class T>
569 struct keyset<T>
570 {
571 static constexpr std::size_t N = 1;
572 key kn[1];
573
574 15 explicit keyset(T& t) noexcept
575 15 : kn{ key(detail::typeindex(BOOST_CORE_TYPEID(T)), &t) }
576 {
577 15 }
578 };
579
580 template<class T, class... Keys>
581 struct keyset
582 {
583 static constexpr std::size_t N = 1 + sizeof...(Keys);
584 key kn[N + 1];
585
586 14 explicit keyset(T& t) noexcept
587
3/3
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 2 times.
46 : kn{
588 14 key(detail::typeindex(BOOST_CORE_TYPEID(T)),
589 14 std::addressof(t)),
590 18 key(detail::typeindex(BOOST_CORE_TYPEID(Keys)),
591 8 &static_cast<Keys&>(t))..., }
592 {
593 14 }
594 };
595
596 template<class T> struct any_impl;
597
598 using any_ptr = std::unique_ptr<any>;
599
600 template<class T, class... Args>
601 auto
602 33 make_any(Args&&... args) ->
603 std::unique_ptr<any_impl<T>>
604 {
605 37 return std::unique_ptr<any_impl<T>>(new
606 37 any_impl<T>(std::forward<Args>(args)...));
607 }
608
609 void destroy() noexcept;
610 BOOST_CAPY_DECL any& get(std::size_t i);
611 BOOST_CAPY_DECL void* find(
612 core::typeinfo const& ti) const noexcept;
613 BOOST_CAPY_DECL void* insert_impl(any_ptr,
614 key const* = nullptr, std::size_t = 0);
615
616 std::vector<any_ptr> v_;
617 std::unordered_map<
618 detail::typeindex, void*> m_;
619 };
620
621 //------------------------------------------------
622
623 struct BOOST_CAPY_DECL
624 polystore::any
625 {
626 36 virtual ~any() = default;
627 virtual void start() = 0;
628 virtual void stop() = 0;
629 private:
630 friend class polystore;
631 virtual void* get() noexcept = 0;
632 };
633
634 //------------------------------------------------
635
636 class polystore::elements
637 {
638 public:
639 std::size_t size() const noexcept
640 {
641 return n_;
642 }
643
644 any& operator[](
645 std::size_t i) noexcept
646 {
647 return ps_.get(i);
648 }
649
650 private:
651 friend class polystore;
652
653 elements(
654 std::size_t n,
655 polystore& ps)
656 : n_(n)
657 , ps_(ps)
658 {
659 }
660
661 std::size_t n_;
662 polystore& ps_;
663 };
664
665 //------------------------------------------------
666
667 template<class T>
668 struct polystore::any_impl : polystore::any
669 {
670 T t;
671
672 template<class... Args>
673 33 explicit any_impl(Args&&... args)
674 33 : t(std::forward<Args>(args)...)
675 {
676 33 }
677 62 void* get() noexcept override { return std::addressof(t); }
678 void start() override { do_start(has_start<T>{}); }
679 void stop() override { do_stop(has_stop<T>{}); }
680 void do_start(std::true_type) { t.start(); }
681 void do_start(std::false_type) {}
682 void do_stop(std::true_type) { t.stop(); }
683 void do_stop(std::false_type) {}
684 };
685
686 //------------------------------------------------
687
688 namespace detail {
689
690 template<class T> struct arg;
691 template<class T> struct arg<T const&> : arg<T&> {};
692 template<class T> struct arg<T const*> : arg<T*> {};
693 template<class T> struct arg<T&>
694 {
695 16 T& operator()(polystore& ps) const
696 {
697 16 return ps.get<T>();
698 }
699 };
700 template<class T> struct arg<T*>
701 {
702 10 T* operator()(polystore& ps) const
703 {
704 10 return ps.find<T>();
705 }
706 };
707
708 template<class F, class... Args>
709 auto
710 20 invoke(polystore& ps, F&& f,
711 mp11::mp_list<Args...> const&) ->
712 typename detail::call_traits<typename
713 std::decay<F>::type>::return_type
714 {
715
2/2
✓ Branch 2 taken 5 times.
✓ Branch 8 taken 2 times.
20 return std::forward<F>(f)(arg<Args>()(ps)...);
716 }
717
718 } // detail
719
720 /** Invoke a callable, injecting stored objects as arguments
721 The callable is invoked with zero or more arguments.
722 For each argument type, if an object of that type
723 (or key type) is stored in the container, a reference
724 to that object is passed to the callable.
725 @par Example
726 @code
727 struct A { int i = 1; };
728 polystore ps;
729 ps.emplace<A>();
730 ps.invoke([](A& a){ assert(a.i == 1; });
731 @endcode
732 @param f The callable to invoke.
733 @return The result of the invocation.
734 @throws std::bad_typeid if any reference argument
735 types are not found in the container.
736 */
737 template<class F>
738 auto
739 10 invoke(polystore& ps, F&& f) ->
740 typename detail::call_traits<
741 typename std::decay<F>::type>::return_type
742 {
743 20 return detail::invoke(ps, std::forward<F>(f),
744 typename detail::call_traits< typename
745 20 std::decay<F>::type>::arg_types{});
746 }
747
748 } // capy
749 } // boost
750
751 #endif
752