RPNX::DataStructures
Header-only C++ data structures and supporting utilities.
Loading...
Searching...
No Matches
variant.hpp
1// Copyright 2024-2026 Ryan P. Nicholl, rnicholl@protonmail.com
2
3#ifndef RPNX_VARIANT_HPP
4#define RPNX_VARIANT_HPP
5
6#include <array>
7#include <cassert>
8#include <cinttypes>
9#include <compare>
10#include <cstdint>
11#include <memory>
12#include <tuple>
13#include <typeindex>
14#include <typeinfo>
15#include <variant>
16
17namespace rpnx
18{
19
20 template < typename T, typename... Ts >
21 struct index_of;
22
23 // Base case: T matches the first type in the list.
24 template < typename T, typename... Ts >
25 struct index_of< T, T, Ts... > : std::integral_constant< std::size_t, 0 >
26 {
27 };
28
29 // Recursive case: T does not match the first type in the list.
30 template < typename T, typename U, typename... Ts >
31 struct index_of< T, U, Ts... > : std::integral_constant< std::size_t, 1 + index_of< T, Ts... >::value >
32 {
33 };
34
35 template < typename Allocator, typename... Ts >
36 class basic_variant;
37
48 template < typename Allocator >
50 {
51 public:
52 template < typename T >
53 class is_supported_variant : public std::false_type
54 {
55 };
56
57 template < typename Allocator2, typename... Ts >
58 class is_supported_variant< basic_variant< Allocator2, Ts... > > : public std::true_type
59 {
60 };
61
63 using default_new_func = void* (*)(Allocator&);
65 using copy_func = void* (*)(Allocator&, void const*);
67 using delete_func = void (*)(Allocator&, void*) noexcept;
69 using less_func = bool (*)(void const*, void const*);
71 using equals_func = bool (*)(void const*, void const*);
73 using three_way_func = std::strong_ordering (*)(void const*, void const*);
75 using new_move_from_func = void* (*)(Allocator&, void*);
76
79 {
81 copy_func m_copy = nullptr;
83 less_func m_less = nullptr;
95 std::type_info const* m_type_info = nullptr;
96 };
97
98 // Function templates follow...
99
105 template < typename T >
107 {
108 return variant_info{.m_copy = &type_copy_func< T >, .m_less = &type_less_func< T >, .m_equals = &type_equals_func< T >, .m_three_way = &type_three_way_func< T >, .m_destroy = &type_delete_func< T >, .m_default_new = &type_default_new_func< T >, .m_new_move_from = &type_new_from_move_func< T >, .m_type_info = &typeid(T)};
109 }
110
111 private:
112 template < typename T >
113 static void* type_copy_func(Allocator& allocator, void const* source)
114 {
115 static_assert(!std::is_same_v< T, void >, "T must not be void");
116 using alloc_triats = std::allocator_traits< Allocator >;
117 using rebound_alloc_type = typename alloc_triats::template rebind_alloc< T >;
118 using rebound_alloc_traits = std::allocator_traits< rebound_alloc_type >;
119
120 rebound_alloc_type rebound_alloc(allocator); // Rebound allocator for type T
121 T* ptr = rebound_alloc_traits::allocate(rebound_alloc, 1); // Allocate space for one T
122
123 try
124 {
125 rebound_alloc_traits::construct(rebound_alloc, ptr,
126 *static_cast< T const* >(source)); // Construct T using the copy constructor
127 }
128 catch (...)
129 {
130 rebound_alloc_traits::deallocate(rebound_alloc, ptr, 1); // Ensure deallocation on exception
131 throw; // Re-throw the exception
132 }
133 return ptr;
134 }
135
136 // Deallocation and destruction logic using Allocator
137 template < typename T >
138 static void type_delete_func(Allocator& allocator, void* object) noexcept
139 {
140 using rebound_allocator_type = typename std::allocator_traits< Allocator >::template rebind_alloc< T >;
141 rebound_allocator_type typed_allocator(allocator); // Rebind the allocator to T
142 T* obj_ptr = static_cast< T* >(object);
143 std::allocator_traits< rebound_allocator_type >::destroy(typed_allocator, obj_ptr); // Destroy the object
144 std::allocator_traits< rebound_allocator_type >::deallocate(typed_allocator, obj_ptr, 1); // Deallocate memory
145 }
146
147 // Allocation and default construction logic using Allocator
148 template < typename T >
149 static void* type_default_new_func(Allocator& allocator)
150 {
151 using allocator_type = typename std::allocator_traits< Allocator >::template rebind_alloc< T >;
152 using alloc_traits = std::allocator_traits< allocator_type >;
153
154 allocator_type typed_allocator(allocator); // Rebind the allocator to T
155 T* ptr = alloc_traits::allocate(typed_allocator, 1); // Allocate space for one T
156
157 try
158 {
159 alloc_traits::construct(typed_allocator, ptr); // Default-construct T
160 }
161 catch (...)
162 {
163 alloc_traits::deallocate(typed_allocator, ptr, 1); // Ensure deallocation on exception
164 throw; // Re-throw the exception
165 }
166
167 return ptr;
168 }
169
170 template < typename T >
171 static void* type_new_from_move_func(Allocator& allocator, void* source)
172 {
173 using alloc_traits = std::allocator_traits< Allocator >;
174 using rebound_alloc_type = typename alloc_traits::template rebind_alloc< T >;
175 using rebound_alloc_traits = std::allocator_traits< rebound_alloc_type >;
176 rebound_alloc_type reboundAlloc(allocator); // Rebound allocator for type T
177 T* ptr = std::allocator_traits< rebound_alloc_type >::allocate(reboundAlloc, 1); // Allocate space for one T
178
179 try
180 {
181 rebound_alloc_traits::construct(reboundAlloc, ptr, std::move(*static_cast< T* >(source))); // Move-construct T
182 }
183 catch (...)
184 {
185 rebound_alloc_traits::deallocate(reboundAlloc, ptr, 1); // Ensure deallocation on exception
186 throw; // Re-throw the exception
187 }
188 return ptr;
189 }
190
191 // Function to compare two objects of type T for less-than
192 template < typename T >
193 static bool type_less_func(void const* lhs, void const* rhs)
194 {
195 return *static_cast< T const* >(lhs) < *static_cast< T const* >(rhs);
196 }
197
198 // Function to check equality of two objects of type T
199 template < typename T >
200 static constexpr bool type_equals_func(void const* lhs, void const* rhs)
201 {
202 // If
203 if constexpr (std::equality_comparable_with< T, T >)
204 {
205 return *static_cast< T const* >(lhs) == *static_cast< T const* >(rhs);
206 }
207 else
208 {
209 // synthesize from <=>
210 return *static_cast< T const* >(lhs) <=> *static_cast< T const* >(rhs) == std::strong_ordering::equal;
211 }
212 }
213
214 // Function to perform a three-way comparison of two objects of type T
215 template < typename T >
216 static constexpr std::strong_ordering type_three_way_func(void const* lhs, void const* rhs)
217 {
218 if constexpr (std::three_way_comparable_with< T, T >)
219 {
220 // If T supports three-way comparison with itself
221 return std::strong_order(*static_cast< const T* >(lhs), *static_cast< const T* >(rhs));
222 }
223 else
224 {
225 // Fallback for types without three-way comparison support.
226 const T& l = *static_cast< const T* >(lhs);
227 const T& r = *static_cast< const T* >(rhs);
228
229 static_assert(std::is_same< decltype(l), decltype(r) >::value, "T must be the same type as T");
230 if (l < r)
231 return std::strong_ordering::less;
232 if (r < l)
233 return std::strong_ordering::greater;
234 return std::strong_ordering::equal;
235 }
236 }
237 };
238
243 template < typename... Ts >
245
247 template < typename T, typename... Ts >
248 inline constexpr auto& get_as(variant< Ts... >& v)
249 {
250 return v.template get_as< T >();
251 }
252
254 template < typename T, typename... Ts >
255 inline constexpr auto const& get_as(variant< Ts... > const& v)
256 {
257 return v.template get_as< T >();
258 }
259
262
265
267 template < typename V, typename F, typename R, std::size_t N, call_type C >
268 inline R apply_nth_visitor(V&& variant, F&& func);
269
271 template < std::size_t NBegin, std::size_t NEnd, typename V, typename F, typename R, call_type C >
272 inline R apply_nth_visitor_branched(std::size_t index, V&& variant, F&& func)
273 {
274 if constexpr (NBegin == NEnd - 1)
275 {
276 return apply_nth_visitor< V&&, F&&, R, NBegin, C >(std::forward<V&&>(variant), std::forward< F &&>(func));
277 }
278 else
279 {
280 static constexpr std::size_t Range = (NEnd - NBegin);
281 static_assert(Range >= 2, "Range should be at least 2");
282
283 static constexpr std::size_t Mid = NBegin + (Range / 2);
284 static_assert(NBegin < Mid && Mid < NEnd, "Mid should be between NBegin and NEnd");
285
286 if (index < Mid)
287 {
288 return apply_nth_visitor_branched< NBegin, Mid, V&&, F&&, R, C >(index, std::forward< V&& >(variant), std::forward< F&& >(func));
289 }
290 else
291 {
292 return apply_nth_visitor_branched< Mid, NEnd, V, F, R, C >(index, std::forward< V&& >(variant), std::forward< F&& >(func));
293 }
294 }
295 }
296
298 template < typename V, typename F, typename R, std::size_t N, call_type C >
299 inline R apply_nth_visitor(V&& variant, F&& func)
300 {
301 if constexpr (C == call_type::required)
302 {
303 if constexpr (std::is_same_v< R, void >)
304 {
305 func(variant.template get_n_unchecked< N >());
306 return;
307 }
308 else
309 {
310 return func(variant.template get_n_unchecked< N >());
311 }
312 }
313 else if constexpr (C == call_type::except_on_missing)
314 {
315 if constexpr (std::is_invocable_v< F, decltype(variant.template get_n_unchecked< N >()) >)
316 {
317 if constexpr (std::is_same_v< R, void >)
318 {
319 func(variant.template get_n_unchecked< N >());
320 return;
321 }
322 else
323 {
324 return func(variant.template get_n_unchecked< N >());
325 }
326 }
327 else
328 {
329 throw std::bad_variant_access();
330 }
331 }
332 else if constexpr (C == call_type::optional)
333 {
334 if constexpr (std::is_invocable_v< F, decltype(variant.template get_n_unchecked< N >()) >)
335 {
336 if constexpr (std::is_same_v< R, void >)
337 {
338 func(variant.template get_n_unchecked< N >());
339 return;
340 }
341 else
342 {
343 return func(variant.template get_n_unchecked< N >());
344 }
345 }
346 else if constexpr (!std::is_same_v< R, void >)
347 {
348 return R{};
349 }
350 }
351 }
352
354 template < typename V, typename F, typename R >
355 using variant_invoke_executor = R (*)(V&&, F&&);
356
358 template < typename F, typename R, typename A, typename... Vs >
360 {
361 using vexecptr = variant_invoke_executor< rpnx::basic_variant< A, Vs... >&, F, R >;
362 std::array< vexecptr, std::tuple_size_v< std::tuple< Vs... > > > result{};
363
364 update_variant_invoke_table_lvalue< 0, F, R, A, Vs... >(result);
365
366 return result;
367 }
368
370 template < typename V, std::size_t N >
372
374 template < typename A, typename... Vs, std::size_t N >
375 class variant_nth_member< rpnx::basic_variant< A, Vs... >, N >
376 {
377 public:
379 using type = std::tuple_element_t< N, std::tuple< Vs... > >;
380 };
381
383 template < typename V >
385
387 template < typename A, typename... Vs >
388 class variant_size< rpnx::basic_variant< A, Vs... > >
389 {
390 public:
392 using type = std::integral_constant< std::size_t, sizeof...(Vs) >;
394 static constexpr std::size_t value = sizeof...(Vs);
395 };
396
398 template < typename V >
399 static constexpr std::size_t variant_size_v = variant_size< V >::value;
400
402 template < typename V, std::size_t N >
404
406 template < std::size_t N, typename F, typename R, typename V, call_type C >
407 constexpr void update_variant_invoke_table2(std::array< variant_invoke_executor< V, F, R >, variant_size_v< std::remove_cvref_t< V > > >& table);
408
410 template < typename F, typename R, typename V, call_type C >
412 {
413 using vexecptr = variant_invoke_executor< V, F, R >;
414 std::array< vexecptr, variant_size_v< std::remove_cvref_t< V > > > result{};
415
417
418 return result;
419 }
420
422 template < std::size_t N, typename F, typename R, typename V, call_type C >
423 constexpr void update_variant_invoke_table2(std::array< variant_invoke_executor< V, F, R >, variant_size_v< std::remove_cvref_t< V > > >& table)
424 {
425 using invoke_ptr = variant_invoke_executor< V, F, R >;
426
427 if constexpr (N < variant_size_v< std::remove_cvref_t< V > >)
428 {
429 invoke_ptr ptr = &apply_nth_visitor< V, F, R, N, C >;
430 table[N] = ptr;
432 }
433 }
434
436 template < typename F, typename R, typename V, call_type C >
438
449 template < typename R, dispatch_type D = dispatch_type::automatic, typename V, typename F >
450 inline R apply_visitor(V&& variant, F&& func)
451 {
452 auto index = variant.index();
453 if constexpr (D == dispatch_type::branching || (D == dispatch_type::automatic && variant_size_v< std::remove_cvref_t<V> > <= 8))
454 {
455 return apply_nth_visitor_branched<0, variant_size_v< std::remove_cvref_t<V> >, V&&, F&&, R, call_type::required>(index, std::forward<V&&>(variant), std::forward<F&&>(func));
456 }
457 else
458 {
459 return variant_invoke_table2< F, R, V&&, call_type::required >[index](std::forward< V >(variant), std::forward< F >(func));
460 }
461 //
462 }
463
471 template < typename R, typename V, typename F >
472 inline R apply_visitor_checked(V&& variant, F&& func)
473 {
474 return variant_invoke_table2< F, R, V&&, call_type::except_on_missing >[variant.index()](std::forward< V >(variant), std::forward< F >(func));
475 }
476
483 template < typename R, typename V, typename F >
484 inline R try_apply_visitor(V&& variant, F&& func)
485 {
486 return variant_invoke_table2< F, R, V&&, call_type::optional >[variant.index()](std::forward< V >(variant), std::forward< F >(func));
487 }
488
494 template < typename A, typename... Ts >
496 {
497 basic_variant< A, Ts... >& m_val;
498
499 public:
502 {
503 }
504
505 template < typename T2 >
506 bool operator()(T2&& other) const
507 {
508 m_val = std::forward< T2 >(other);
509 return true;
510 }
511 };
512
522 template < typename Allocator, typename... Ts >
524 {
525 struct variant_impl_info
526 {
528 typename variant_detail< Allocator >::variant_info m_general_info;
530 std::size_t m_index = 0;
531 };
532
538 template < std::size_t N >
539 static consteval variant_impl_info calc_info()
540 {
541 using type = typename std::tuple_element< N, std::tuple< Ts... > >::type;
542 variant_impl_info result{};
543 auto v_info = variant_detail< Allocator >::template make_variant_info< type >();
544
545 result.m_general_info = v_info;
546 result.m_index = N;
547 return result;
548 }
549
550 template < std::size_t N >
551 static constexpr variant_impl_info s_v_info_for = calc_info< N >();
552
554 void* m_data = nullptr;
556 variant_impl_info const* m_vinf = nullptr;
558 [[no_unique_address]] Allocator m_alloc;
559
565 template < typename T2 >
566 static constexpr bool has_cvref_removed_identical_type()
567 {
568 // If T2 is the same as any of the types in Ts..., return true
569 return (std::is_same_v< std::remove_cvref_t< T2 >, Ts > || ...);
570 }
571
576 bool valueless() const
577 {
578 return m_data == nullptr;
579 }
580
585 bool valid() const
586 {
587 if (m_vinf == nullptr && m_data == nullptr)
588 {
589 return true;
590 }
591
592 if (m_vinf == nullptr || m_data == nullptr)
593 {
594 return false;
595 }
596
597 if (m_vinf->m_index >= std::tuple_size_v< std::tuple< Ts... > >)
598 {
599 return false;
600 }
601
602 return true;
603 }
604
605 public:
607 using allocator_type = Allocator;
608
614 constexpr basic_variant(const allocator_type& alloc = allocator_type()) : m_alloc(alloc)
615 {
616 assert((m_vinf == nullptr) == (m_data == nullptr));
617 m_vinf = &s_v_info_for< 0 >;
618 try
619 {
620 m_data = m_vinf->m_general_info.m_default_new(m_alloc);
621 }
622 catch (...)
623 {
624 m_vinf = nullptr;
625 throw;
626 }
627
628 assert(valid());
629 }
630
635 constexpr basic_variant(basic_variant< Allocator, Ts... >&& other) noexcept(std::is_nothrow_move_constructible_v<Allocator>) : m_alloc(std::move(other.m_alloc))
636 {
637 assert((m_vinf == nullptr) == (m_data == nullptr));
638
639 m_vinf = nullptr;
640 m_data = nullptr;
641
642 std::swap(m_vinf, other.m_vinf);
643 std::swap(m_data, other.m_data);
644
645 assert((m_vinf == nullptr) == (m_data == nullptr));
646
647 assert(valid());
648 }
649
655 constexpr basic_variant(basic_variant< Allocator, Ts... > const& other) : m_alloc(std::allocator_traits< Allocator >::select_on_container_copy_construction(other.m_alloc))
656 {
657
658 m_vinf = nullptr;
659 m_data = nullptr;
660 assert((other.m_vinf == nullptr) == (other.m_data == nullptr));
661
662 if (other.m_vinf == nullptr)
663 {
664 return;
665 }
666 m_vinf = other.m_vinf;
667 try
668 {
669 m_data = m_vinf->m_general_info.m_copy(m_alloc, other.m_data);
670 }
671 catch (...)
672 {
673 m_vinf = nullptr;
674 throw;
675 }
676
677 assert(valid());
678 }
679
684 {
685 assert((m_vinf == nullptr) == (m_data == nullptr));
686 reset();
687 assert((m_vinf == nullptr) == (m_data == nullptr));
688 }
689
693 void reset()
694 {
695 assert(valid());
696 if (m_vinf != nullptr)
697 {
698 assert(m_data != nullptr);
699 m_vinf->m_general_info.m_destroy(m_alloc, m_data);
700 m_data = nullptr;
701 m_vinf = nullptr;
702 }
703 assert(valid());
704 }
705
712 template < typename... Ts2 >
713 basic_variant(basic_variant< Allocator, Ts2... > const& other, std::enable_if_t< !std::is_same_v< basic_variant< Allocator, Ts... >, basic_variant< Allocator, Ts2... > > && !has_cvref_removed_identical_type< basic_variant< Allocator, Ts2... > >(), int > = 0) : basic_variant()
714 {
715 assert(valid());
716 // reset();
717 assert((m_vinf == nullptr) == (m_data == nullptr));
719 assert(valid());
720 }
721
727 template < typename T >
728 static consteval bool can_construct_subtype_with()
729 {
730 // Don't construct members using a reference to selftype, even if this looks possible
731 // because this is usually not what was intended.
732 // This can occur for example, expression = variant<plus, negate>, struct negate { expression expr; }
733 // In this case, a negate can be constructed using a single expression argument, which can ab
734 if constexpr (std::is_same_v< std::remove_cvref_t< T >, basic_variant< Allocator, Ts... > >)
735 {
736 return false;
737 }
738 else if constexpr (requires { typename std::remove_cvref_t< T >::value_type; })
739 {
740 if constexpr (std::is_same_v< typename std::remove_cvref_t< T >::value_type, basic_variant< Allocator, Ts... > >)
741 {
742 return false;
743 }
744 else
745 {
746 return (std::is_convertible_v< T, Ts > || ...);
747 }
748 }
749 else
750 {
751 // And there must be some constructible member type.
752 return (std::is_convertible_v< T, Ts > || ...);
753 }
754 }
755
763 template < typename T2 >
764 constexpr basic_variant(T2&& value, const allocator_type& alloc = allocator_type(), std::enable_if_t< rpnx::basic_variant< Allocator, Ts... >::can_construct_subtype_with< T2 >(), int > = 0) : m_alloc(alloc)
765 {
766 constexpr std::size_t index = constructor_index< T2 >();
767 using selected_type = std::tuple_element_t< index, std::tuple< Ts... > >;
768 using rebound_alloc_type = typename std::allocator_traits< allocator_type >::template rebind_alloc< selected_type >;
769 rebound_alloc_type rebound_alloc(m_alloc);
770
771 m_vinf = &s_v_info_for< index >;
772 assert(m_vinf->m_index == index);
773 assert(m_vinf->m_index < (std::tuple_size_v< std::tuple< Ts... > >));
774 try
775 {
776 // Rebind allocator to allocate memory for the selected alternative type.
777 // Rebound allocator
778 m_data = std::allocator_traits< rebound_alloc_type >::allocate(rebound_alloc,
779 1); // Allocate memory for the selected alternative.
780
781 // Construct the value in the allocated memory
782 std::allocator_traits< rebound_alloc_type >::construct(rebound_alloc, static_cast< selected_type* >(m_data), std::forward< T2 >(value));
783 }
784 catch (...)
785 {
786 if (m_data != nullptr)
787 {
788 std::allocator_traits< rebound_alloc_type >::deallocate(rebound_alloc, static_cast< selected_type* >(m_data), 1);
789 }
790 m_vinf = nullptr;
791
792 throw;
793 }
794 assert(valid());
795 }
796
804 template < typename T, std::enable_if_t< can_construct_subtype_with< T >(), int > = 0 >
805 constexpr basic_variant< Allocator, Ts... >& operator=(T&& value)
806 {
807 assert((m_vinf == nullptr) == (m_data == nullptr));
808
809 if (m_vinf != nullptr)
810 {
811 assert(m_vinf->m_index < std::tuple_size_v< std::tuple< Ts... > >);
812 }
813 auto old_vinf = m_vinf;
814 auto old_data = m_data;
815
816 m_vinf = nullptr;
817 m_data = nullptr;
818
819 constexpr std::size_t index = constructor_index< T >();
820 using selected_type = std::tuple_element_t< index, std::tuple< Ts... > >;
821 m_vinf = &s_v_info_for< index >;
822 assert(m_vinf->m_index == index);
823 assert(m_vinf->m_index < (std::tuple_size_v< std::tuple< Ts... > >));
824
825 // Rebind allocator to allocate memory for the selected alternative type.
826 using rebound_alloc_type = typename std::allocator_traits< allocator_type >::template rebind_alloc< selected_type >;
827 rebound_alloc_type value_alloc(m_alloc); // Rebound allocator
828 try
829 {
830 m_data = value_alloc.allocate(1); // Allocate memory for the selected alternative type
831 // Construct the value in the allocated memory
832 std::allocator_traits< rebound_alloc_type >::construct(value_alloc, static_cast< selected_type* >(m_data), std::forward< T >(value));
833 }
834 catch (...)
835 {
836 if (m_data != nullptr)
837 {
838 std::allocator_traits< rebound_alloc_type >::deallocate(value_alloc, static_cast< selected_type* >(m_data), 1);
839 }
840 m_vinf = nullptr;
841
842 m_vinf = old_vinf;
843 m_data = old_data;
844 old_vinf = nullptr;
845 old_data = nullptr;
846 throw;
847 }
848
849 if (old_vinf != nullptr)
850 {
851 old_vinf->m_general_info.m_destroy(m_alloc, old_data);
852 }
853 assert(valid());
854 return *this;
855 }
856
863 {
864 assert(valid());
865
866 m_alloc = other.m_alloc;
867
868 std::swap(m_vinf, other.m_vinf);
869 std::swap(m_data, other.m_data);
870
871 assert(valid());
872
873 other.reset();
874
875 return *this;
876 }
877
884 template < typename T >
886 {
887 return this->template apply_visitor< T& >(
888 [](auto& arg) -> T&
889 {
890 if constexpr (std::is_convertible_v<decltype(arg), T&>) {
891 return static_cast< T& >(arg);
892 } else {
893 // This branch should never be taken at runtime if the variant holds the correct type.
894 // However, apply_visitor instantiates the lambda for all possible types in the variant.
895 // We throw to satisfy the return type and indicate an internal error if it ever happened.
896 throw std::bad_variant_access();
897 }
898 });
899 }
900
907 template < typename T >
908 T const& static_cast_as() const
909 {
910 return this->template apply_visitor< T const& >(
911 [](auto& arg) -> T const&
912 {
913 if constexpr (std::is_convertible_v<decltype(arg), T const&>) {
914 return static_cast< T const& >(arg);
915 } else {
916 throw std::bad_variant_access();
917 }
918 });
919 }
920
927 template < typename T >
929 {
930 assert(valid());
931 // static_assert(has_cvref_removed_identical_type<T>(), "Must be in type list");
932 // Check if the variant is currently holding a value of type T
933 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
934 {
935 // If it is not, throw an exception
936 throw std::bad_variant_access();
937 }
938 // If it is, return a reference to the value, casted to T
939 return *static_cast< T* >(m_data);
940 }
941
948 template < typename T >
949 T& as()
950 {
951 assert(valid());
952 // static_assert(has_cvref_removed_identical_type<T>(), "Must be in type list");
953 // Check if the variant is currently holding a value of type T
954 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
955 {
956 // If it is not, throw an exception
957 throw std::bad_variant_access();
958 }
959 // If it is, return a reference to the value, casted to T
960 return *static_cast< T* >(m_data);
961 }
962
969 template < typename T >
971 {
972 assert(valid());
973 // static_assert(has_cvref_removed_identical_type<T>(), "Must be in type list");
974 // Check if the variant is currently holding a value of type T
975 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
976 {
977 // If it is not, throw an exception
978 throw std::bad_variant_access();
979 }
980 // If it is, return a reference to the value, casted to T
981 return *static_cast< T* >(m_data);
982 }
983
990 template < typename T >
992 {
993 assert(valid());
994 // static_assert(has_cvref_removed_identical_type<T>(), "Must be in type list");
995 // Check if the variant is currently holding a value of type T
996 assert(!(m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value));
997 // If it is, return a reference to the value, casted to T
998 return *static_cast< T* >(m_data);
999 }
1000
1007 template < typename T >
1008 T const& unwrap_unchecked() const
1009 {
1010 assert(valid());
1011 // static_assert(has_cvref_removed_identical_type<T>(), "Must be in type list");
1012 // Check if the variant is currently holding a value of type T
1013 assert(!(m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value));
1014 // If it is, return a reference to the value, casted to T
1015 return *static_cast< T const* >(m_data);
1016 }
1017
1024 template < typename T >
1025 T const& get_as() const
1026 {
1027 assert(valid());
1028 static_assert(has_cvref_removed_identical_type< T >(), "Must be in type list");
1029
1030 // Check if the variant is currently holding a value of type T
1031 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
1032 {
1033 // If it is not, throw an exception
1034 throw std::bad_variant_access();
1035 }
1036 assert(valid());
1037 // If it is, return a reference to the value, casted to T
1038 return *static_cast< T const* >(m_data);
1039 }
1040
1047 template < typename T >
1048 T const& as() const
1049 {
1050 assert(valid());
1051 static_assert(has_cvref_removed_identical_type< T >(), "Must be in type list");
1052
1053 // Check if the variant is currently holding a value of type T
1054 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
1055 {
1056 // If it is not, throw an exception
1057 throw std::bad_variant_access();
1058 }
1059 assert(valid());
1060 // If it is, return a reference to the value, casted to T
1061 return *static_cast< T const* >(m_data);
1062 }
1063
1070 template < typename T >
1071 T const& unwrap() const
1072 {
1073 assert(valid());
1074 static_assert(has_cvref_removed_identical_type< T >(), "Must be in type list");
1075
1076 // Check if the variant is currently holding a value of type T
1077 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
1078 {
1079 // If it is not, throw an exception
1080 throw std::bad_variant_access();
1081 }
1082 assert(valid());
1083 // If it is, return a reference to the value, casted to T
1084 return *static_cast< T const* >(m_data);
1085 }
1086
1093 template < std::size_t N >
1094 auto const& get_n() const
1095 {
1096 assert(valid());
1097
1098 // Check if the variant is currently holding a value of type T
1099 if (m_vinf == nullptr || m_vinf->m_index != N)
1100 {
1101 // If it is not, throw an exception
1102 throw std::bad_variant_access();
1103 }
1104
1105 // If it is, return a reference to the value, casted to T
1106 assert(valid());
1107 return *static_cast< typename std::tuple_element< N, std::tuple< Ts... > >::type const* >(m_data);
1108 }
1109
1116 template < std::size_t N >
1117 auto const& get_n_unchecked() const
1118 {
1119 assert(valid());
1120
1121 return *static_cast< typename std::tuple_element< N, std::tuple< Ts... > >::type const* >(m_data);
1122 }
1123
1131 {
1132 assert(valid());
1133 if (m_vinf == nullptr || other.m_vinf == nullptr)
1134 {
1135 throw std::bad_variant_access();
1136 }
1137 if (m_vinf->m_index != other.m_vinf->m_index)
1138 {
1139 return false;
1140 }
1141 assert(valid());
1142 return m_vinf->m_general_info.m_equals(m_data, other.m_data);
1143 }
1144
1152 {
1153 assert(valid());
1154 return !(*this == other);
1155 }
1156
1157 public:
1165 {
1166 assert(valid());
1167 if (m_vinf == nullptr || other.m_vinf == nullptr)
1168 {
1169 throw std::bad_variant_access();
1170 }
1171 if (m_vinf->m_index != other.m_vinf->m_index)
1172 {
1173 return m_vinf->m_index < other.m_vinf->m_index;
1174 }
1175 assert(valid());
1176 return m_vinf->m_general_info.m_less(m_data, other.m_data);
1177 }
1178
1179 public:
1186 std::strong_ordering operator<=>(basic_variant< Allocator, Ts... > const& other) const
1187 {
1188 assert(valid());
1189 if (m_vinf == nullptr || other.m_vinf == nullptr)
1190 {
1191 throw std::bad_variant_access();
1192 }
1193
1194 if (m_vinf->m_index != other.m_vinf->m_index)
1195 {
1196 return m_vinf->m_index <=> other.m_vinf->m_index;
1197 }
1198 return m_vinf->m_general_info.m_three_way(m_data, other.m_data);
1199 }
1200
1206 template < typename T >
1207 bool type_is() const
1208 {
1209 assert(valid());
1210 // Check if the variant is currently holding a value of type T
1211 return m_vinf != nullptr && m_vinf->m_index == index_of< T, Ts... >::value;
1212 }
1213
1219 template < typename... Ts2 >
1220 bool type_any_of() const
1221 {
1222 assert(valid());
1223 if (m_vinf == nullptr)
1224 {
1225 return false;
1226 }
1227 return (type_is< Ts2 >() || ...);
1228 }
1229
1238 template < typename R, dispatch_type D = dispatch_type::automatic, typename F >
1239 R apply_visitor(F&& func) &
1240 {
1241 return rpnx::apply_visitor< R, D >(*this, std::forward< F >(func));
1242 }
1243
1252 template < typename R, dispatch_type D = dispatch_type::automatic, typename F >
1253 R apply_visitor(F&& func) const&
1254 {
1255 return rpnx::apply_visitor< R, D >(*this, std::forward< F >(func));
1256 }
1257
1266 template < typename R, dispatch_type D = dispatch_type::automatic, typename F >
1267 R apply_visitor(F&& func) &&
1268 {
1269 return rpnx::apply_visitor< R, D >(std::move(*this), std::forward< F >(func));
1270 }
1271
1280 template < typename R, typename F >
1282 {
1283 return rpnx::apply_visitor_checked< R >(*this, std::forward< F >(func));
1284 }
1285
1294 template < typename R, typename F >
1295 R apply_visitor_checked(F&& func) const&
1296 {
1297 return rpnx::apply_visitor_checked< R >(*this, std::forward< F >(func));
1298 }
1299
1308 template < typename R, typename F >
1310 {
1311 return rpnx::apply_visitor_checked< R >(std::move(*this), std::forward< F >(func));
1312 }
1313
1314
1322 template < typename R, typename F >
1323 R try_apply_visitor(F&& func) &
1324 {
1325 return rpnx::try_apply_visitor< R >(*this, std::forward< F >(func));
1326 }
1327
1335 template < typename R, typename F >
1336 R try_apply_visitor(F&& func) const&
1337 {
1338 return rpnx::try_apply_visitor< R >(*this, std::forward< F >(func));
1339 }
1340
1348 template < typename R, typename F >
1349 R try_apply_visitor(F&& func) &&
1350 {
1351 return rpnx::try_apply_visitor< R >(std::move(*this), std::forward< F >(func));
1352 }
1353
1360 template < typename T, typename F >
1361 bool match(F&& func)
1362 {
1363 if (type_is< T >())
1364 {
1365 func(get_as< T >());
1366 return true;
1367 }
1368
1369 return false;
1370 }
1371
1378 template < typename T, typename F >
1379 bool test(F&& func)
1380 {
1381 if (type_is< T >())
1382 {
1383 return func(get_as< T >());
1384 }
1385 return false;
1386 }
1387
1395 template < typename T, typename F >
1396 bool match(F&& func) const
1397 {
1398 if (type_is< T >())
1399 {
1400 func(get_as< T >());
1401 return true;
1402 }
1403
1404 return false;
1405 }
1406
1414 template < typename T, typename F >
1415 bool test(F&& func) const
1416 {
1417 if (type_is< T >())
1418 {
1419 return func(get_as< T >());
1420 }
1421
1422 return false;
1423 }
1424
1430 template < typename T >
1432 {
1433 assert(valid());
1434 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
1435 {
1436 return nullptr;
1437 }
1438 return static_cast< T* >(m_data);
1439 }
1440
1446 template < typename T >
1447 T const* cast_ptr() const
1448 {
1449 assert(valid());
1450 if (m_vinf == nullptr || m_vinf->m_index != index_of< T, Ts... >::value)
1451 {
1452 return nullptr;
1453 }
1454 return static_cast< T const* >(m_data);
1455 }
1456
1462 std::type_info const& type() const
1463 {
1464 assert(valid());
1465 if (m_vinf == nullptr)
1466 {
1467 throw std::bad_variant_access();
1468 }
1469 return *m_vinf->m_general_info.m_type_info;
1470 }
1471
1477 std::type_index type_index() const
1478 {
1479 assert(valid());
1480 return std::type_index(type());
1481 }
1482
1489 template < std::size_t N >
1490 auto& get_n()
1491 {
1492 assert(valid());
1493 if (m_vinf == nullptr || m_vinf->m_index != N)
1494 {
1495 throw std::bad_variant_access();
1496 }
1497
1498 return *static_cast< std::tuple_element_t< N, std::tuple< Ts... > >* >(m_data);
1499 }
1500
1507 template < std::size_t N >
1509 {
1510 assert(valid());
1511
1512 return *static_cast< std::tuple_element_t< N, std::tuple< Ts... > >* >(m_data);
1513 }
1514
1520 std::size_t index() const
1521 {
1522 assert(valid());
1523 if (m_vinf == nullptr) [[unlikely]]
1524 {
1525 throw std::bad_variant_access();
1526 }
1527 return m_vinf->m_index;
1528 }
1529
1530 private:
1537 template < typename T, std::size_t N >
1538 static consteval std::size_t cvref_removed_identical_index()
1539 {
1540 if constexpr (N >= sizeof...(Ts))
1541 {
1542 return N;
1543 }
1544 else
1545 {
1546 if constexpr (std::is_same_v< std::remove_cvref_t< T >, std::tuple_element_t< N, std::tuple< Ts... > > >)
1547 {
1548 return N;
1549 }
1550 else
1551 {
1552 return cvref_removed_identical_index< T, N + 1 >();
1553 }
1554 }
1555 }
1556
1562 template < typename T >
1563 static constexpr std::size_t constructor_index()
1564 {
1565 static_assert(can_construct_subtype_with< T >());
1566
1567 // If T is the same as any of the types in Ts..., return the index of the first match assuming it is convertible
1568 // otherwise, return the index of the first type in Ts... that T is convertible to
1569
1570 if constexpr (has_cvref_removed_identical_type< T >())
1571 {
1572 constexpr auto exact_index = cvref_removed_identical_index< T, 0 >();
1573 if constexpr (std::is_convertible_v< T, std::tuple_element_t< exact_index, std::tuple< Ts... > > >)
1574 {
1575 return exact_index;
1576 }
1577 else
1578 {
1579 return convertible_index< T, 0 >();
1580 }
1581 }
1582 else
1583 {
1584 return convertible_index< T, 0 >();
1585 }
1586 }
1587
1594 template < typename T, std::size_t N >
1595 static constexpr std::size_t convertible_index()
1596 {
1597 static_assert(can_construct_subtype_with< T >());
1598 if constexpr (std::is_convertible_v< T, std::tuple_element_t< N, std::tuple< Ts... > > >)
1599 {
1600 return N;
1601 }
1602 else
1603 {
1604 return convertible_index< T, N + 1 >();
1605 }
1606 }
1607 };
1608
1609} // namespace rpnx
1610
1611//#include <rpnx/demangle.hpp>
1612
1613#endif // QUXLANG_VARIANT_HPP
Heap-backed tagged union with allocator-aware storage.
Definition variant.hpp:524
auto & get_n()
Retrieves the held value by alternative index with runtime checking.
Definition variant.hpp:1490
T & unwrap_unchecked()
Retrieves the held value as T& without runtime checks.
Definition variant.hpp:991
R apply_visitor_checked(F &&func) &
Applies a visitor and throws if not invocable for the active alternative.
Definition variant.hpp:1281
T const & static_cast_as() const
Returns the held value cast to T const& via visitor dispatch.
Definition variant.hpp:908
bool type_is() const
Checks whether the active alternative is exactly T.
Definition variant.hpp:1207
void reset()
Resets the variant to the valueless state.
Definition variant.hpp:693
R apply_visitor(F &&func) &
Applies a visitor to the active alternative.
Definition variant.hpp:1239
bool match(F &&func) const
Const overload of match().
Definition variant.hpp:1396
R try_apply_visitor(F &&func) const &
Const lvalue overload of try_apply_visitor().
Definition variant.hpp:1336
std::type_info const & type() const
Returns RTTI for the active alternative.
Definition variant.hpp:1462
bool test(F &&func) const
Const overload of test().
Definition variant.hpp:1415
bool test(F &&func)
Invokes func with the held value if it is of type T and returns predicate result.
Definition variant.hpp:1379
bool type_any_of() const
Checks whether the active alternative is any of Ts2.
Definition variant.hpp:1220
auto const & get_n_unchecked() const
Retrieves the held value by alternative index without runtime checking.
Definition variant.hpp:1117
static consteval bool can_construct_subtype_with()
Indicates whether this variant can be constructed from T.
Definition variant.hpp:728
T & get_as()
Retrieves the held value as T& with runtime type checking.
Definition variant.hpp:928
basic_variant(basic_variant< Allocator, Ts2... > const &other, std::enable_if_t< !std::is_same_v< basic_variant< Allocator, Ts... >, basic_variant< Allocator, Ts2... > > &&!has_cvref_removed_identical_type< basic_variant< Allocator, Ts2... > >(), int >=0)
Converting copy constructor from another compatible variant type.
Definition variant.hpp:713
std::strong_ordering operator<=>(basic_variant< Allocator, Ts... > const &other) const
Three-way comparison.
Definition variant.hpp:1186
T const & get_as() const
Retrieves the held value as T const& with runtime type checking.
Definition variant.hpp:1025
T const * cast_ptr() const
Const overload of cast_ptr().
Definition variant.hpp:1447
bool operator==(basic_variant< Allocator, Ts... > const &other) const
Equality comparison.
Definition variant.hpp:1130
std::allocator< void > allocator_type
Definition variant.hpp:607
R apply_visitor(F &&func) const &
Const lvalue overload of apply_visitor().
Definition variant.hpp:1253
R apply_visitor_checked(F &&func) const &
Const lvalue overload of apply_visitor_checked().
Definition variant.hpp:1295
auto const & get_n() const
Retrieves the held value by alternative index with runtime checking.
Definition variant.hpp:1094
R try_apply_visitor(F &&func) &&
Rvalue overload of try_apply_visitor().
Definition variant.hpp:1349
R apply_visitor_checked(F &&func) &&
Rvalue overload of apply_visitor_checked().
Definition variant.hpp:1309
bool operator!=(basic_variant< Allocator, Ts... > const &other) const
Inequality comparison.
Definition variant.hpp:1151
constexpr basic_variant(T2 &&value, const allocator_type &alloc=allocator_type(), std::enable_if_t< rpnx::basic_variant< Allocator, Ts... >::can_construct_subtype_with< T2 >(), int >=0)
Constructs the variant from a value convertible to one of the alternatives.
Definition variant.hpp:764
std::size_t index() const
Definition variant.hpp:1520
T const & as() const
Const alias of get_as().
Definition variant.hpp:1048
bool operator<(basic_variant< Allocator, Ts... > const &other) const
Strict-weak ordering comparison.
Definition variant.hpp:1164
T const & unwrap() const
Const alias of get_as().
Definition variant.hpp:1071
bool match(F &&func)
Invokes func with the held value if it is of type T.
Definition variant.hpp:1361
constexpr basic_variant(const allocator_type &alloc=allocator_type())
Default-constructs the first alternative.
Definition variant.hpp:614
R try_apply_visitor(F &&func) &
Applies a visitor and returns default R{} when not invocable for the active type.
Definition variant.hpp:1323
auto & get_n_unchecked()
Retrieves the held value by alternative index without runtime checking.
Definition variant.hpp:1508
basic_variant< Allocator, Ts... > & operator=(basic_variant< Allocator, Ts... > other)
Copy-assigns by value using swap semantics.
Definition variant.hpp:862
T & unwrap()
Alias of get_as().
Definition variant.hpp:970
T & static_cast_as()
Returns the held value cast to T& via visitor dispatch.
Definition variant.hpp:885
constexpr basic_variant< Allocator, Ts... > & operator=(T &&value)
Assigns from a value convertible to one of the alternatives.
Definition variant.hpp:805
std::type_index type_index() const
Returns std::type_index for the active alternative.
Definition variant.hpp:1477
~basic_variant()
Destroys the currently-held value if present.
Definition variant.hpp:683
T * cast_ptr()
Returns pointer to held value if active type is T.
Definition variant.hpp:1431
constexpr basic_variant(basic_variant< Allocator, Ts... > &&other) noexcept(std::is_nothrow_move_constructible_v< Allocator >)
Move-constructs from another variant of the same type.
Definition variant.hpp:635
R apply_visitor(F &&func) &&
Rvalue overload of apply_visitor().
Definition variant.hpp:1267
T const & unwrap_unchecked() const
Retrieves the held value as T const& without runtime checks.
Definition variant.hpp:1008
constexpr basic_variant(basic_variant< Allocator, Ts... > const &other)
Copy-constructs from another variant of the same type.
Definition variant.hpp:655
T & as()
Alias of get_as().
Definition variant.hpp:949
A generic result class that can hold either a value or an exception.
Definition result.hpp:21
Visitor that assigns its argument into a target variant.
Definition variant.hpp:496
variant_convert_to(basic_variant< A, Ts... > &val)
Binds the assignment target.
Definition variant.hpp:501
bool operator()(T2 &&other) const
Assigns a visited value into the target.
Definition variant.hpp:506
Allocator-aware erased operations used by basic_variant.
Definition variant.hpp:50
std::strong_ordering(*)(void const *, void const *) three_way_func
Strong three-way comparison operation for one alternative type.
Definition variant.hpp:73
void(*)(Allocator &, void *) noexcept delete_func
Destroys and deallocates one erased alternative.
Definition variant.hpp:67
bool(*)(void const *, void const *) less_func
Less-than comparison operation for one alternative type.
Definition variant.hpp:69
static constexpr variant_info make_variant_info()
Builds the erased operation table for an alternative.
Definition variant.hpp:106
void *(*)(Allocator &, void const *) copy_func
Allocates and copy-constructs one erased alternative.
Definition variant.hpp:65
bool(*)(void const *, void const *) equals_func
Equality comparison operation for one alternative type.
Definition variant.hpp:71
void *(*)(Allocator &) default_new_func
Allocates and default-constructs one erased alternative.
Definition variant.hpp:63
void *(*)(Allocator &, void *) new_move_from_func
Allocates an alternative by moving from erased storage.
Definition variant.hpp:75
std::tuple_element_t< N, std::tuple< Vs... > > type
Alternative type at index N.
Definition variant.hpp:379
Obtains an alternative type by index.
Definition variant.hpp:371
static constexpr std::size_t value
Number of alternative types.
Definition variant.hpp:394
std::integral_constant< std::size_t, sizeof...(Vs) > type
Integral-constant type containing the alternative count.
Definition variant.hpp:392
Obtains the number of alternatives in a variant type.
Definition variant.hpp:384
Containers, iterator adapters, callable wrappers, and value utilities.
Definition annex.hpp:14
R apply_nth_visitor_branched(std::size_t index, V &&variant, F &&func)
Dispatches an alternative index through a compile-time binary branch tree.
Definition variant.hpp:272
basic_variant< std::allocator< void >, Ts... > variant
Convenience alias for a variant using std::allocator<void>.
Definition variant.hpp:244
call_type
Policy used when a visitor cannot accept an active alternative.
Definition variant.hpp:261
@ required
Definition variant.hpp:261
@ except_on_missing
Definition variant.hpp:261
@ optional
Definition variant.hpp:261
constexpr auto & get_as(variant< Ts... > &v)
Retrieves a mutable alternative by type.
Definition variant.hpp:248
auto consteval variant_invoke_table_gen()
Generates the legacy lvalue visitor dispatch table.
Definition variant.hpp:359
R(*)(V &&, F &&) variant_invoke_executor
Function-pointer type for one erased visitor dispatch entry.
Definition variant.hpp:355
dispatch_type
Strategy used to dispatch a visitor to an active alternative.
Definition variant.hpp:264
@ automatic
Definition variant.hpp:264
@ branching
Definition variant.hpp:264
@ indirect
Definition variant.hpp:264
R try_apply_visitor(V &&variant, F &&func)
Invokes a visitor only when it accepts the active alternative.
Definition variant.hpp:484
R apply_visitor_checked(V &&variant, F &&func)
Invokes a visitor and throws when it does not accept the active alternative.
Definition variant.hpp:472
R apply_nth_visitor(V &&variant, F &&func)
Invokes a visitor for one compile-time alternative index.
Definition variant.hpp:299
typename variant_nth_member< V, N >::type variant_nth_member_t
Alternative type at an index.
Definition variant.hpp:403
R apply_visitor(V &&variant, F &&func)
Invokes a visitor for the active alternative.
Definition variant.hpp:450
auto constexpr variant_invoke_table_gen2()
Generates a visitor dispatch table.
Definition variant.hpp:411
constexpr auto variant_invoke_table2
Shared compile-time visitor dispatch table.
Definition variant.hpp:437
constexpr void update_variant_invoke_table2(std::array< variant_invoke_executor< V, F, R >, variant_size_v< std::remove_cvref_t< V > > > &table)
Populates visitor dispatch-table entries recursively.
Definition variant.hpp:423
Definition variant.hpp:21
Erased operation table for a single variant alternative.
Definition variant.hpp:79
default_new_func m_default_new
Default-allocation operation.
Definition variant.hpp:91
three_way_func m_three_way
Three-way comparison operation.
Definition variant.hpp:87
copy_func m_copy
Copy-allocation operation.
Definition variant.hpp:81
new_move_from_func m_new_move_from
Move-allocation operation.
Definition variant.hpp:93
delete_func m_destroy
Destruction and deallocation operation.
Definition variant.hpp:89
equals_func m_equals
Equality operation.
Definition variant.hpp:85
std::type_info const * m_type_info
Run-time type information for the alternative.
Definition variant.hpp:95
less_func m_less
Less-than operation.
Definition variant.hpp:83