Line data Source code
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 66 : T* find() const noexcept
199 : {
200 66 : 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 34 : T& get() const
241 : {
242 34 : if(auto t = find<T>())
243 33 : return *t;
244 1 : 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 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 5 : 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 5 : auto p = make_any<T>(std::forward<Args>(args)...);
327 5 : keyset<T, typename get_key<T>::type> ks(
328 5 : *static_cast<T*>(p->get()));
329 10 : return *static_cast<T*>(insert_impl(
330 13 : std::move(p), ks.kn, ks.N));
331 5 : }
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 9 : 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 9 : auto p = make_any<T>(std::forward<Args>(args)...);
364 9 : keyset<T, Keys...> ks(*static_cast<T*>(p->get()));
365 18 : return *static_cast<T*>(insert_impl(
366 21 : std::move(p), ks.kn, ks.N));
367 9 : }
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 : if(auto t = find<T>())
401 1 : return *t;
402 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 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 : if(auto t = find<T>())
441 1 : return *t;
442 1 : auto p = make_any<T>(std::forward<Args>(args)...);
443 1 : keyset<T, Keys...> ks(*static_cast<T*>(p->get()));
444 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 2 : T& use()
499 : {
500 : // T must be default constructible
501 : BOOST_CORE_STATIC_ASSERT(
502 : std::is_default_constructible<T>::value);
503 2 : if(auto t = find<T>())
504 0 : return *t;
505 2 : 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 8 : explicit keyset(T& t) noexcept
575 8 : : kn{ key(detail::typeindex(BOOST_CORE_TYPEID(T)), &t) }
576 : {
577 8 : }
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 8 : explicit keyset(T& t) noexcept
587 26 : : kn{
588 8 : key(detail::typeindex(BOOST_CORE_TYPEID(T)),
589 8 : std::addressof(t)),
590 10 : key(detail::typeindex(BOOST_CORE_TYPEID(Keys)),
591 4 : &static_cast<Keys&>(t))..., }
592 : {
593 8 : }
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 18 : make_any(Args&&... args) ->
603 : std::unique_ptr<any_impl<T>>
604 : {
605 21 : return std::unique_ptr<any_impl<T>>(new
606 21 : 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 18 : 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 0 : std::size_t size() const noexcept
640 : {
641 0 : return n_;
642 : }
643 :
644 0 : any& operator[](
645 : std::size_t i) noexcept
646 : {
647 0 : return ps_.get(i);
648 : }
649 :
650 : private:
651 : friend class polystore;
652 :
653 0 : elements(
654 : std::size_t n,
655 : polystore& ps)
656 0 : : n_(n)
657 0 : , ps_(ps)
658 : {
659 0 : }
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 18 : explicit any_impl(Args&&... args)
674 18 : : t(std::forward<Args>(args)...)
675 : {
676 18 : }
677 34 : void* get() noexcept override { return std::addressof(t); }
678 0 : void start() override { do_start(has_start<T>{}); }
679 0 : void stop() override { do_stop(has_stop<T>{}); }
680 : void do_start(std::true_type) { t.start(); }
681 0 : void do_start(std::false_type) {}
682 : void do_stop(std::true_type) { t.stop(); }
683 0 : 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 8 : T& operator()(polystore& ps) const
696 : {
697 8 : return ps.get<T>();
698 : }
699 : };
700 : template<class T> struct arg<T*>
701 : {
702 5 : T* operator()(polystore& ps) const
703 : {
704 5 : return ps.find<T>();
705 : }
706 : };
707 :
708 : template<class F, class... Args>
709 : auto
710 10 : 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 10 : 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
|