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