Zserio C++17 runtime library  0.5.0
Built for Zserio 2.17.0
Variant.h
Go to the documentation of this file.
1 #ifndef ZSERIO_VARIANT_H_INC
2 #define ZSERIO_VARIANT_H_INC
3 
4 #include <variant>
5 
8 #include "zserio/HashCodeUtil.h"
9 #include "zserio/Traits.h"
10 
11 namespace zserio
12 {
13 
14 namespace detail
15 {
16 
17 template <typename T, bool BIG = (sizeof(T) > 3 * sizeof(void*))>
18 struct variant_element
19 {
20  using type = T;
21  using is_heap_allocated = std::false_type;
22 };
23 
24 template <typename T>
25 struct variant_element<T, true>
26 {
27  using type = T*;
28  using is_heap_allocated = std::true_type;
29 };
30 
31 template <size_t I, typename... T>
32 struct type_at
33 {
34  using type = std::tuple_element_t<I, std::tuple<T...>>;
35 };
36 
37 template <auto I, typename... T>
38 using type_at_t = typename type_at<static_cast<size_t>(I), T...>::type;
39 
40 template <auto I, typename... T>
41 struct is_variant_heap_allocated : variant_element<type_at_t<I, T...>>::is_heap_allocated
42 {};
43 
44 template <auto I, class... T>
45 constexpr bool is_variant_heap_allocated_v = is_variant_heap_allocated<I, T...>::value;
46 
47 template <typename... T>
48 struct any_variant_heap_allocated : std::false_type
49 {};
50 
51 template <typename T, typename... ARGS>
52 struct any_variant_heap_allocated<T, ARGS...>
53  : std::bool_constant<variant_element<T>::is_heap_allocated::value ||
54  any_variant_heap_allocated<ARGS...>::value>
55 {};
56 
57 template <typename... ARGS>
58 inline constexpr bool any_variant_heap_allocated_v = any_variant_heap_allocated<ARGS...>::value;
59 
60 } // namespace detail
61 
66 {
67 public:
69 };
70 
71 template <auto I>
73 {};
74 
78 template <auto I>
80 
92 template <typename ALLOC, typename INDEX, typename... T>
93 class BasicVariant : public AllocatorHolder<ALLOC>
94 {
95  using AllocTraits = std::allocator_traits<ALLOC>;
98 
99 public:
101  using allocator_type = ALLOC;
102  using IndexType = INDEX;
103  using VariantType = std::variant<typename detail::variant_element<T>::type...>;
104 
109  BasicVariant(ALLOC())
110  {}
111 
119  explicit BasicVariant(const ALLOC& allocator) :
120  AllocatorHolder<ALLOC>(allocator)
121  {
122  if constexpr (detail::is_variant_heap_allocated_v<0, T...>)
123  {
124  emplace<INDEX{}>(); // enforce no empty state like std::variant
125  }
126  }
127 
136  template <INDEX I, typename... ARGS,
137  std::enable_if_t<!detail::is_variant_heap_allocated_v<I, T...>>* = nullptr,
138  std::enable_if_t<!is_first_allocator_v<ARGS...>>* = nullptr>
139  explicit BasicVariant(in_place_index_t<I>, ARGS&&... args) :
140  m_data(std::in_place_index<static_cast<size_t>(I)>, std::forward<ARGS>(args)...)
141  {}
142 
152  template <INDEX I, typename... ARGS,
153  std::enable_if_t<!detail::is_variant_heap_allocated_v<I, T...>>* = nullptr>
154  BasicVariant(in_place_index_t<I>, const ALLOC& allocator, ARGS&&... args) :
155  AllocatorHolder<ALLOC>(allocator),
156  m_data(std::in_place_index<static_cast<size_t>(I)>, std::forward<ARGS>(args)...)
157  {}
158 
168  template <INDEX I, typename... ARGS, typename U = detail::type_at_t<static_cast<size_t>(I), T...>,
169  std::enable_if_t<detail::is_variant_heap_allocated_v<I, T...>>* = nullptr>
170  BasicVariant(in_place_index_t<I>, const ALLOC& allocator, ARGS&&... args) :
171  AllocatorHolder<ALLOC>(allocator),
172  m_data(std::in_place_index<static_cast<size_t>(I)>, allocateValue<U>(std::forward<ARGS>(args)...))
173  {}
174 
183  template <INDEX I, typename... ARGS, typename U = detail::type_at_t<static_cast<size_t>(I), T...>,
184  std::enable_if_t<detail::is_variant_heap_allocated_v<I, T...>>* = nullptr,
185  std::enable_if_t<!is_first_allocator_v<ARGS...>>* = nullptr>
186  explicit BasicVariant(in_place_index_t<I>, ARGS&&... args) :
187  m_data(std::in_place_index<static_cast<size_t>(I)>, allocateValue<U>(std::forward<ARGS>(args)...))
188  {}
189 
194  {
195  clear();
196  }
197 
203  BasicVariant(const BasicVariant& other) :
204  AllocatorHolder<ALLOC>(
205  AllocTraits::select_on_container_copy_construction(other.get_allocator_ref()))
206  {
207  copy(other);
208  }
209 
218  BasicVariant(const BasicVariant& other, const ALLOC& allocator) :
219  AllocatorHolder<ALLOC>(allocator)
220  {
221  copy(other);
222  }
223 
234  {
235  if (this != &other)
236  {
237  clear();
238  if constexpr (AllocTraits::propagate_on_container_copy_assignment::value)
239  {
241  }
242  copy(other);
243  }
244 
245  return *this;
246  }
247 
256  AllocatorHolder<ALLOC>(std::move(other.get_allocator_ref()))
257  {
258  move(std::move(other));
259  }
260 
269  BasicVariant(BasicVariant&& other, const ALLOC& allocator) :
270  AllocatorHolder<ALLOC>(allocator)
271  {
272  move(std::move(other));
273  }
274 
283  {
284  if (this != &other)
285  {
286  clear();
287  if constexpr (AllocTraits::propagate_on_container_move_assignment::value)
288  {
289  set_allocator(std::move(other.get_allocator_ref()));
290  }
291  move(std::move(other));
292  }
293 
294  return *this;
295  }
296 
305  bool valueless_by_exception() const noexcept
306  {
307  if constexpr (detail::any_variant_heap_allocated_v<T...>)
308  {
309  return m_data.valueless_by_exception() ||
310  valuelessByMoveSeq(std::make_index_sequence<sizeof...(T)>());
311  }
312  else
313  {
314  return m_data.valueless_by_exception();
315  }
316  }
317 
323  template <INDEX I, typename... ARGS>
324  decltype(auto) emplace(ARGS&&... args)
325  {
326  clear();
327  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
328  {
329  using U = detail::type_at_t<I, T...>;
330  U* ptr = allocateValue<U>(std::forward<ARGS>(args)...);
331  return *m_data.template emplace<static_cast<size_t>(I)>(ptr);
332  }
333  else
334  {
335  return m_data.template emplace<static_cast<size_t>(I)>(std::forward<ARGS>(args)...);
336  }
337  }
338 
342  INDEX index() const noexcept
343  {
344  if constexpr (detail::any_variant_heap_allocated_v<T...>)
345  {
346  return valuelessByMoveSeq(std::make_index_sequence<sizeof...(T)>())
347  ? static_cast<INDEX>(std::variant_npos)
348  : static_cast<INDEX>(m_data.index());
349  }
350  else
351  {
352  return static_cast<INDEX>(m_data.index());
353  }
354  }
355 
359  template <INDEX I, typename U = detail::type_at_t<I, T...>>
360  U* get_if() noexcept
361  {
362  if (I != index())
363  {
364  return nullptr;
365  }
366  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
367  {
368  return *std::get_if<static_cast<size_t>(I)>(&m_data);
369  }
370  else
371  {
372  return std::get_if<static_cast<size_t>(I)>(&m_data);
373  }
374  }
375 
379  template <INDEX I, typename U = detail::type_at_t<I, T...>>
380  const U* get_if() const noexcept
381  {
382  if (I != index())
383  {
384  return nullptr;
385  }
386  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
387  {
388  return *std::get_if<static_cast<size_t>(I)>(&m_data);
389  }
390  else
391  {
392  return std::get_if<static_cast<size_t>(I)>(&m_data);
393  }
394  }
395 
401  template <INDEX I, typename U = detail::type_at_t<I, T...>>
402  U& get()
403  {
404  auto ptr = get_if<I>();
405  if (!ptr)
406  {
407  throw BadVariantAccess("Variant: Attempt to retrieve an inactive element at index ")
408  << static_cast<size_t>(I) << ". Active element index is " << static_cast<size_t>(index());
409  }
410  return *ptr;
411  }
412 
418  template <INDEX I, typename U = detail::type_at_t<I, T...>>
419  const U& get() const
420  {
421  auto ptr = get_if<I>();
422  if (!ptr)
423  {
424  throw BadVariantAccess("Variant: Attempt to retrieve an inactive element at index ")
425  << static_cast<size_t>(I) << ". Active element index is " << static_cast<size_t>(index());
426  }
427  return *ptr;
428  }
429 
435  void swap(BasicVariant& other)
436  {
437  m_data.swap(other.m_data);
438  if constexpr (AllocTraits::propagate_on_container_swap::value)
439  {
440  using std::swap;
442  }
443  }
444 
452  template <typename F>
453  auto visit(F&& fun) -> decltype(fun(std::declval<detail::type_at_t<0, T...>>()))
454  {
456  {
457  throw BadVariantAccess("Variant: Cannot visit variant in valueless state");
458  }
459  using R = decltype(fun(std::declval<detail::type_at_t<0, T...>>()));
460  if constexpr (std::is_same_v<R, void>)
461  {
462  std::nullptr_t dummy;
463  visitSeq(std::forward<F>(fun), dummy, std::make_index_sequence<sizeof...(T)>());
464  }
465  else
466  {
467  R ret;
468  visitSeq(std::forward<F>(fun), ret, std::make_index_sequence<sizeof...(T)>());
469  return ret;
470  }
471  }
472 
480  template <typename F>
481  auto visit(F&& fun) const -> decltype(fun(std::declval<detail::type_at_t<0, T...>>()))
482  {
484  {
485  throw BadVariantAccess("Variant: Cannot visit variant in valueless state");
486  }
487  using R = decltype(fun(std::declval<detail::type_at_t<0, T...>>()));
488  if constexpr (std::is_same_v<R, void>)
489  {
490  std::nullptr_t dummy;
491  visitSeq(std::forward<F>(fun), dummy, std::make_index_sequence<sizeof...(T)>());
492  }
493  else
494  {
495  R ret;
496  visitSeq(std::forward<F>(fun), ret, std::make_index_sequence<sizeof...(T)>());
497  return ret;
498  }
499  }
500 
506  bool operator==(const BasicVariant& other) const
507  {
508  if (index() != other.index())
509  {
510  return false;
511  }
512  return equalSeq(other, std::make_index_sequence<sizeof...(T)>());
513  }
514 
520  bool operator!=(const BasicVariant& other) const
521  {
522  return !(*this == other);
523  }
524 
530  bool operator<(const BasicVariant& other) const
531  {
532  if (index() != other.index())
533  {
534  return index() < other.index();
535  }
536  return lessSeq(other, std::make_index_sequence<sizeof...(T)>());
537  }
538 
544  bool operator>(const BasicVariant& other) const
545  {
546  return other < *this;
547  }
548 
554  bool operator<=(const BasicVariant& other) const
555  {
556  return !(other < *this);
557  }
558 
564  bool operator>=(const BasicVariant& other) const
565  {
566  return !(other > *this);
567  }
568 
569 private:
570  template <size_t... I>
571  bool valuelessByMoveSeq(std::index_sequence<I...>) const
572  {
573  return (valuelessByMove<I>() || ...);
574  }
575 
576  template <size_t I>
577  bool valuelessByMove() const
578  {
579  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
580  {
581  if (I != m_data.index())
582  {
583  return false;
584  }
585  return *std::get_if<I>(&m_data) == nullptr;
586  }
587  else
588  {
589  return false;
590  }
591  }
592 
593  template <size_t... I, typename F, typename R>
594  void visitSeq(F&& fun, R& returnValue, std::index_sequence<I...>)
595  {
596  (visit<I>(fun, returnValue), ...);
597  }
598 
599  template <size_t... I, typename F, typename R>
600  void visitSeq(F&& fun, R& returnValue, std::index_sequence<I...>) const
601  {
602  (visit<I>(fun, returnValue), ...);
603  }
604 
605  template <size_t I, typename F, typename R>
606  void visit(F&& fun, R& returnValue)
607  {
608  if (I != static_cast<size_t>(index()))
609  {
610  return;
611  }
612  if constexpr (std::is_same_v<R, std::nullptr_t>)
613  {
614  std::forward<F>(fun)(get<static_cast<INDEX>(I)>());
615  }
616  else
617  {
618  returnValue = std::forward<F>(fun)(get<static_cast<INDEX>(I)>());
619  }
620  }
621 
622  template <size_t I, typename F, typename R>
623  void visit(F&& fun, R& returnValue) const
624  {
625  if (I != static_cast<size_t>(index()))
626  {
627  return;
628  }
629  if constexpr (std::is_same_v<R, std::nullptr_t>)
630  {
631  std::forward<F>(fun)(get<static_cast<INDEX>(I)>());
632  }
633  else
634  {
635  returnValue = std::forward<F>(fun)(get<static_cast<INDEX>(I)>());
636  }
637  }
638 
639  template <size_t... I>
640  bool equalSeq(const BasicVariant& other, std::index_sequence<I...>) const
641  {
642  return (equal<I>(other) && ...);
643  }
644 
645  template <size_t I>
646  bool equal(const BasicVariant& other) const
647  {
648  if (I != static_cast<size_t>(index()))
649  {
650  return true;
651  }
652  return get<static_cast<INDEX>(I)>() == other.get<static_cast<INDEX>(I)>();
653  }
654 
655  template <size_t... I>
656  bool lessSeq(const BasicVariant& other, std::index_sequence<I...>) const
657  {
658  return (less<I>(other) && ...);
659  }
660 
661  template <size_t I>
662  bool less(const BasicVariant& other) const
663  {
664  if (I != static_cast<size_t>(index()))
665  {
666  return true;
667  }
668  return get<static_cast<INDEX>(I)>() < other.get<static_cast<INDEX>(I)>();
669  }
670 
671  template <typename U, typename... ARGS>
672  U* allocateValue(ARGS&&... args)
673  {
674  using AllocType = RebindAlloc<ALLOC, U>;
675  using ConcreteAllocTraits = std::allocator_traits<AllocType>;
676  AllocType typedAlloc = get_allocator_ref();
677  auto ptr = ConcreteAllocTraits::allocate(typedAlloc, 1);
678  try
679  {
680  ConcreteAllocTraits::construct(typedAlloc, std::addressof(*ptr), std::forward<ARGS>(args)...);
681  }
682  catch (...)
683  {
684  ConcreteAllocTraits::deallocate(typedAlloc, ptr, 1);
685  throw;
686  }
687  return ptr;
688  }
689 
690  // same as for std::variant throwing dtors are not supported see
691  // https://stackoverflow.com/questions/78602733/should-stdvariant-be-nothrow-destructible-when-its-alternative-has-potentially
692  template <typename U>
693  void destroyValue(U* ptr)
694  {
695  if (ptr)
696  {
697  using AllocType = RebindAlloc<ALLOC, U>;
698  using ConcreteAllocTraits = std::allocator_traits<AllocType>;
699  AllocType typedAlloc = get_allocator_ref();
700  ConcreteAllocTraits::destroy(typedAlloc, ptr);
701  ConcreteAllocTraits::deallocate(typedAlloc, ptr, 1);
702  }
703  }
704 
705  void copy(const BasicVariant& other)
706  {
707  // assumes this holder is cleared
708 
709  copySeq(other, std::make_index_sequence<sizeof...(T)>());
710  }
711 
712  template <size_t... I>
713  void copySeq(const BasicVariant& other, std::index_sequence<I...>)
714  {
715  (copy<I>(other), ...);
716  }
717 
718  template <size_t I>
719  void copy(const BasicVariant& other)
720  {
721  if (I != static_cast<size_t>(other.index()))
722  {
723  return;
724  }
725  emplace<static_cast<INDEX>(I)>(other.get<static_cast<INDEX>(I)>());
726  }
727 
728  void move(BasicVariant&& other)
729  {
730  // assumes this holder is cleared
731 
732  moveSeq(std::move(other), std::make_index_sequence<sizeof...(T)>());
733  }
734 
735  template <size_t... I>
736  void moveSeq(BasicVariant&& other, std::index_sequence<I...>)
737  {
738  (move<I>(std::move(other)), ...);
739  }
740 
741  template <size_t I>
742  void move(BasicVariant&& other)
743  {
744  if (I != static_cast<size_t>(other.index()))
745  {
746  return;
747  }
748  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
749  {
750  if (get_allocator_ref() == other.get_allocator_ref())
751  {
752  auto& ptr = *std::get_if<I>(&other.m_data);
753  m_data.template emplace<I>(ptr);
754  ptr = nullptr;
755  }
756  else
757  {
758  using U = detail::type_at_t<I, T...>;
759  U* ptr = allocateValue<U>(std::move(**std::get_if<I>(&other.m_data)));
760  m_data.template emplace<I>(ptr);
761  }
762  }
763  else
764  {
765  auto& value = *std::get_if<I>(&other.m_data);
766  m_data.template emplace<I>(std::move(value));
767  }
768  }
769 
770  void clear()
771  {
772  clearSeq(std::make_index_sequence<sizeof...(T)>());
773  }
774 
775  template <size_t... I>
776  void clearSeq(std::index_sequence<I...>)
777  {
778  (clear<I>(), ...);
779  }
780 
781  template <size_t I>
782  void clear()
783  {
784  if (I != static_cast<size_t>(index()))
785  {
786  return;
787  }
788  if constexpr (detail::is_variant_heap_allocated_v<I, T...>)
789  {
790  auto& ptr = *std::get_if<I>(&m_data);
791  destroyValue(ptr);
792  ptr = nullptr;
793  }
794  }
795 
796  VariantType m_data;
797 };
798 
799 // Using declarations
800 
801 template <typename INDEX, typename... T>
803 
811 template <auto I, typename ALLOC, typename INDEX, typename... T>
812 decltype(auto) get(BasicVariant<ALLOC, INDEX, T...>& var)
813 {
814  static_assert(std::is_same_v<decltype(I), INDEX>, "Index has a wrong type");
815  return var.template get<I>();
816 }
817 
825 template <auto I, typename ALLOC, typename INDEX, typename... T>
826 decltype(auto) get(const BasicVariant<ALLOC, INDEX, T...>& var)
827 {
828  static_assert(std::is_same_v<decltype(I), INDEX>, "Index has a wrong type");
829  return var.template get<I>();
830 }
831 
837 template <auto I, typename ALLOC, typename INDEX, typename... T>
838 decltype(auto) get_if(BasicVariant<ALLOC, INDEX, T...>* var)
839 {
840  static_assert(std::is_same_v<decltype(I), INDEX>, "Index has a wrong type");
841  return var->template get_if<I>();
842 }
843 
849 template <auto I, typename ALLOC, typename INDEX, typename... T>
850 decltype(auto) get_if(const BasicVariant<ALLOC, INDEX, T...>* var)
851 {
852  static_assert(std::is_same_v<decltype(I), INDEX>, "Index has a wrong type");
853  return var->template get_if<I>();
854 }
855 
864 template <typename F, typename ALLOC, typename INDEX, typename... T>
865 decltype(auto) visit(F&& fun, BasicVariant<ALLOC, INDEX, T...>& var)
866 {
867  return var.visit(std::forward<F>(fun));
868 }
869 
878 template <typename F, typename ALLOC, typename INDEX, typename... T>
879 decltype(auto) visit(F&& fun, const BasicVariant<ALLOC, INDEX, T...>& var)
880 {
881  return var.visit(std::forward<F>(fun));
882 }
883 
890 template <typename ALLOC, typename INDEX, typename... T>
891 uint32_t calcHashCode(uint32_t seed, const BasicVariant<ALLOC, INDEX, T...>& var)
892 {
893  uint32_t result = seed;
894  result = calcHashCode(result, static_cast<size_t>(var.index()));
895  var.visit([&result](const auto& value) {
896  result = calcHashCode(result, value);
897  });
898  return result;
899 }
900 
901 } // namespace zserio
902 
903 namespace std
904 {
905 
906 template <typename ALLOC, typename INDEX, typename... T>
907 struct hash<zserio::BasicVariant<ALLOC, INDEX, T...>>
908 {
910  {
911  return zserio::calcHashCode(zserio::HASH_SEED, var);
912  }
913 };
914 
915 } // namespace std
916 
917 #endif // ZSERIO_VARIANT_H_INC
allocator_type & get_allocator_ref()
void set_allocator(const allocator_type &allocator)
auto visit(F &&fun) const -> decltype(fun(std::declval< detail::type_at_t< 0, T... >>()))
Definition: Variant.h:481
BasicVariant(const BasicVariant &other, const ALLOC &allocator)
Definition: Variant.h:218
void swap(BasicVariant &other)
Definition: Variant.h:435
BasicVariant(BasicVariant &&other, const ALLOC &allocator)
Definition: Variant.h:269
BasicVariant(const ALLOC &allocator)
Definition: Variant.h:119
bool operator<=(const BasicVariant &other) const
Definition: Variant.h:554
bool operator<(const BasicVariant &other) const
Definition: Variant.h:530
bool operator>(const BasicVariant &other) const
Definition: Variant.h:544
BasicVariant & operator=(BasicVariant &&other)
Definition: Variant.h:282
U * get_if() noexcept
Definition: Variant.h:360
bool valueless_by_exception() const noexcept
Definition: Variant.h:305
BasicVariant & operator=(const BasicVariant &other)
Definition: Variant.h:233
BasicVariant(in_place_index_t< I >, ARGS &&... args)
Definition: Variant.h:186
std::variant< typename detail::variant_element< T >::type... > VariantType
Definition: Variant.h:103
BasicVariant(in_place_index_t< I >, const ALLOC &allocator, ARGS &&... args)
Definition: Variant.h:154
bool operator==(const BasicVariant &other) const
Definition: Variant.h:506
BasicVariant(in_place_index_t< I >, const ALLOC &allocator, ARGS &&... args)
Definition: Variant.h:170
const U * get_if() const noexcept
Definition: Variant.h:380
bool operator>=(const BasicVariant &other) const
Definition: Variant.h:564
BasicVariant(in_place_index_t< I >, ARGS &&... args)
Definition: Variant.h:139
auto visit(F &&fun) -> decltype(fun(std::declval< detail::type_at_t< 0, T... >>()))
Definition: Variant.h:453
INDEX index() const noexcept
Definition: Variant.h:342
BasicVariant(const BasicVariant &other)
Definition: Variant.h:203
BasicVariant(BasicVariant &&other)
Definition: Variant.h:255
decltype(auto) emplace(ARGS &&... args)
Definition: Variant.h:324
const U & get() const
Definition: Variant.h:419
bool operator!=(const BasicVariant &other) const
Definition: Variant.h:520
CppRuntimeException(const char *message="")
Definition: BitBuffer.h:602
decltype(auto) visit(F &&fun, BasicVariant< ALLOC, INDEX, T... > &var)
Definition: Variant.h:865
constexpr in_place_index_t< I > in_place_index
Definition: Variant.h:79
decltype(auto) get(BasicVariant< ALLOC, INDEX, T... > &var)
Definition: Variant.h:812
uint32_t calcHashCode(uint32_t seedValue, const ArrayView< T, ARRAY_TRAITS > &array)
Definition: ArrayView.h:860
decltype(auto) get_if(BasicVariant< ALLOC, INDEX, T... > *var)
Definition: Variant.h:838
constexpr bool is_first_allocator_v
Definition: Traits.h:114
size_t operator()(const zserio::BasicVariant< ALLOC, INDEX, T... > &var) const
Definition: Variant.h:909