Zserio C++ runtime library  1.1.0
Built for Zserio 2.15.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  {
68  return true;
69  }
70 
71  if (getDerived()->hasValue() != other.getDerived()->hasValue())
72  {
73  return false;
74  }
75 
76  if (getDerived()->hasValue())
77  {
78  return get() == other.get();
79  }
80 
81  return true;
82  }
83 
91  bool operator<(const optional_holder_base& other) const
92  {
93  if (getDerived()->hasValue() && other.getDerived()->hasValue())
94  {
95  return get() < other.get();
96  }
97 
98  return !getDerived()->hasValue() && other.getDerived()->hasValue();
99  }
100 
106  explicit operator bool() const noexcept
107  {
108  return getDerived()->hasValue();
109  }
110 
118  const T* operator->() const
119  {
120  return std::addressof(get());
121  }
122 
130  T* operator->()
131  {
132  return std::addressof(get());
133  }
134 
142  const T& value() const
143  {
144  return get();
145  }
146 
154  T& value()
155  {
156  return get();
157  }
158 
159 protected:
160  void checkHasValue() const
161  {
162  if (!getDerived()->hasValue())
163  {
164  throwNonPresentException();
165  }
166  }
167 
168 private:
169  T& get()
170  {
171  return getDerived()->operator*();
172  }
173 
174  const T& get() const
175  {
176  return getDerived()->operator*();
177  }
178 
179  Derived* getDerived()
180  {
181  return static_cast<Derived*>(this);
182  }
183 
184  const Derived* getDerived() const
185  {
186  return static_cast<const Derived*>(this);
187  }
188 
190  void throwNonPresentException() const
191  {
192  throw CppRuntimeException("Trying to access value of non-present optional field!");
193  }
194 };
195 
199 template <typename T, typename ALLOC>
200 class heap_optional_holder : public optional_holder_base<T, heap_optional_holder<T, ALLOC>>
201 {
202 public:
203  using allocator_type = ALLOC;
204  using allocator_traits = std::allocator_traits<allocator_type>;
205  using value_type = T;
206  using storage_type = zserio::unique_ptr<T, allocator_type>;
207 
213  allocator_type get_allocator() const
214  {
215  return m_storage.get_deleter().get_allocator();
216  }
217 
223  explicit constexpr heap_optional_holder(const allocator_type& allocator = allocator_type()) noexcept :
224  m_storage(nullptr, allocator)
225  {}
226 
232  constexpr heap_optional_holder(NullOptType, const allocator_type& allocator = allocator_type()) noexcept :
233  m_storage(nullptr, allocator)
234  {}
235 
242  heap_optional_holder(const T& val, const allocator_type& allocator = allocator_type()) :
243  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, val))
244  {}
245 
252  heap_optional_holder(T&& val, const allocator_type& allocator = allocator_type()) :
253  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::move(val)))
254  {}
255 
256  // called from allocatorPropagatingCopy
263  template <typename U = T,
264  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
265  heap_optional_holder(NoInitT, T&& val, const allocator_type& allocator = allocator_type()) :
266  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(val)))
267  {}
268 
275  template <typename... U>
276  explicit heap_optional_holder(const allocator_type& allocator, U&&... parameters) :
277  m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::forward<U>(parameters)...))
278  {}
279 
283  ~heap_optional_holder() = default;
284 
290  heap_optional_holder(const heap_optional_holder& other) :
291  m_storage(copy_initialize(
292  other, allocator_traits::select_on_container_copy_construction(other.get_allocator())))
293  {}
294 
300  template <typename U = T,
301  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
302  heap_optional_holder(NoInitT, const heap_optional_holder& other) :
303  m_storage(copy_initialize(NoInit, other,
304  allocator_traits::select_on_container_copy_construction(other.get_allocator())))
305  {}
306 
313  heap_optional_holder(const heap_optional_holder& other, const allocator_type& allocator) :
314  m_storage(copy_initialize(other, allocator))
315  {}
316 
322  heap_optional_holder(heap_optional_holder&& other) noexcept = default;
323 
329  template <typename U = T,
330  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
331  heap_optional_holder(NoInitT, heap_optional_holder&& other) :
332  m_storage(move_initialize(NoInit, std::move(other), other.get_allocator()))
333  {}
334 
341  heap_optional_holder(heap_optional_holder&& other, const allocator_type& allocator) :
342  m_storage(move_initialize(std::move(other), allocator))
343  {}
344 
352  heap_optional_holder& operator=(const heap_optional_holder& other)
353  {
354  if (this == &other)
355  {
356  return *this;
357  }
358 
359  m_storage = copy_initialize(other,
360  select_allocator(other.get_allocator(),
361  typename allocator_traits::propagate_on_container_copy_assignment()));
362 
363  return *this;
364  }
365 
373  template <typename U = T,
374  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
375  heap_optional_holder& assign(NoInitT, const heap_optional_holder& other)
376  {
377  if (this == &other)
378  {
379  return *this;
380  }
381 
382  m_storage = copy_initialize(NoInit, other,
383  select_allocator(other.get_allocator(),
384  typename allocator_traits::propagate_on_container_copy_assignment()));
385 
386  return *this;
387  }
388 
396  heap_optional_holder& operator=(heap_optional_holder&& other)
397  {
398  if (this == &other)
399  {
400  return *this;
401  }
402 
403  m_storage = move_initialize(std::move(other),
404  select_allocator(other.get_allocator(),
405  typename allocator_traits::propagate_on_container_move_assignment()));
406 
407  return *this;
408  }
409 
417  template <typename U = T,
418  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
419  heap_optional_holder& assign(NoInitT, heap_optional_holder&& other)
420  {
421  if (this == &other)
422  {
423  return *this;
424  }
425 
426  m_storage = move_initialize(NoInit, std::move(other),
427  select_allocator(other.get_allocator(),
428  typename allocator_traits::propagate_on_container_move_assignment()));
429 
430  return *this;
431  }
432 
440  heap_optional_holder& operator=(const T& val)
441  {
442  set(val);
443 
444  return *this;
445  }
446 
454  heap_optional_holder& operator=(T&& val)
455  {
456  set(std::move(val));
457 
458  return *this;
459  }
460 
464  void reset() noexcept
465  {
466  m_storage.reset();
467  }
468 
474  bool hasValue() const noexcept
475  {
476  return static_cast<bool>(m_storage);
477  }
478 
486  const T& operator*() const
487  {
488  this->checkHasValue();
489  return *m_storage.get();
490  }
491 
499  T& operator*()
500  {
501  this->checkHasValue();
502  return *m_storage.get();
503  }
504 
505 private:
506  allocator_type select_allocator(allocator_type other_allocator, std::true_type)
507  {
508  return other_allocator;
509  }
510 
511  allocator_type select_allocator(allocator_type, std::false_type)
512  {
513  return get_allocator(); // current allocator
514  }
515 
516  template <typename U = T>
517  void set(U&& val)
518  {
519  reset();
520  m_storage = zserio::allocate_unique<T, allocator_type>(get_allocator(), std::forward<U>(val));
521  }
522 
523  static storage_type copy_initialize(const heap_optional_holder& other, const allocator_type& allocator)
524  {
525  if (other.hasValue())
526  {
527  return zserio::allocate_unique<T, allocator_type>(allocator, *other);
528  }
529  else
530  {
531  return storage_type(nullptr, allocator);
532  }
533  }
534 
535  static storage_type copy_initialize(
536  NoInitT, const heap_optional_holder& other, const allocator_type& allocator)
537  {
538  if (other.hasValue())
539  {
540  return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, *other);
541  }
542  else
543  {
544  return storage_type(nullptr, allocator);
545  }
546  }
547 
548  static storage_type move_initialize(heap_optional_holder&& other, const allocator_type& allocator)
549  {
550  if (other.hasValue())
551  {
552  if (allocator == other.get_allocator())
553  {
554  return std::move(other.m_storage);
555  }
556 
557  return zserio::allocate_unique<T, allocator_type>(allocator, std::move(*other));
558  }
559  else
560  {
561  return storage_type(nullptr, allocator);
562  }
563  }
564 
565  static storage_type move_initialize(NoInitT, heap_optional_holder&& other, const allocator_type& allocator)
566  {
567  if (other.hasValue())
568  {
569  if (allocator == other.get_allocator())
570  {
571  return std::move(other.m_storage);
572  }
573 
574  return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(*other));
575  }
576  else
577  {
578  return storage_type(nullptr, allocator);
579  }
580  }
581 
582  storage_type m_storage;
583 };
584 
588 template <typename T>
589 class in_place_storage
590 {
591 public:
595  in_place_storage() = default;
596 
600  ~in_place_storage() = default;
601 
604  in_place_storage(const in_place_storage&) = delete;
605  in_place_storage& operator=(const in_place_storage&) = delete;
609  in_place_storage(in_place_storage&& other) = delete;
610 
618  in_place_storage& operator=(in_place_storage&& other)
619  {
620  new (&m_inPlace) T(std::move(*other.getObject()));
621  other.getObject()->~T(); // ensure that destructor of object in original storage is called
622 
623  return *this;
624  }
625 
633  in_place_storage& assign(NoInitT, in_place_storage&& other)
634  {
635  new (&m_inPlace) T(NoInit, std::move(*other.getObject()));
636  other.getObject()->~T(); // ensure that destructor of object in original storage is called
637 
638  return *this;
639  }
640 
646  void* getStorage()
647  {
648  return &m_inPlace;
649  }
650 
656  T* getObject()
657  {
658  return reinterpret_cast<T*>(&m_inPlace);
659  }
660 
666  const T* getObject() const
667  {
668  return reinterpret_cast<const T*>(&m_inPlace);
669  }
670 
671 private:
672  // Caution!
673  // We use static constants as a WORKAROUND for a GCC 8/9 bug observed when cross-compiling with -m32 flag.
674  // The compilers somehow spoils the aligned storage when alignof(T) is used directly as the template
675  // argument which leads to "*** stack smashing detected ***" error (as observed in some language tests).
676  static constexpr size_t SIZEOF_T = sizeof(T);
677  static constexpr size_t ALIGNOF_T = alignof(T);
678  using AlignedStorage = typename std::aligned_storage<SIZEOF_T, ALIGNOF_T>::type;
679  AlignedStorage m_inPlace;
680 };
681 
685 template <typename T>
686 class inplace_optional_holder : public optional_holder_base<T, inplace_optional_holder<T>>
687 {
688 public:
689  using value_type = T;
690 
694  constexpr inplace_optional_holder() noexcept = default;
695 
699  constexpr inplace_optional_holder(NullOptType) noexcept
700  {}
701 
707  inplace_optional_holder(const T& val)
708  {
709  new (m_storage.getStorage()) T(val);
710  m_hasValue = true;
711  }
712 
718  inplace_optional_holder(T&& val)
719  {
720  new (m_storage.getStorage()) T(std::move(val));
721  m_hasValue = true;
722  }
723 
724  // called from allocatorPropagatingCopy
730  template <typename U = T,
731  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
732  inplace_optional_holder(NoInitT, T&& val)
733  {
734  new (m_storage.getStorage()) T(NoInit, std::move(val));
735  m_hasValue = true;
736  }
737 
743  inplace_optional_holder(const inplace_optional_holder& other)
744  {
745  if (other.hasValue())
746  {
747  new (m_storage.getStorage()) T(*other.m_storage.getObject());
748  m_hasValue = true;
749  }
750  }
751 
757  template <typename U = T,
758  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
759  inplace_optional_holder(NoInitT, const inplace_optional_holder& other)
760  {
761  if (other.hasValue())
762  {
763  new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
764  m_hasValue = true;
765  }
766  }
767 
773  inplace_optional_holder(inplace_optional_holder&& other) noexcept(
774  std::is_nothrow_move_constructible<in_place_storage<T>>::value)
775  {
776  if (other.hasValue())
777  {
778  m_storage = std::move(other.m_storage);
779  other.m_hasValue = false;
780  m_hasValue = true;
781  }
782  }
783 
789  template <typename U = T,
790  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
791  inplace_optional_holder(NoInitT, inplace_optional_holder&& other) noexcept(
792  std::is_nothrow_move_constructible<in_place_storage<T>>::value)
793  {
794  if (other.hasValue())
795  {
796  m_storage.assign(NoInit, std::move(other.m_storage));
797  other.m_hasValue = false;
798  m_hasValue = true;
799  }
800  }
801 
807  template <typename... U>
808  explicit inplace_optional_holder(InPlaceT, U&&... parameters)
809  {
810  new (m_storage.getStorage()) T(std::forward<U>(parameters)...);
811  m_hasValue = true;
812  }
813 
817  ~inplace_optional_holder()
818  {
819  reset();
820  }
821 
829  inplace_optional_holder& operator=(const inplace_optional_holder& other)
830  {
831  if (this != &other)
832  {
833  reset();
834  if (other.hasValue())
835  {
836  new (m_storage.getStorage()) T(*other.m_storage.getObject());
837  m_hasValue = true;
838  }
839  }
840 
841  return *this;
842  }
843 
851  template <typename U = T,
852  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
853  inplace_optional_holder& assign(NoInitT, const inplace_optional_holder& other)
854  {
855  if (this != &other)
856  {
857  reset();
858  if (other.hasValue())
859  {
860  new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
861  m_hasValue = true;
862  }
863  }
864 
865  return *this;
866  }
867 
875  inplace_optional_holder& operator=(inplace_optional_holder&& other)
876  {
877  if (this != &other)
878  {
879  reset();
880  if (other.hasValue())
881  {
882  m_storage = std::move(other.m_storage);
883  other.m_hasValue = false;
884  m_hasValue = true;
885  }
886  }
887 
888  return *this;
889  }
890 
898  template <typename U = T,
899  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
900  inplace_optional_holder& assign(NoInitT, inplace_optional_holder&& other)
901  {
902  if (this != &other)
903  {
904  reset();
905  if (other.hasValue())
906  {
907  m_storage.assign(NoInit, std::move(other.m_storage));
908  other.m_hasValue = false;
909  m_hasValue = true;
910  }
911  }
912 
913  return *this;
914  }
915 
923  inplace_optional_holder& operator=(const T& val)
924  {
925  set(val);
926 
927  return *this;
928  }
929 
937  inplace_optional_holder& operator=(T&& val)
938  {
939  set(std::move(val));
940 
941  return *this;
942  }
943 
947  void reset() noexcept
948  {
949  if (hasValue())
950  {
951  m_storage.getObject()->~T();
952  m_hasValue = false;
953  }
954  }
955 
961  bool hasValue() const noexcept
962  {
963  return m_hasValue;
964  }
965 
973  const T& operator*() const
974  {
975  this->checkHasValue();
976  return *m_storage.getObject();
977  }
978 
986  T& operator*()
987  {
988  this->checkHasValue();
989  return *m_storage.getObject();
990  }
991 
992 private:
993  template <typename U = T>
994  void set(U&& val)
995  {
996  reset();
997  new (m_storage.getStorage()) T(std::forward<U>(val));
998  m_hasValue = true;
999  }
1000 
1001  in_place_storage<T> m_storage;
1002  bool m_hasValue = false;
1003 };
1004 
1005 } // namespace detail
1006 
1007 // Be aware that if Inplace/HeapOptionalHolder is defined by typename, C++ compiler will have problem with
1008 // template function overload, see HashCodeUtil.h (overloads for objects and for Inplace/HeapOptionalHolder).
1012 template <typename T>
1013 using InplaceOptionalHolder = detail::inplace_optional_holder<T>;
1014 
1018 template <typename T, typename ALLOC = std::allocator<T>>
1019 using HeapOptionalHolder = detail::heap_optional_holder<T, ALLOC>;
1020 
1021 } // namespace zserio
1022 
1023 #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:878
constexpr NullOptType NullOpt
constexpr bool operator==(BasicStringView< CharT, Traits > lhs, BasicStringView< CharT, Traits > rhs) noexcept
Definition: StringView.h:852
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)