Zserio C++ runtime library  1.4.2
Built for Zserio 2.19.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) noexcept(std::is_nothrow_move_constructible<T>::value)
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) noexcept(
633  std::is_nothrow_move_constructible<T>::value)
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) noexcept(std::is_nothrow_constructible<T>::value)
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) noexcept(std::is_nothrow_move_constructible<T>::value)
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_assignable<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_assignable<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) noexcept(
876  std::is_nothrow_move_assignable<in_place_storage<T>>::value)
877  {
878  if (this != &other)
879  {
880  reset();
881  if (other.hasValue())
882  {
883  m_storage = std::move(other.m_storage);
884  other.m_hasValue = false;
885  m_hasValue = true;
886  }
887  }
888 
889  return *this;
890  }
891 
899  template <typename U = T,
900  typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
901  inplace_optional_holder& assign(NoInitT, inplace_optional_holder&& other) noexcept(
902  std::is_nothrow_move_assignable<in_place_storage<T>>::value)
903  {
904  if (this != &other)
905  {
906  reset();
907  if (other.hasValue())
908  {
909  m_storage.assign(NoInit, std::move(other.m_storage));
910  other.m_hasValue = false;
911  m_hasValue = true;
912  }
913  }
914 
915  return *this;
916  }
917 
925  inplace_optional_holder& operator=(const T& val)
926  {
927  set(val);
928 
929  return *this;
930  }
931 
939  inplace_optional_holder& operator=(T&& val)
940  {
941  set(std::move(val));
942 
943  return *this;
944  }
945 
949  void reset() noexcept
950  {
951  if (hasValue())
952  {
953  m_storage.getObject()->~T();
954  m_hasValue = false;
955  }
956  }
957 
963  bool hasValue() const noexcept
964  {
965  return m_hasValue;
966  }
967 
975  const T& operator*() const
976  {
977  this->checkHasValue();
978  return *m_storage.getObject();
979  }
980 
988  T& operator*()
989  {
990  this->checkHasValue();
991  return *m_storage.getObject();
992  }
993 
994 private:
995  template <typename U = T>
996  void set(U&& val)
997  {
998  reset();
999  new (m_storage.getStorage()) T(std::forward<U>(val));
1000  m_hasValue = true;
1001  }
1002 
1003  in_place_storage<T> m_storage;
1004  bool m_hasValue = false;
1005 };
1006 
1007 } // namespace detail
1008 
1009 // Be aware that if Inplace/HeapOptionalHolder is defined by typename, C++ compiler will have problem with
1010 // template function overload, see HashCodeUtil.h (overloads for objects and for Inplace/HeapOptionalHolder).
1014 template <typename T>
1015 using InplaceOptionalHolder = detail::inplace_optional_holder<T>;
1016 
1020 template <typename T, typename ALLOC = std::allocator<T>>
1021 using HeapOptionalHolder = detail::heap_optional_holder<T, ALLOC>;
1022 
1023 } // namespace zserio
1024 
1025 #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:104
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:92
constexpr InPlaceT InPlace
detail::inplace_optional_holder< T > InplaceOptionalHolder
constexpr InPlaceT()=default
constexpr NullOptType(int)