Zserio C++ runtime library  1.0.1
Built for Zserio 2.14.0
OptionalHolder.h
Go to the documentation of this file.
1 #ifndef ZSERIO_OPTIONAL_HOLDER_H_INC
2 #define ZSERIO_OPTIONAL_HOLDER_H_INC
3 
4 #include <cstddef>
5 #include <type_traits>
6 
8 #include "zserio/NoInit.h"
9 #include "zserio/Types.h"
10 #include "zserio/UniquePtr.h"
11 
12 namespace zserio
13 {
14 
19 {
25  constexpr explicit NullOptType(int)
26  {}
27 };
28 
32 constexpr NullOptType NullOpt{int()};
33 
37 struct InPlaceT
38 {
39  constexpr explicit InPlaceT() = default;
40 };
41 
45 constexpr InPlaceT InPlace{};
46 
47 namespace detail
48 {
49 
53 template <typename T, typename Derived>
54 class optional_holder_base
55 {
56 public:
64  bool operator==(const optional_holder_base& other) const
65  {
66  if (this == &other)
67  return true;
68 
69  if (getDerived()->hasValue() != other.getDerived()->hasValue())
70  return false;
71 
72  if (getDerived()->hasValue())
73  return get() == other.get();
74 
75  return true;
76  }
77 
85  bool operator<(const optional_holder_base& other) const
86  {
87  if (getDerived()->hasValue() && other.getDerived()->hasValue())
88  return get() < other.get();
89 
90  return !getDerived()->hasValue() && other.getDerived()->hasValue();
91  }
92 
98  explicit operator bool() const noexcept
99  {
100  return getDerived()->hasValue();
101  }
102 
110  const T* operator->() const
111  {
112  return std::addressof(get());
113  }
114 
122  T* operator->()
123  {
124  return std::addressof(get());
125  }
126 
134  const T& value() const
135  {
136  return get();
137  }
138 
146  T& value()
147  {
148  return get();
149  }
150 
151 protected:
152  void checkHasValue() const
153  {
154  if (!getDerived()->hasValue())
155  throwNonPresentException();
156  }
157 
158 private:
159  T& get()
160  {
161  return getDerived()->operator*();
162  }
163 
164  const T& get() const
165  {
166  return getDerived()->operator*();
167  }
168 
169  Derived* getDerived()
170  {
171  return static_cast<Derived*>(this);
172  }
173 
174  const Derived* getDerived() const
175  {
176  return static_cast<const Derived*>(this);
177  }
178 
180  void throwNonPresentException() const
181  {
182  throw CppRuntimeException("Trying to access value of non-present optional field!");
183  }
184 };
185 
189 template <typename T, typename ALLOC>
190 class heap_optional_holder : public optional_holder_base<T, heap_optional_holder<T, ALLOC>>
191 {
192 public:
193  using allocator_type = ALLOC;
194  using allocator_traits = std::allocator_traits<allocator_type>;
195  using value_type = T;
196  using storage_type = zserio::unique_ptr<T, allocator_type>;
197 
203  allocator_type get_allocator() const
204  {
205  return m_storage.get_deleter().get_allocator();
206  }
207 
213  explicit constexpr heap_optional_holder(const allocator_type& allocator = allocator_type()) noexcept :
214  m_storage(nullptr, allocator)
215  {}
216 
222  constexpr heap_optional_holder(NullOptType, const allocator_type& allocator = allocator_type()) noexcept :
223  m_storage(nullptr, allocator)
224  {}
225 
232  heap_optional_holder(const T& value, const allocator_type& allocator = allocator_type()) :
233  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, value))
234  {}
235 
242  heap_optional_holder(T&& value, const allocator_type& allocator = allocator_type()) :
243  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::move(value)))
244  {}
245 
246  // called from allocatorPropagatingCopy
253  template <typename U = T,
254  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
255  heap_optional_holder(NoInitT, T&& value, const allocator_type& allocator = allocator_type()) :
256  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(value)))
257  {}
258 
265  template <typename... U>
266  explicit heap_optional_holder(const allocator_type& allocator, U&&... parameters) :
267  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::forward<U>(parameters)...))
268  {}
269 
273  ~heap_optional_holder() = default;
274 
280  heap_optional_holder(const heap_optional_holder& other) :
281  m_storage(copy_initialize(
282  other, allocator_traits::select_on_container_copy_construction(other.get_allocator())))
283  {}
284 
290  template <typename U = T,
291  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
292  heap_optional_holder(NoInitT, const heap_optional_holder& other) :
293  m_storage(copy_initialize(NoInit, other,
294  allocator_traits::select_on_container_copy_construction(other.get_allocator())))
295  {}
296 
303  heap_optional_holder(const heap_optional_holder& other, const allocator_type& allocator) :
304  m_storage(copy_initialize(other, allocator))
305  {}
306 
312  heap_optional_holder(heap_optional_holder&& other) noexcept = default;
313 
319  template <typename U = T,
320  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
321  heap_optional_holder(NoInitT, heap_optional_holder&& other) :
322  m_storage(move_initialize(NoInit, std::move(other), other.get_allocator()))
323  {}
324 
331  heap_optional_holder(heap_optional_holder&& other, const allocator_type& allocator) :
332  m_storage(move_initialize(std::move(other), allocator))
333  {}
334 
342  heap_optional_holder& operator=(const heap_optional_holder& other)
343  {
344  if (this == &other)
345  return *this;
346 
347  m_storage = copy_initialize(other,
348  select_allocator(other.get_allocator(),
349  typename allocator_traits::propagate_on_container_copy_assignment()));
350 
351  return *this;
352  }
353 
361  template <typename U = T,
362  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
363  heap_optional_holder& assign(NoInitT, const heap_optional_holder& other)
364  {
365  if (this == &other)
366  return *this;
367 
368  m_storage = copy_initialize(NoInit, other,
369  select_allocator(other.get_allocator(),
370  typename allocator_traits::propagate_on_container_copy_assignment()));
371 
372  return *this;
373  }
374 
382  heap_optional_holder& operator=(heap_optional_holder&& other)
383  {
384  if (this == &other)
385  return *this;
386 
387  m_storage = move_initialize(std::move(other),
388  select_allocator(other.get_allocator(),
389  typename allocator_traits::propagate_on_container_move_assignment()));
390 
391  return *this;
392  }
393 
401  template <typename U = T,
402  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
403  heap_optional_holder& assign(NoInitT, heap_optional_holder&& other)
404  {
405  if (this == &other)
406  return *this;
407 
408  m_storage = move_initialize(NoInit, std::move(other),
409  select_allocator(other.get_allocator(),
410  typename allocator_traits::propagate_on_container_move_assignment()));
411 
412  return *this;
413  }
414 
422  heap_optional_holder& operator=(const T& value)
423  {
424  set(value);
425 
426  return *this;
427  }
428 
436  heap_optional_holder& operator=(T&& value)
437  {
438  set(std::move(value));
439 
440  return *this;
441  }
442 
446  void reset() noexcept
447  {
448  m_storage.reset();
449  }
450 
456  bool hasValue() const noexcept
457  {
458  return static_cast<bool>(m_storage);
459  }
460 
468  const T& operator*() const
469  {
470  this->checkHasValue();
471  return *m_storage.get();
472  }
473 
481  T& operator*()
482  {
483  this->checkHasValue();
484  return *m_storage.get();
485  }
486 
487 private:
488  allocator_type select_allocator(allocator_type other_allocator, std::true_type)
489  {
490  return other_allocator;
491  }
492 
493  allocator_type select_allocator(allocator_type, std::false_type)
494  {
495  return get_allocator(); // current allocator
496  }
497 
498  template <typename U = T>
499  void set(U&& value)
500  {
501  reset();
502  m_storage = zserio::allocate_unique<T, allocator_type>(get_allocator(), std::forward<U>(value));
503  }
504 
505  static storage_type copy_initialize(const heap_optional_holder& other, const allocator_type& allocator)
506  {
507  if (other.hasValue())
508  return zserio::allocate_unique<T, allocator_type>(allocator, *other);
509  else
510  return storage_type(nullptr, allocator);
511  }
512 
513  static storage_type copy_initialize(
514  NoInitT, const heap_optional_holder& other, const allocator_type& allocator)
515  {
516  if (other.hasValue())
517  return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, *other);
518  else
519  return storage_type(nullptr, allocator);
520  }
521 
522  static storage_type move_initialize(heap_optional_holder&& other, const allocator_type& allocator)
523  {
524  if (other.hasValue())
525  {
526  if (allocator == other.get_allocator())
527  return std::move(other.m_storage);
528 
529  return zserio::allocate_unique<T, allocator_type>(allocator, std::move(*other));
530  }
531  else
532  {
533  return storage_type(nullptr, allocator);
534  }
535  }
536 
537  static storage_type move_initialize(NoInitT, heap_optional_holder&& other, const allocator_type& allocator)
538  {
539  if (other.hasValue())
540  {
541  if (allocator == other.get_allocator())
542  return std::move(other.m_storage);
543 
544  return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(*other));
545  }
546  else
547  {
548  return storage_type(nullptr, allocator);
549  }
550  }
551 
552  storage_type m_storage;
553 };
554 
558 template <typename T>
559 class in_place_storage
560 {
561 public:
565  in_place_storage() = default;
566 
570  ~in_place_storage() = default;
571 
574  in_place_storage(const in_place_storage&) = delete;
575  in_place_storage& operator=(const in_place_storage&) = delete;
579  in_place_storage(in_place_storage&& other) = delete;
580 
588  in_place_storage& operator=(in_place_storage&& other)
589  {
590  new (&m_inPlace) T(std::move(*other.getObject()));
591  other.getObject()->~T(); // ensure that destructor of object in original storage is called
592 
593  return *this;
594  }
595 
603  in_place_storage& assign(NoInitT, in_place_storage&& other)
604  {
605  new (&m_inPlace) T(NoInit, std::move(*other.getObject()));
606  other.getObject()->~T(); // ensure that destructor of object in original storage is called
607 
608  return *this;
609  }
610 
616  void* getStorage()
617  {
618  return &m_inPlace;
619  }
620 
626  T* getObject()
627  {
628  return reinterpret_cast<T*>(&m_inPlace);
629  }
630 
636  const T* getObject() const
637  {
638  return reinterpret_cast<const T*>(&m_inPlace);
639  }
640 
641 private:
642  // Caution!
643  // We use static constants as a WORKAROUND for a GCC 8/9 bug observed when cross-compiling with -m32 flag.
644  // The compilers somehow spoils the aligned storage when alignof(T) is used directly as the template
645  // argument which leads to "*** stack smashing detected ***" error (as observed in some language tests).
646  static constexpr size_t SIZEOF_T = sizeof(T);
647  static constexpr size_t ALIGNOF_T = alignof(T);
648  using AlignedStorage = typename std::aligned_storage<SIZEOF_T, ALIGNOF_T>::type;
649  AlignedStorage m_inPlace;
650 };
651 
655 template <typename T>
656 class inplace_optional_holder : public optional_holder_base<T, inplace_optional_holder<T>>
657 {
658 public:
659  using value_type = T;
660 
664  constexpr inplace_optional_holder() noexcept = default;
665 
669  constexpr inplace_optional_holder(NullOptType) noexcept
670  {}
671 
677  inplace_optional_holder(const T& value)
678  {
679  new (m_storage.getStorage()) T(value);
680  m_hasValue = true;
681  }
682 
688  inplace_optional_holder(T&& value)
689  {
690  new (m_storage.getStorage()) T(std::move(value));
691  m_hasValue = true;
692  }
693 
694  // called from allocatorPropagatingCopy
700  template <typename U = T,
701  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
702  inplace_optional_holder(NoInitT, T&& value)
703  {
704  new (m_storage.getStorage()) T(NoInit, std::move(value));
705  m_hasValue = true;
706  }
707 
713  inplace_optional_holder(const inplace_optional_holder& other)
714  {
715  if (other.hasValue())
716  {
717  new (m_storage.getStorage()) T(*other.m_storage.getObject());
718  m_hasValue = true;
719  }
720  }
721 
727  template <typename U = T,
728  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
729  inplace_optional_holder(NoInitT, const inplace_optional_holder& other)
730  {
731  if (other.hasValue())
732  {
733  new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
734  m_hasValue = true;
735  }
736  }
737 
743  inplace_optional_holder(inplace_optional_holder&& other) noexcept(
744  std::is_nothrow_move_constructible<in_place_storage<T>>::value)
745  {
746  if (other.hasValue())
747  {
748  m_storage = std::move(other.m_storage);
749  other.m_hasValue = false;
750  m_hasValue = true;
751  }
752  }
753 
759  template <typename U = T,
760  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
761  inplace_optional_holder(NoInitT, inplace_optional_holder&& other) noexcept(
762  std::is_nothrow_move_constructible<in_place_storage<T>>::value)
763  {
764  if (other.hasValue())
765  {
766  m_storage.assign(NoInit, std::move(other.m_storage));
767  other.m_hasValue = false;
768  m_hasValue = true;
769  }
770  }
771 
777  template <typename... U>
778  explicit inplace_optional_holder(InPlaceT, U&&... parameters)
779  {
780  new (m_storage.getStorage()) T(std::forward<U>(parameters)...);
781  m_hasValue = true;
782  }
783 
787  ~inplace_optional_holder()
788  {
789  reset();
790  }
791 
799  inplace_optional_holder& operator=(const inplace_optional_holder& other)
800  {
801  if (this != &other)
802  {
803  reset();
804  if (other.hasValue())
805  {
806  new (m_storage.getStorage()) T(*other.m_storage.getObject());
807  m_hasValue = true;
808  }
809  }
810 
811  return *this;
812  }
813 
821  template <typename U = T,
822  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
823  inplace_optional_holder& assign(NoInitT, const inplace_optional_holder& other)
824  {
825  if (this != &other)
826  {
827  reset();
828  if (other.hasValue())
829  {
830  new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
831  m_hasValue = true;
832  }
833  }
834 
835  return *this;
836  }
837 
845  inplace_optional_holder& operator=(inplace_optional_holder&& other)
846  {
847  if (this != &other)
848  {
849  reset();
850  if (other.hasValue())
851  {
852  m_storage = std::move(other.m_storage);
853  other.m_hasValue = false;
854  m_hasValue = true;
855  }
856  }
857 
858  return *this;
859  }
860 
868  template <typename U = T,
869  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
870  inplace_optional_holder& assign(NoInitT, inplace_optional_holder&& other)
871  {
872  if (this != &other)
873  {
874  reset();
875  if (other.hasValue())
876  {
877  m_storage.assign(NoInit, std::move(other.m_storage));
878  other.m_hasValue = false;
879  m_hasValue = true;
880  }
881  }
882 
883  return *this;
884  }
885 
893  inplace_optional_holder& operator=(const T& value)
894  {
895  set(value);
896 
897  return *this;
898  }
899 
907  inplace_optional_holder& operator=(T&& value)
908  {
909  set(std::move(value));
910 
911  return *this;
912  }
913 
917  void reset() noexcept
918  {
919  if (hasValue())
920  {
921  m_storage.getObject()->~T();
922  m_hasValue = false;
923  }
924  }
925 
931  bool hasValue() const noexcept
932  {
933  return m_hasValue;
934  }
935 
943  const T& operator*() const
944  {
945  this->checkHasValue();
946  return *m_storage.getObject();
947  }
948 
956  T& operator*()
957  {
958  this->checkHasValue();
959  return *m_storage.getObject();
960  }
961 
962 private:
963  template <typename U = T>
964  void set(U&& value)
965  {
966  reset();
967  new (m_storage.getStorage()) T(std::forward<U>(value));
968  m_hasValue = true;
969  }
970 
971  in_place_storage<T> m_storage;
972  bool m_hasValue = false;
973 };
974 
975 } // namespace detail
976 
977 // Be aware that if Inplace/HeapOptionalHolder is defined by typename, C++ compiler will have problem with
978 // template function overload, see HashCodeUtil.h (overloads for objects and for Inplace/HeapOptionalHolder).
982 template <typename T>
983 using InplaceOptionalHolder = detail::inplace_optional_holder<T>;
984 
988 template <typename T, typename ALLOC = std::allocator<T>>
989 using HeapOptionalHolder = detail::heap_optional_holder<T, ALLOC>;
990 
991 } // namespace zserio
992 
993 #endif // ifndef ZSERIO_OPTIONAL_HOLDER_H_INC
std::set< T, COMPARE, PropagatingPolymorphicAllocator< T > > set
Definition: Set.h:17
constexpr NoInitT NoInit
Definition: NoInit.h:18
zserio::unique_ptr< T, RebindAlloc< ALLOC, T > > allocate_unique(const ALLOC &allocator, Args &&... args)
Definition: UniquePtr.h:101
constexpr bool operator<(BasicStringView< CharT, Traits > lhs, BasicStringView< CharT, Traits > rhs) noexcept
Definition: StringView.h:846
constexpr NullOptType NullOpt
constexpr bool operator==(BasicStringView< CharT, Traits > lhs, BasicStringView< CharT, Traits > rhs) noexcept
Definition: StringView.h:820
detail::heap_optional_holder< T, ALLOC > HeapOptionalHolder
std::unique_ptr< T, detail::UniquePtrDeleter< ALLOC > > unique_ptr
Definition: UniquePtr.h:89
constexpr InPlaceT InPlace
detail::inplace_optional_holder< T > InplaceOptionalHolder
constexpr InPlaceT()=default
constexpr NullOptType(int)