Zserio C++17 runtime library 1.3.0
Built for Zserio 2.20.0
Loading...
Searching...
No Matches
Any.h
Go to the documentation of this file.
1#ifndef ZSERIO_ANY_H_INC
2#define ZSERIO_ANY_H_INC
3
4#include <cstddef>
5#include <cstring>
6#include <optional>
7#include <type_traits>
8#include <typeinfo>
9
12#include "zserio/HashCodeUtil.h"
13#include "zserio/Optional.h"
14#include "zserio/RebindAlloc.h"
15#include "zserio/Traits.h"
16#include "zserio/Types.h"
17
18namespace zserio
19{
20
21namespace detail
22{
23
24#if !defined(ZSERIO_TYPEID_STATIC_ONLY)
25 #if !defined(_MSC_VER) && !defined(__GNUC__)
26 #warning "Unsupported compiler, type identification in zserio::Any runs in fallback mode"
27 #define ZSERIO_TYPEID_STATIC_ONLY
28 #endif
29#endif
30
31class TypeIdHolder
32{
33public:
34#if defined(ZSERIO_TYPEID_STATIC_ONLY)
35 using type_id = uintptr_t;
36
37 template <typename T>
38 static uintptr_t get()
39 {
40 static const id = 0;
41 return &id;
42 }
43
44 static bool equals(type_id lhs, type_id rhs)
45 {
46 return lhs == rhs;
47 }
48
49// in MSVC typeid works even when RTTI is disabled
50#elif defined(_MSC_VER) || defined(__GXX_RTTI)
51 using type_id = const std::type_info&;
52
53 template <typename T>
54 static type_id get()
55 {
56 return typeid(T);
57 }
58
59 static bool equals(type_id lhs, type_id rhs)
60 {
61 #if defined(_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION) && _LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION == 3
62 // libc++ on arm64 Apple platforms (see __non_unique_arm_rtti_bit_impl) marks RTTI with hidden
63 // visibility as non-unique and type_info::operator== can then report a type as unequal to itself,
64 // e.g. when a hidden visibility translation unit and a default visibility one use the same type.
65 // Falling back to the type names gives the right answer for such types. The price is that types
66 // with the same name and internal linkage, like types in anonymous namespaces in different
67 // translation units, compare equal there.
68 return lhs == rhs || std::strcmp(lhs.name(), rhs.name()) == 0;
69 #else
70 return lhs == rhs;
71 #endif
72 }
73
74#else
75 using type_id = uintptr_t;
76
77 // returns uintptr_t instead of type_id to keep PRETTY_FUNCTION shorter
78 template <typename T>
79 static uintptr_t get()
80 {
81 // PRETTY_FUNCTION gives a string like:
82 // static uintptr_t zserio::detail::TypeIdHolder::get() [T = int]
83 // static uintptr_t zserio::detail::TypeIdHolder::get() [with T = int; uintptr_t = xyz]
84 static const auto id = calcHashCode(zserio::HASH_SEED, std::string_view(__PRETTY_FUNCTION__));
85 return static_cast<uintptr_t>(id);
86 }
87
88 static bool equals(type_id lhs, type_id rhs)
89 {
90 return lhs == rhs;
91 }
92
93#endif
94};
95
96// Interface for object holders
97template <typename ALLOC>
98class IHolder
99{
100public:
101 virtual ~IHolder() = default;
102 virtual IHolder* clone(const ALLOC& allocator) const = 0;
103 virtual IHolder* clone(void* storage) const = 0;
104 virtual IHolder* move(const ALLOC& allocator) = 0;
105 virtual IHolder* move(void* storage) = 0;
106 virtual void destroy(const ALLOC& allocator) = 0;
107 virtual bool isType(detail::TypeIdHolder::type_id typeId) const = 0;
108};
109
110// Base of object holders, holds a value in the std::optional
111template <typename T, typename ALLOC>
112class HolderBase : public IHolder<ALLOC>
113{
114public:
115 template <typename U>
116 void set(U&& value)
117 {
118 m_typedHolder = std::forward<U>(value);
119 }
120
121 template <typename... ARGS>
122 T& construct(const ALLOC& allocator, ARGS&&... args)
123 {
124 // in case T is allocator-aware (e.g. std::vector) we need to construct
125 // it with parent allocator but because std::optional doesn't support
126 // allocators we emulate uses_allocator construction here
127 if constexpr (!std::uses_allocator_v<T, ALLOC>)
128 {
129 return m_typedHolder.emplace(std::forward<ARGS>(args)...);
130 }
131 else if constexpr (std::is_constructible_v<T, std::allocator_arg_t, const ALLOC&, ARGS...>)
132 {
133 return m_typedHolder.emplace(std::allocator_arg, allocator, std::forward<ARGS>(args)...);
134 }
135 else if constexpr (std::is_constructible_v<T, ARGS..., const ALLOC&>)
136 {
137 return m_typedHolder.emplace(std::forward<ARGS>(args)..., allocator);
138 }
139 else
140 {
141 static_assert(always_false<T>::value,
142 "if uses_allocator_v<remove_cv_t<T>, ALLOC> is true, "
143 "T must be constructible with allocator argument");
144 }
145 }
146
147 void setHolder(const std::optional<T>& value)
148 {
149 m_typedHolder = value;
150 }
151
152 void setHolder(std::optional<T>&& value)
153 {
154 m_typedHolder = std::move(value);
155 }
156
157 T& get()
158 {
159 return m_typedHolder.value();
160 }
161
162 const T& get() const
163 {
164 return m_typedHolder.value();
165 }
166
167 bool isType(detail::TypeIdHolder::type_id typeId) const override
168 {
169 return detail::TypeIdHolder::equals(detail::TypeIdHolder::get<T>(), typeId);
170 }
171
172protected:
173 std::optional<T>& getHolder()
174 {
175 return m_typedHolder;
176 }
177
178 const std::optional<T>& getHolder() const
179 {
180 return m_typedHolder;
181 }
182
183private:
184 std::optional<T> m_typedHolder;
185};
186
187// Holder allocated on heap
188template <typename T, typename ALLOC>
189class HeapHolder : public HolderBase<T, ALLOC>
190{
191private:
192 struct ConstructTag
193 {};
194
195public:
196 using this_type = HeapHolder<T, ALLOC>;
197
198 explicit HeapHolder(ConstructTag) noexcept
199 {}
200
201 static this_type* create(const ALLOC& allocator)
202 {
203 using AllocType = RebindAlloc<ALLOC, this_type>;
204 using AllocTraits = std::allocator_traits<AllocType>;
205
206 AllocType typedAlloc = allocator;
207 typename AllocTraits::pointer ptr = AllocTraits::allocate(typedAlloc, 1);
208 // this never throws because HeapHolder constructor never throws
209 AllocTraits::construct(typedAlloc, std::addressof(*ptr), ConstructTag{});
210 return ptr;
211 }
212
213 IHolder<ALLOC>* clone(const ALLOC& allocator) const override
214 {
215 this_type* holder = create(allocator);
216 holder->setHolder(this->getHolder());
217 return holder;
218 }
219
220 IHolder<ALLOC>* clone(void*) const override
221 {
222 throw CppRuntimeException("BasicAny: Unexpected clone call.");
223 }
224
225 IHolder<ALLOC>* move(const ALLOC& allocator) override
226 {
227 this_type* holder = create(allocator);
228 holder->setHolder(std::move(this->getHolder()));
229 return holder;
230 }
231
232 IHolder<ALLOC>* move(void*) override
233 {
234 throw CppRuntimeException("BasicAny: Unexpected move call.");
235 }
236
237 void destroy(const ALLOC& allocator) override
238 {
239 using AllocType = RebindAlloc<ALLOC, this_type>;
240 using AllocTraits = std::allocator_traits<AllocType>;
241
242 AllocType typedAlloc = allocator;
243 AllocTraits::destroy(typedAlloc, this);
244 AllocTraits::deallocate(typedAlloc, this, 1);
245 }
246};
247
248// Holder allocated in the in-place storage
249template <typename T, typename ALLOC>
250class NonHeapHolder : public HolderBase<T, ALLOC>
251{
252public:
253 using this_type = NonHeapHolder<T, ALLOC>;
254
255 static this_type* create(void* storage)
256 {
257 return new (storage) this_type();
258 }
259
260 IHolder<ALLOC>* clone(const ALLOC&) const override
261 {
262 throw CppRuntimeException("BasicAny: Unexpected clone call.");
263 }
264
265 IHolder<ALLOC>* clone(void* storage) const override
266 {
267 NonHeapHolder* holder = new (storage) NonHeapHolder();
268 holder->setHolder(this->getHolder());
269 return holder;
270 }
271
272 IHolder<ALLOC>* move(const ALLOC&) override
273 {
274 throw CppRuntimeException("BasicAny: Unexpected move call.");
275 }
276
277 IHolder<ALLOC>* move(void* storage) override
278 {
279 NonHeapHolder* holder = new (storage) NonHeapHolder();
280 holder->setHolder(std::move(this->getHolder()));
281 return holder;
282 }
283
284 void destroy(const ALLOC&) override
285 {
286 this->~NonHeapHolder();
287 }
288
289private:
290 NonHeapHolder() = default;
291};
292
293template <typename ALLOC>
294union UntypedHolder
295{
296 // 2 * sizeof(void*) for T + sizeof(void*) for Holder's vptr
297 using MaxInPlaceType = std::aligned_storage<3 * sizeof(void*), alignof(void*)>::type;
298
299 detail::IHolder<ALLOC>* heap;
300 MaxInPlaceType inPlace;
301};
302
303template <typename T, typename ALLOC>
304using has_non_heap_holder = std::integral_constant<bool,
305 sizeof(NonHeapHolder<T, ALLOC>) <= sizeof(typename UntypedHolder<ALLOC>::MaxInPlaceType) &&
306 std::is_nothrow_move_constructible<T>::value &&
307 alignof(T) <= alignof(typename UntypedHolder<ALLOC>::MaxInPlaceType)>;
308
309} // namespace detail
310
314template <typename ALLOC = std::allocator<uint8_t>>
315class BasicAny : public AllocatorHolder<ALLOC>
316{
317 using AllocTraits = std::allocator_traits<ALLOC>;
320
321public:
324
329 BasicAny(ALLOC())
330 {}
331
335 explicit BasicAny(const ALLOC& allocator) :
337 {
338 m_untypedHolder.heap = nullptr;
339 }
340
346 template <typename T,
347 typename std::enable_if<!std::is_same<typename std::decay<T>::type, BasicAny>::value &&
348 !std::is_same<typename std::decay<T>::type, ALLOC>::value,
349 int>::type = 0>
350 explicit BasicAny(T&& value, const ALLOC& allocator = ALLOC()) :
352 {
353 m_untypedHolder.heap = nullptr;
354 set(std::forward<T>(value));
355 }
356
361 {
362 clearHolder();
363 }
364
372 AllocTraits::select_on_container_copy_construction(other.get_allocator_ref()))
373 {
374 copy(other);
375 }
376
385 {
386 copy(other);
387 }
388
397 {
398 if (this != &other)
399 {
400 // TODO: do not dealloc unless necessary
401 clearHolder();
402 if constexpr (AllocTraits::propagate_on_container_copy_assignment::value)
403 {
404 set_allocator(other.get_allocator_ref());
405 }
406 copy(other);
407 }
408
409 return *this;
410 }
411
418 AllocatorHolder<ALLOC>(std::move(other.get_allocator_ref()))
419 {
420 move(std::move(other));
421 }
422
431 {
432 move(std::move(other));
433 }
434
443 {
444 if (this != &other)
445 {
446 clearHolder();
447 if constexpr (AllocTraits::propagate_on_container_move_assignment::value)
448 {
449 set_allocator(std::move(other.get_allocator_ref()));
450 }
451 move(std::move(other));
452 }
453
454 return *this;
455 }
456
464 template <typename T,
465 typename std::enable_if<!std::is_same<typename std::decay<T>::type, BasicAny>::value, int>::type =
466 0>
467 BasicAny& operator=(T&& value)
468 {
469 set(std::forward<T>(value));
470
471 return *this;
472 }
473
480 {
481 if (!m_isInPlace && m_untypedHolder.heap != nullptr && !other.m_isInPlace &&
482 other.m_untypedHolder.heap != nullptr && get_allocator_ref() == other.get_allocator_ref())
483 {
484 std::swap(m_untypedHolder.heap, other.m_untypedHolder.heap);
485 }
486 else
487 {
488 BasicAny tmp(std::move(*this), get_allocator_ref());
489 move(std::move(other));
490 other.move(std::move(tmp));
491
492 if constexpr (AllocTraits::propagate_on_container_swap::value)
493 {
494 using std::swap;
495 swap(get_allocator_ref(), other.get_allocator_ref());
496 }
497 }
498 }
499
503 void reset()
504 {
505 clearHolder();
506 }
507
513 template <typename T>
514 void set(T&& value)
515 {
516 createHolder<typename std::decay<T>::type>()->set(std::forward<T>(value));
517 }
518
524 template <typename T, typename... ARGS>
526 {
528 get_allocator_ref(), std::forward<ARGS>(args)...);
529 }
530
536 template <typename T>
538 {
539 if (!isType<T>())
540 {
541 return nullptr;
542 }
543 // TODO: remove try-catch as part of issue #46
544 try
545 {
546 return &getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
547 }
548 catch (const std::exception&)
549 {
550 return nullptr;
551 }
552 }
553
559 template <typename T>
561 {
562 if (!isType<T>())
563 {
564 return nullptr;
565 }
566 // TODO: remove try-catch as part of issue #46
567 try
568 {
569 return &getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
570 }
571 catch (const std::exception&)
572 {
573 return nullptr;
574 }
575 }
576
584 template <typename T>
585 T& get()
586 {
587 checkType<T>();
588 return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
589 }
590
598 template <typename T>
599 const T& get() const
600 {
601 checkType<T>();
602 return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
603 }
604
610 template <typename T>
612 {
613 return hasHolder() && getUntypedHolder()->isType(detail::TypeIdHolder::get<T>());
614 }
615
622 {
623 return hasHolder();
624 }
625
626private:
627 void copy(const BasicAny& other)
628 {
629 if (other.m_isInPlace)
630 {
631 other.getUntypedHolder()->clone(&m_untypedHolder.inPlace);
632 m_isInPlace = true;
633 }
634 else if (other.m_untypedHolder.heap != nullptr)
635 {
636 m_untypedHolder.heap = other.getUntypedHolder()->clone(get_allocator_ref());
637 }
638 else
639 {
640 m_untypedHolder.heap = nullptr;
641 }
642 }
643
644 void move(BasicAny&& other)
645 {
646 if (other.m_isInPlace)
647 {
648 other.getUntypedHolder()->move(&m_untypedHolder.inPlace);
649 m_isInPlace = true;
650 other.clearHolder();
651 }
652 else if (other.m_untypedHolder.heap != nullptr)
653 {
654 if (get_allocator_ref() == other.get_allocator_ref())
655 {
656 // take over the other's storage
657 m_untypedHolder.heap = other.m_untypedHolder.heap;
658 other.m_untypedHolder.heap = nullptr;
659 }
660 else
661 {
662 // cannot steal the storage, allocate our own and move the holder
663 m_untypedHolder.heap = other.getUntypedHolder()->move(get_allocator_ref());
664 other.clearHolder();
665 }
666 }
667 else
668 {
669 m_untypedHolder.heap = nullptr;
670 }
671 }
672
673 void clearHolder()
674 {
675 if (hasHolder())
676 {
677 getUntypedHolder()->destroy(get_allocator_ref());
678 m_isInPlace = false;
679 m_untypedHolder.heap = nullptr;
680 }
681 }
682
683 bool hasHolder() const noexcept
684 {
685 return (m_isInPlace || m_untypedHolder.heap != nullptr);
686 }
687
688 template <typename T>
689 detail::HolderBase<T, ALLOC>* createHolder()
690 {
691 if (hasHolder())
692 {
693 if (getUntypedHolder()->isType(detail::TypeIdHolder::get<T>()))
694 {
695 return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>());
696 }
697
698 clearHolder();
699 }
700
701 return createHolderImpl<T>(detail::has_non_heap_holder<T, ALLOC>());
702 }
703
704 template <typename T>
705 detail::HolderBase<T, ALLOC>* createHolderImpl(std::true_type)
706 {
707 detail::NonHeapHolder<T, ALLOC>* holder =
708 detail::NonHeapHolder<T, ALLOC>::create(&m_untypedHolder.inPlace);
709 m_isInPlace = true;
710 return holder;
711 }
712
713 template <typename T>
714 detail::HolderBase<T, ALLOC>* createHolderImpl(std::false_type)
715 {
716 detail::HeapHolder<T, ALLOC>* holder = detail::HeapHolder<T, ALLOC>::create(get_allocator_ref());
717 m_untypedHolder.heap = holder;
718 return holder;
719 }
720
721 template <typename T>
722 void checkType() const
723 {
724 if (!isType<T>())
725 {
726 throwBadType();
727 }
728 }
729
731 void throwBadType() const
732 {
733 throw CppRuntimeException("Bad type in BasicAny");
734 }
735
736 template <typename T>
737 detail::HeapHolder<T, ALLOC>* getHeapHolder() noexcept
738 {
739 return static_cast<detail::HeapHolder<T, ALLOC>*>(m_untypedHolder.heap);
740 }
741
742 template <typename T>
743 const detail::HeapHolder<T, ALLOC>* getHeapHolder() const noexcept
744 {
745 return static_cast<detail::HeapHolder<T, ALLOC>*>(m_untypedHolder.heap);
746 }
747
748 template <typename T>
749 detail::NonHeapHolder<T, ALLOC>* getInplaceHolder() noexcept
750 {
751 return reinterpret_cast<detail::NonHeapHolder<T, ALLOC>*>(&m_untypedHolder.inPlace);
752 }
753
754 template <typename T>
755 const detail::NonHeapHolder<T, ALLOC>* getInplaceHolder() const noexcept
756 {
757 return reinterpret_cast<const detail::NonHeapHolder<T, ALLOC>*>(&m_untypedHolder.inPlace);
758 }
759
760 template <typename T>
761 detail::HolderBase<T, ALLOC>* getHolder(std::true_type) noexcept
762 {
763 return static_cast<detail::HolderBase<T, ALLOC>*>(getInplaceHolder<T>());
764 }
765
766 template <typename T>
767 detail::HolderBase<T, ALLOC>* getHolder(std::false_type) noexcept
768 {
769 return static_cast<detail::HolderBase<T, ALLOC>*>(getHeapHolder<T>());
770 }
771
772 template <typename T>
773 const detail::HolderBase<T, ALLOC>* getHolder(std::true_type) const noexcept
774 {
775 return static_cast<const detail::HolderBase<T, ALLOC>*>(getInplaceHolder<T>());
776 }
777
778 template <typename T>
779 const detail::HolderBase<T, ALLOC>* getHolder(std::false_type) const noexcept
780 {
781 return static_cast<const detail::HolderBase<T, ALLOC>*>(getHeapHolder<T>());
782 }
783
784 detail::IHolder<ALLOC>* getUntypedHolder() noexcept
785 {
786 return (m_isInPlace)
787 ? reinterpret_cast<detail::IHolder<ALLOC>*>(&m_untypedHolder.inPlace)
788 : m_untypedHolder.heap;
789 }
790
791 const detail::IHolder<ALLOC>* getUntypedHolder() const noexcept
792 {
793 return (m_isInPlace)
794 ? reinterpret_cast<const detail::IHolder<ALLOC>*>(&m_untypedHolder.inPlace)
795 : m_untypedHolder.heap;
796 }
797
798 detail::UntypedHolder<ALLOC> m_untypedHolder;
799 bool m_isInPlace = false;
800};
801
804
805} // namespace zserio
806
807#endif // ifndef ZSERIO_ANY_H_INC
allocator_type get_allocator() const
void set_allocator(const allocator_type &allocator)
allocator_type & get_allocator_ref()
BasicAny(const BasicAny &other)
Definition Any.h:370
T & emplace(ARGS &&... args)
Definition Any.h:525
T * get_if() noexcept
Definition Any.h:537
bool isType() const noexcept
Definition Any.h:611
bool hasValue() const noexcept
Definition Any.h:621
BasicAny(const BasicAny &other, const ALLOC &allocator)
Definition Any.h:383
BasicAny & operator=(const BasicAny &other)
Definition Any.h:396
void swap(BasicAny &other)
Definition Any.h:479
BasicAny & operator=(BasicAny &&other)
Definition Any.h:442
T & get()
Definition Any.h:585
BasicAny(T &&value, const ALLOC &allocator=ALLOC())
Definition Any.h:350
const T & get() const
Definition Any.h:599
BasicAny & operator=(T &&value)
Definition Any.h:467
BasicAny(BasicAny &&other) noexcept
Definition Any.h:417
void set(T &&value)
Definition Any.h:514
void reset()
Definition Any.h:503
BasicAny(const ALLOC &allocator)
Definition Any.h:335
BasicAny(BasicAny &&other, const ALLOC &allocator)
Definition Any.h:429
const T * get_if() const noexcept
Definition Any.h:560
decltype(auto) get(BasicVariant< ALLOC, INDEX, T... > &var)
Definition Variant.h:511
uint32_t calcHashCode(uint32_t seedValue, const ArrayView< T, ARRAY_TRAITS > &array)
Definition ArrayView.h:882