Zserio C++17 runtime library  1.1.0
Built for Zserio 2.18.1
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 <optional>
6 #include <type_traits>
7 
10 #include "zserio/HashCodeUtil.h"
11 #include "zserio/Optional.h"
12 #include "zserio/RebindAlloc.h"
13 #include "zserio/Traits.h"
14 #include "zserio/Types.h"
15 
16 namespace zserio
17 {
18 
19 namespace detail
20 {
21 
22 #if !defined(ZSERIO_TYPEID_STATIC_ONLY)
23  #if !defined(_MSC_VER) && !defined(__GNUC__)
24  #warning "Unsupported compiler, type identification in zserio::Any runs in fallback mode"
25  #define ZSERIO_TYPEID_STATIC_ONLY
26  #endif
27 #endif
28 
29 class TypeIdHolder
30 {
31 public:
32 #if defined(ZSERIO_TYPEID_STATIC_ONLY)
33  using type_id = uintptr_t;
34 
35  template <typename T>
36  static uintptr_t get()
37  {
38  static const id = 0;
39  return &id;
40  }
41 
42 // in MSVC typeid works even when RTTI is disabled
43 #elif defined(_MSC_VER) || defined(__GXX_RTTI)
44  using type_id = const std::type_info&;
45 
46  template <typename T>
47  static type_id get()
48  {
49  return typeid(T);
50  }
51 
52 #else
53  using type_id = uintptr_t;
54 
55  // returns uintptr_t instead of type_id to keep PRETTY_FUNCTION shorter
56  template <typename T>
57  static uintptr_t get()
58  {
59  // PRETTY_FUNCTION gives a string like:
60  // static uintptr_t zserio::detail::TypeIdHolder::get() [T = int]
61  // static uintptr_t zserio::detail::TypeIdHolder::get() [with T = int; uintptr_t = xyz]
62  static const auto id = calcHashCode(zserio::HASH_SEED, std::string_view(__PRETTY_FUNCTION__));
63  return static_cast<uintptr_t>(id);
64  }
65 
66 #endif
67 };
68 
69 // Interface for object holders
70 template <typename ALLOC>
71 class IHolder
72 {
73 public:
74  virtual ~IHolder() = default;
75  virtual IHolder* clone(const ALLOC& allocator) const = 0;
76  virtual IHolder* clone(void* storage) const = 0;
77  virtual IHolder* move(const ALLOC& allocator) = 0;
78  virtual IHolder* move(void* storage) = 0;
79  virtual void destroy(const ALLOC& allocator) = 0;
80  virtual bool isType(detail::TypeIdHolder::type_id typeId) const = 0;
81 };
82 
83 // Base of object holders, holds a value in the std::optional
84 template <typename T, typename ALLOC>
85 class HolderBase : public IHolder<ALLOC>
86 {
87 public:
88  template <typename U>
89  void set(U&& value)
90  {
91  m_typedHolder = std::forward<U>(value);
92  }
93 
94  template <typename... ARGS>
95  T& construct(const ALLOC& allocator, ARGS&&... args)
96  {
97  // in case T is allocator-aware (e.g. std::vector) we need to construct
98  // it with parent allocator but because std::optional doesn't support
99  // allocators we emulate uses_allocator construction here
100  if constexpr (!std::uses_allocator_v<T, ALLOC>)
101  {
102  return m_typedHolder.emplace(std::forward<ARGS>(args)...);
103  }
104  else if constexpr (std::is_constructible_v<T, std::allocator_arg_t, const ALLOC&, ARGS...>)
105  {
106  return m_typedHolder.emplace(std::allocator_arg, allocator, std::forward<ARGS>(args)...);
107  }
108  else if constexpr (std::is_constructible_v<T, ARGS..., const ALLOC&>)
109  {
110  return m_typedHolder.emplace(std::forward<ARGS>(args)..., allocator);
111  }
112  else
113  {
114  static_assert(always_false<T>::value,
115  "if uses_allocator_v<remove_cv_t<T>, ALLOC> is true, "
116  "T must be constructible with allocator argument");
117  }
118  }
119 
120  void setHolder(const std::optional<T>& value)
121  {
122  m_typedHolder = value;
123  }
124 
125  void setHolder(std::optional<T>&& value)
126  {
127  m_typedHolder = std::move(value);
128  }
129 
130  T& get()
131  {
132  return m_typedHolder.value();
133  }
134 
135  const T& get() const
136  {
137  return m_typedHolder.value();
138  }
139 
140  bool isType(detail::TypeIdHolder::type_id typeId) const override
141  {
142  return detail::TypeIdHolder::get<T>() == typeId;
143  }
144 
145 protected:
146  std::optional<T>& getHolder()
147  {
148  return m_typedHolder;
149  }
150 
151  const std::optional<T>& getHolder() const
152  {
153  return m_typedHolder;
154  }
155 
156 private:
157  std::optional<T> m_typedHolder;
158 };
159 
160 // Holder allocated on heap
161 template <typename T, typename ALLOC>
162 class HeapHolder : public HolderBase<T, ALLOC>
163 {
164 private:
165  struct ConstructTag
166  {};
167 
168 public:
169  using this_type = HeapHolder<T, ALLOC>;
170 
171  explicit HeapHolder(ConstructTag) noexcept
172  {}
173 
174  static this_type* create(const ALLOC& allocator)
175  {
176  using AllocType = RebindAlloc<ALLOC, this_type>;
177  using AllocTraits = std::allocator_traits<AllocType>;
178 
179  AllocType typedAlloc = allocator;
180  typename AllocTraits::pointer ptr = AllocTraits::allocate(typedAlloc, 1);
181  // this never throws because HeapHolder constructor never throws
182  AllocTraits::construct(typedAlloc, std::addressof(*ptr), ConstructTag{});
183  return ptr;
184  }
185 
186  IHolder<ALLOC>* clone(const ALLOC& allocator) const override
187  {
188  this_type* holder = create(allocator);
189  holder->setHolder(this->getHolder());
190  return holder;
191  }
192 
193  IHolder<ALLOC>* clone(void*) const override
194  {
195  throw CppRuntimeException("BasicAny: Unexpected clone call.");
196  }
197 
198  IHolder<ALLOC>* move(const ALLOC& allocator) override
199  {
200  this_type* holder = create(allocator);
201  holder->setHolder(std::move(this->getHolder()));
202  return holder;
203  }
204 
205  IHolder<ALLOC>* move(void*) override
206  {
207  throw CppRuntimeException("BasicAny: Unexpected move call.");
208  }
209 
210  void destroy(const ALLOC& allocator) override
211  {
212  using AllocType = RebindAlloc<ALLOC, this_type>;
213  using AllocTraits = std::allocator_traits<AllocType>;
214 
215  AllocType typedAlloc = allocator;
216  AllocTraits::destroy(typedAlloc, this);
217  AllocTraits::deallocate(typedAlloc, this, 1);
218  }
219 };
220 
221 // Holder allocated in the in-place storage
222 template <typename T, typename ALLOC>
223 class NonHeapHolder : public HolderBase<T, ALLOC>
224 {
225 public:
226  using this_type = NonHeapHolder<T, ALLOC>;
227 
228  static this_type* create(void* storage)
229  {
230  return new (storage) this_type();
231  }
232 
233  IHolder<ALLOC>* clone(const ALLOC&) const override
234  {
235  throw CppRuntimeException("BasicAny: Unexpected clone call.");
236  }
237 
238  IHolder<ALLOC>* clone(void* storage) const override
239  {
240  NonHeapHolder* holder = new (storage) NonHeapHolder();
241  holder->setHolder(this->getHolder());
242  return holder;
243  }
244 
245  IHolder<ALLOC>* move(const ALLOC&) override
246  {
247  throw CppRuntimeException("BasicAny: Unexpected move call.");
248  }
249 
250  IHolder<ALLOC>* move(void* storage) override
251  {
252  NonHeapHolder* holder = new (storage) NonHeapHolder();
253  holder->setHolder(std::move(this->getHolder()));
254  return holder;
255  }
256 
257  void destroy(const ALLOC&) override
258  {
259  this->~NonHeapHolder();
260  }
261 
262 private:
263  NonHeapHolder() = default;
264 };
265 
266 template <typename ALLOC>
267 union UntypedHolder
268 {
269  // 2 * sizeof(void*) for T + sizeof(void*) for Holder's vptr
270  using MaxInPlaceType = std::aligned_storage<3 * sizeof(void*), alignof(void*)>::type;
271 
272  detail::IHolder<ALLOC>* heap;
273  MaxInPlaceType inPlace;
274 };
275 
276 template <typename T, typename ALLOC>
277 using has_non_heap_holder = std::integral_constant<bool,
278  sizeof(NonHeapHolder<T, ALLOC>) <= sizeof(typename UntypedHolder<ALLOC>::MaxInPlaceType) &&
279  std::is_nothrow_move_constructible<T>::value &&
280  alignof(T) <= alignof(typename UntypedHolder<ALLOC>::MaxInPlaceType)>;
281 
282 } // namespace detail
283 
287 template <typename ALLOC = std::allocator<uint8_t>>
288 class BasicAny : public AllocatorHolder<ALLOC>
289 {
290  using AllocTraits = std::allocator_traits<ALLOC>;
293 
294 public:
296  using allocator_type = ALLOC;
297 
302  BasicAny(ALLOC())
303  {}
304 
308  explicit BasicAny(const ALLOC& allocator) :
309  AllocatorHolder<ALLOC>(allocator)
310  {
311  m_untypedHolder.heap = nullptr;
312  }
313 
319  template <typename T,
320  typename std::enable_if<!std::is_same<typename std::decay<T>::type, BasicAny>::value &&
321  !std::is_same<typename std::decay<T>::type, ALLOC>::value,
322  int>::type = 0>
323  explicit BasicAny(T&& value, const ALLOC& allocator = ALLOC()) :
324  AllocatorHolder<ALLOC>(allocator)
325  {
326  m_untypedHolder.heap = nullptr;
327  set(std::forward<T>(value));
328  }
329 
334  {
335  clearHolder();
336  }
337 
343  BasicAny(const BasicAny& other) :
344  AllocatorHolder<ALLOC>(
345  AllocTraits::select_on_container_copy_construction(other.get_allocator_ref()))
346  {
347  copy(other);
348  }
349 
356  BasicAny(const BasicAny& other, const ALLOC& allocator) :
357  AllocatorHolder<ALLOC>(allocator)
358  {
359  copy(other);
360  }
361 
369  BasicAny& operator=(const BasicAny& other)
370  {
371  if (this != &other)
372  {
373  // TODO: do not dealloc unless necessary
374  clearHolder();
375  if constexpr (AllocTraits::propagate_on_container_copy_assignment::value)
376  {
378  }
379  copy(other);
380  }
381 
382  return *this;
383  }
384 
390  BasicAny(BasicAny&& other) noexcept :
391  AllocatorHolder<ALLOC>(std::move(other.get_allocator_ref()))
392  {
393  move(std::move(other));
394  }
395 
402  BasicAny(BasicAny&& other, const ALLOC& allocator) :
403  AllocatorHolder<ALLOC>(allocator)
404  {
405  move(std::move(other));
406  }
407 
416  {
417  if (this != &other)
418  {
419  clearHolder();
420  if constexpr (AllocTraits::propagate_on_container_move_assignment::value)
421  {
422  set_allocator(std::move(other.get_allocator_ref()));
423  }
424  move(std::move(other));
425  }
426 
427  return *this;
428  }
429 
437  template <typename T,
438  typename std::enable_if<!std::is_same<typename std::decay<T>::type, BasicAny>::value, int>::type =
439  0>
440  BasicAny& operator=(T&& value)
441  {
442  set(std::forward<T>(value));
443 
444  return *this;
445  }
446 
452  void swap(BasicAny& other)
453  {
454  if (!m_isInPlace && m_untypedHolder.heap != nullptr && !other.m_isInPlace &&
455  other.m_untypedHolder.heap != nullptr && get_allocator_ref() == other.get_allocator_ref())
456  {
457  std::swap(m_untypedHolder.heap, other.m_untypedHolder.heap);
458  }
459  else
460  {
461  BasicAny tmp(std::move(*this), get_allocator_ref());
462  move(std::move(other));
463  other.move(std::move(tmp));
464 
465  if constexpr (AllocTraits::propagate_on_container_swap::value)
466  {
467  using std::swap;
469  }
470  }
471  }
472 
476  void reset()
477  {
478  clearHolder();
479  }
480 
486  template <typename T>
487  void set(T&& value)
488  {
489  createHolder<typename std::decay<T>::type>()->set(std::forward<T>(value));
490  }
491 
497  template <typename T, typename... ARGS>
498  T& emplace(ARGS&&... args)
499  {
500  return createHolder<typename std::decay<T>::type>()->construct(
501  get_allocator_ref(), std::forward<ARGS>(args)...);
502  }
503 
509  template <typename T>
510  T* get_if() noexcept
511  {
512  if (!isType<T>())
513  {
514  return nullptr;
515  }
516  // TODO: remove try-catch as part of issue #46
517  try
518  {
519  return &getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
520  }
521  catch (const std::exception&)
522  {
523  return nullptr;
524  }
525  }
526 
532  template <typename T>
533  const T* get_if() const noexcept
534  {
535  if (!isType<T>())
536  {
537  return nullptr;
538  }
539  // TODO: remove try-catch as part of issue #46
540  try
541  {
542  return &getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
543  }
544  catch (const std::exception&)
545  {
546  return nullptr;
547  }
548  }
549 
557  template <typename T>
558  T& get()
559  {
560  checkType<T>();
561  return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
562  }
563 
571  template <typename T>
572  const T& get() const
573  {
574  checkType<T>();
575  return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>())->get();
576  }
577 
583  template <typename T>
584  bool isType() const noexcept
585  {
586  return hasHolder() && getUntypedHolder()->isType(detail::TypeIdHolder::get<T>());
587  }
588 
594  bool hasValue() const noexcept
595  {
596  return hasHolder();
597  }
598 
599 private:
600  void copy(const BasicAny& other)
601  {
602  if (other.m_isInPlace)
603  {
604  other.getUntypedHolder()->clone(&m_untypedHolder.inPlace);
605  m_isInPlace = true;
606  }
607  else if (other.m_untypedHolder.heap != nullptr)
608  {
609  m_untypedHolder.heap = other.getUntypedHolder()->clone(get_allocator_ref());
610  }
611  else
612  {
613  m_untypedHolder.heap = nullptr;
614  }
615  }
616 
617  void move(BasicAny&& other)
618  {
619  if (other.m_isInPlace)
620  {
621  other.getUntypedHolder()->move(&m_untypedHolder.inPlace);
622  m_isInPlace = true;
623  other.clearHolder();
624  }
625  else if (other.m_untypedHolder.heap != nullptr)
626  {
627  if (get_allocator_ref() == other.get_allocator_ref())
628  {
629  // take over the other's storage
630  m_untypedHolder.heap = other.m_untypedHolder.heap;
631  other.m_untypedHolder.heap = nullptr;
632  }
633  else
634  {
635  // cannot steal the storage, allocate our own and move the holder
636  m_untypedHolder.heap = other.getUntypedHolder()->move(get_allocator_ref());
637  other.clearHolder();
638  }
639  }
640  else
641  {
642  m_untypedHolder.heap = nullptr;
643  }
644  }
645 
646  void clearHolder()
647  {
648  if (hasHolder())
649  {
650  getUntypedHolder()->destroy(get_allocator_ref());
651  m_isInPlace = false;
652  m_untypedHolder.heap = nullptr;
653  }
654  }
655 
656  bool hasHolder() const noexcept
657  {
658  return (m_isInPlace || m_untypedHolder.heap != nullptr);
659  }
660 
661  template <typename T>
662  detail::HolderBase<T, ALLOC>* createHolder()
663  {
664  if (hasHolder())
665  {
666  if (getUntypedHolder()->isType(detail::TypeIdHolder::get<T>()))
667  {
668  return getHolder<T>(detail::has_non_heap_holder<T, ALLOC>());
669  }
670 
671  clearHolder();
672  }
673 
674  return createHolderImpl<T>(detail::has_non_heap_holder<T, ALLOC>());
675  }
676 
677  template <typename T>
678  detail::HolderBase<T, ALLOC>* createHolderImpl(std::true_type)
679  {
680  detail::NonHeapHolder<T, ALLOC>* holder =
681  detail::NonHeapHolder<T, ALLOC>::create(&m_untypedHolder.inPlace);
682  m_isInPlace = true;
683  return holder;
684  }
685 
686  template <typename T>
687  detail::HolderBase<T, ALLOC>* createHolderImpl(std::false_type)
688  {
689  detail::HeapHolder<T, ALLOC>* holder = detail::HeapHolder<T, ALLOC>::create(get_allocator_ref());
690  m_untypedHolder.heap = holder;
691  return holder;
692  }
693 
694  template <typename T>
695  void checkType() const
696  {
697  if (!isType<T>())
698  {
699  throwBadType();
700  }
701  }
702 
704  void throwBadType() const
705  {
706  throw CppRuntimeException("Bad type in BasicAny");
707  }
708 
709  template <typename T>
710  detail::HeapHolder<T, ALLOC>* getHeapHolder() noexcept
711  {
712  return static_cast<detail::HeapHolder<T, ALLOC>*>(m_untypedHolder.heap);
713  }
714 
715  template <typename T>
716  const detail::HeapHolder<T, ALLOC>* getHeapHolder() const noexcept
717  {
718  return static_cast<detail::HeapHolder<T, ALLOC>*>(m_untypedHolder.heap);
719  }
720 
721  template <typename T>
722  detail::NonHeapHolder<T, ALLOC>* getInplaceHolder() noexcept
723  {
724  return reinterpret_cast<detail::NonHeapHolder<T, ALLOC>*>(&m_untypedHolder.inPlace);
725  }
726 
727  template <typename T>
728  const detail::NonHeapHolder<T, ALLOC>* getInplaceHolder() const noexcept
729  {
730  return reinterpret_cast<const detail::NonHeapHolder<T, ALLOC>*>(&m_untypedHolder.inPlace);
731  }
732 
733  template <typename T>
734  detail::HolderBase<T, ALLOC>* getHolder(std::true_type) noexcept
735  {
736  return static_cast<detail::HolderBase<T, ALLOC>*>(getInplaceHolder<T>());
737  }
738 
739  template <typename T>
740  detail::HolderBase<T, ALLOC>* getHolder(std::false_type) noexcept
741  {
742  return static_cast<detail::HolderBase<T, ALLOC>*>(getHeapHolder<T>());
743  }
744 
745  template <typename T>
746  const detail::HolderBase<T, ALLOC>* getHolder(std::true_type) const noexcept
747  {
748  return static_cast<const detail::HolderBase<T, ALLOC>*>(getInplaceHolder<T>());
749  }
750 
751  template <typename T>
752  const detail::HolderBase<T, ALLOC>* getHolder(std::false_type) const noexcept
753  {
754  return static_cast<const detail::HolderBase<T, ALLOC>*>(getHeapHolder<T>());
755  }
756 
757  detail::IHolder<ALLOC>* getUntypedHolder() noexcept
758  {
759  return (m_isInPlace)
760  ? reinterpret_cast<detail::IHolder<ALLOC>*>(&m_untypedHolder.inPlace)
761  : m_untypedHolder.heap;
762  }
763 
764  const detail::IHolder<ALLOC>* getUntypedHolder() const noexcept
765  {
766  return (m_isInPlace)
767  ? reinterpret_cast<const detail::IHolder<ALLOC>*>(&m_untypedHolder.inPlace)
768  : m_untypedHolder.heap;
769  }
770 
771  detail::UntypedHolder<ALLOC> m_untypedHolder;
772  bool m_isInPlace = false;
773 };
774 
776 using Any = BasicAny<>;
777 
778 } // namespace zserio
779 
780 #endif // ifndef ZSERIO_ANY_H_INC
void set_allocator(const allocator_type &allocator)
BasicAny(const BasicAny &other)
Definition: Any.h:343
T & emplace(ARGS &&... args)
Definition: Any.h:498
bool isType() const noexcept
Definition: Any.h:584
const T & get() const
Definition: Any.h:572
T & get()
Definition: Any.h:558
bool hasValue() const noexcept
Definition: Any.h:594
T * get_if() noexcept
Definition: Any.h:510
BasicAny(const BasicAny &other, const ALLOC &allocator)
Definition: Any.h:356
void swap(BasicAny &other)
Definition: Any.h:452
BasicAny & operator=(BasicAny &&other)
Definition: Any.h:415
BasicAny(T &&value, const ALLOC &allocator=ALLOC())
Definition: Any.h:323
const T * get_if() const noexcept
Definition: Any.h:533
BasicAny & operator=(const BasicAny &other)
Definition: Any.h:369
BasicAny(BasicAny &&other) noexcept
Definition: Any.h:390
void set(T &&value)
Definition: Any.h:487
ALLOC allocator_type
Definition: Any.h:296
void reset()
Definition: Any.h:476
BasicAny(const ALLOC &allocator)
Definition: Any.h:308
BasicAny & operator=(T &&value)
Definition: Any.h:440
BasicAny(BasicAny &&other, const ALLOC &allocator)
Definition: Any.h:402
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