├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmarks └── benchmark.cpp ├── include └── swl │ ├── variant.hpp │ ├── variant_detail.hpp │ └── variant_visit.hpp └── tests ├── archetypes.h ├── archetypes.ipp ├── poisoned_hash_helper.h ├── swl_assert.hpp ├── test_convertible.h ├── test_macros.h ├── test_workarounds.h ├── tests ├── throwing_conversion.pass.cpp ├── variant.bad_variant_access │ └── bad_variant_access.pass.cpp ├── variant.get │ ├── get_if_index.pass.cpp │ ├── get_if_type.pass.cpp │ ├── get_index.pass.cpp │ ├── get_type.pass.cpp │ └── holds_alternative.pass.cpp ├── variant.hash │ ├── enabled_hash.pass.cpp.ignore │ └── hash.pass.cpp ├── variant.helpers │ ├── variant_alternative.fail.cpp │ ├── variant_alternative.pass.cpp │ └── variant_size.pass.cpp ├── variant.monostate.relops │ └── relops.pass.cpp ├── variant.monostate │ └── monostate.pass.cpp ├── variant.relops │ ├── relops.pass.cpp │ └── relops_bool_conv.fail.cpp ├── variant.variant │ ├── variant.assign │ │ ├── T.pass.cpp │ │ ├── conv.pass.cpp │ │ ├── copy.fail.cpp │ │ ├── copy.pass.cpp │ │ └── move.pass.cpp │ ├── variant.ctor │ │ ├── T.pass.cpp │ │ ├── conv.pass.cpp │ │ ├── copy.pass.cpp │ │ ├── default.pass.cpp │ │ ├── in_place_index_args.pass.cpp │ │ ├── in_place_index_init_list_args.pass.cpp │ │ ├── in_place_type_args.pass.cpp │ │ ├── in_place_type_init_list_args.pass.cpp │ │ └── move.pass.cpp │ ├── variant.dtor │ │ └── dtor.pass.cpp │ ├── variant.mod │ │ ├── emplace_index_args.pass.cpp │ │ ├── emplace_index_init_list_args.pass.cpp │ │ ├── emplace_type_args.pass.cpp │ │ └── emplace_type_init_list_args.pass.cpp │ ├── variant.status │ │ ├── index.pass.cpp │ │ └── valueless_by_exception.pass.cpp │ ├── variant.swap │ │ └── swap.pass.cpp │ ├── variant_array.fail.cpp │ ├── variant_empty.fail.cpp │ ├── variant_reference.fail.cpp │ └── variant_void.fail.cpp └── variant.visit │ ├── robust_against_adl.pass.cpp │ ├── visit.pass.cpp │ └── visit_return_type.pass.cpp ├── type_id.h └── variant_test_helpers.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(swl.variant 4 | VERSION 1.0.0 5 | LANGUAGES CXX 6 | HOMEPAGE_URL https://github.com/groundswellaudio/swl-variant) 7 | 8 | add_library(swl-variant INTERFACE) 9 | 10 | target_include_directories(swl-variant INTERFACE 11 | $ 12 | $) 13 | 14 | target_compile_features(swl-variant INTERFACE cxx_std_20) 15 | 16 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 17 | 18 | include(CTest) 19 | 20 | ## tests that are supposed to not compile 21 | ## https://stackoverflow.com/questions/30155619/expected-build-failure-tests-in-cmake 22 | 23 | math(EXPR test_idx 0) 24 | 25 | file(GLOB_RECURSE test-sources CONFIGURE_DEPENDS tests/*.fail.cpp) 26 | foreach (source IN LISTS test-sources) 27 | get_filename_component(name "${source}" NAME_WE) 28 | 29 | set(test "${PROJECT_NAME}-test-${name}-${test_idx}") 30 | MATH(EXPR test_idx "${test_idx} + 1") 31 | 32 | add_executable(${test} "${source}") 33 | 34 | ## avoid actually building the target 35 | set_target_properties(${test} PROPERTIES 36 | EXCLUDE_FROM_ALL TRUE 37 | EXCLUDE_FROM_DEFAULT_BUILD TRUE) 38 | 39 | target_link_libraries(${test} swl-variant) 40 | target_include_directories(${test} PRIVATE ./tests) 41 | 42 | add_test(NAME ${test} 43 | COMMAND ${CMAKE_COMMAND} --build ./ --target ${test} --config $ 44 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) 45 | 46 | set_tests_properties(${test} PROPERTIES WILL_FAIL TRUE) 47 | 48 | endforeach() 49 | 50 | file(GLOB_RECURSE test-sources CONFIGURE_DEPENDS tests/*.pass.cpp) 51 | 52 | foreach (source IN LISTS test-sources) 53 | get_filename_component(name "${source}" NAME_WE) 54 | 55 | set(test "${PROJECT_NAME}-test-${name}-${test_idx}") 56 | MATH(EXPR test_idx "${test_idx} + 1") 57 | 58 | add_executable(${test} "${source}") 59 | target_link_libraries(${test} swl-variant) 60 | target_include_directories(${test} PRIVATE ./tests) 61 | add_test(NAME ${test} COMMAND ${test}) 62 | endforeach() 63 | 64 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jean-Baptiste Vallon Hoarau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swl::variant 2 | 3 | A minimal compile-time overhead, C++20 implementation of std::variant. Fully standard conforming with a couple of documented differences. 4 | 5 | ## Compile-time performance 6 | 7 | Because `std::variant` is implemented in both GCC and Clang libraries using a simple recursive union, accessing each members result in approximately N^2 functions template instantiations for a variant of size N. This implementation instead use a "binary-tree of unions", resulting in N.log2(N) instantiations, which results in faster compile times (see measurements below). 8 | 9 | ## Run-time performance and binary size 10 | 11 | `std::variant` visit method is usually implemented using a table of functions pointers. Unfortunately, compilers cannot (yet?) "see through" those, and the generated code tends to be much larger and slower than a switch-case equivalent - [more on this here](https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/). Similarly to [Michael Park's implementation](https://github.com/mpark/variant), this implementation use a big, recursive switch for visitation. 12 | 13 | ## Testing 14 | 15 | The tests are from the LLVM test suite. 16 | To run them do : 17 | `mkdir ./test_out && cd ./test_out` 18 | `cmake ../` 19 | `ctest --build-and-test ../ ./ --build-generator "Unix Makefiles"` (replace "Unix Makefiles" as needed) 20 | `make test` 21 | 22 | ## Implementation divergence 23 | 24 | * `index()` doesn't return a `std::size_t`, but an integer whose size depends on the numbers of type inside the variant. Basically either `unsigned char` or `unsigned short`. 25 | * a `visit_with_index` function is furnished, which is useful for example when you want 26 | to apply a function to multiple variant whose index is known to be the same : 27 | ```cpp 28 | visit_with_index(variant1, [&variant2] (auto& elem, auto cst) { foo(elem, get(variant2)); }); 29 | ``` 30 | 31 | ## Extensions and customization 32 | 33 | * If you like to live dangerously, `swl::unsafe_get` behave just like get, but without any errors checking. 34 | 35 | * Two macro based knobs are available : 36 | - `SWL_VARIANT_NO_STD_HASH` : this disable the `std::hash` specializations and avoid the `#include `, which is big 37 | - `SWL_VARIANT_NO_CONSTEXPR_EMPLACE` : this disable `constexpr` for emplace, and avoid the `#include `, which is even bigger. Note that this one is an ODR footgun : don't use it if you can't guarantee that it's enabled everywhere in your binaries. 38 | 39 | To use these macros, define them in a file named `swl_variant_knobs.hpp`, and put it either in the same directory as `variant.hpp` or at the root of a header search path. 40 | 41 | Both of these are provided to reduce compile times, whether or not this matter depends on your compiler : on my version of Clang, activating both of these macros result in a mere -0.5s, on GCC however, this reduce compile times by more than 4s. 42 | 43 | ## Measurements 44 | 45 | The measurements are of the form (compile time, executable file size). 46 | 47 | All of these measurements were done without optimizations. 48 | 49 | The compilers used were Clang 12 and GCC 10. 50 | 51 | Single visitation : 52 | | Variant size | swl (clang) | std (clang) | swl (gcc) | std (gcc) 53 | |--|--|--|--|--| 54 | | 20 | 1s, 50 Ko | 1.2s, 80 Ko | 4.6s, 50 Ko | 1s, 133 Ko | 55 | | 40 | 1.2s, 120 Ko | 2s, 260 Ko | 4.8s, 120 Ko | 2s, 440 Ko | 56 | | 80 | 1.4s, 300 Ko | 4.6s, 1 Mo | 5.3s, 290 Ko | 5.7s, 1.8 Mo | 57 | | 160 | 1.8s, 700 Ko | 15s, 4.3 Mo | 6s, 720 Ko | 21s, 8.2 Mo | 58 | | 320 | 3s, 1.7 Mo | 54s, 22 Mo | 8.4s, 1.8 Mo | 90s, 40 Mo | 59 | | 640 | 5s, 4 Mo | 250s, 130 Mo | 17s, 4.4 Mo | 415s, 250 Mo | 60 | 61 | Multi visitation of some variants of size 10 : 62 | 63 | | Numbers of variants | swl (clang) | std (clang) | swl (gcc) | std (gcc) 64 | |--|--|--|--|--| 65 | | 2 | 1.1s, 49 Ko | 1.6s, 128 Ko | 2.8s, 41 Ko | 1.3s, 160 Ko | 66 | | 3 | 2s, 142 Ko | 8s, 1.1 Mo | 3.8s, 123 Ko | 9s, 1.5 Mo | 67 | | 4 | 6.7s, 630 Ko | 68s, 11 Mo | 10.5s, 560 Ko | 95s, 17 Mo | 68 | 69 | ### Tested compilers 70 | 71 | * GCC 10 72 | 73 | Clang 12/13 will only work for trivially destructible types as their implementation of C++20 is incomplete. 74 | -------------------------------------------------------------------------------- /benchmarks/benchmark.cpp: -------------------------------------------------------------------------------- 1 | 2 | //#define TEST_STD 3 | 4 | #ifdef TEST_STD 5 | #include 6 | #define swl std 7 | #else 8 | #include 9 | #endif 10 | 11 | #define PACK int, float, char, bool, double 12 | #define PACK10 PACK, PACK 13 | #define PACK20 PACK10, PACK10 14 | #define PACK40 PACK20, PACK20 15 | #define PACK80 PACK40, PACK40 16 | #define PACK160 PACK80, PACK80 17 | #define PACK320 PACK160, PACK160 18 | #define PACK640 PACK320, PACK320 19 | 20 | //#define TEST_MULTI_VISIT 21 | 22 | int main(){ 23 | 24 | swl::variant a, b, c, d; 25 | 26 | #ifndef TEST_MULTI_VISIT 27 | 28 | swl::visit( [] (auto x) {}, a ); 29 | 30 | #else 31 | 32 | swl::visit( [] (auto... args) {}, a, b, c ); 33 | 34 | #endif 35 | } -------------------------------------------------------------------------------- /include/swl/variant_detail.hpp: -------------------------------------------------------------------------------- 1 | #ifdef SWL_CPP_LIBRARY_VARIANT_HPP 2 | 3 | template 4 | constexpr int find_first_true(bool (&&arr)[N]){ 5 | for (int k = 0; k < N; ++k) 6 | if (arr[k]) 7 | return k; 8 | return -1; 9 | } 10 | 11 | template 12 | inline constexpr bool appears_exactly_once = (static_cast(std::is_same_v) + ...) == 1; 13 | 14 | // ============= type pack element 15 | 16 | #if __has_builtin(__type_pack_element) 17 | 18 | template 19 | using type_pack_element = __type_pack_element; 20 | 21 | #else 22 | 23 | template 24 | struct find_type_i; 25 | 26 | template <> 27 | struct find_type_i<1> { 28 | template 29 | using f = typename find_type_i<(Idx != 1)>::template f; 30 | }; 31 | 32 | template <> 33 | struct find_type_i<0> { 34 | template 35 | using f = T; 36 | }; 37 | 38 | template 39 | using type_pack_element = typename find_type_i<(K != 0)>::template f; 40 | 41 | #endif 42 | 43 | // ============= overload match detector. to be used for variant generic assignment 44 | 45 | template 46 | using arr1 = T[1]; 47 | 48 | template 49 | struct overload_frag { 50 | using type = A; 51 | template 52 | requires requires { arr1{std::declval()}; } 53 | auto operator()(A, T&&) -> overload_frag; 54 | }; 55 | 56 | template 57 | struct make_overload; 58 | 59 | template 60 | struct make_overload, Args...> 61 | : overload_frag... { 62 | using overload_frag::operator()...; 63 | }; 64 | 65 | template 66 | using best_overload_match = typename decltype( 67 | make_overload, Ts...>{} 68 | ( std::declval(), std::declval() ) 69 | )::type; 70 | 71 | template 72 | concept has_non_ambiguous_match = 73 | requires { typename best_overload_match; }; 74 | 75 | // ================================== rel ops 76 | 77 | template 78 | concept convertible = std::is_convertible_v; 79 | 80 | template 81 | concept has_eq_comp = requires (T a, T b) { 82 | { a == b } -> convertible; 83 | }; 84 | 85 | template 86 | concept has_lesser_comp = requires (T a, T b) { 87 | { a < b } -> convertible; 88 | }; 89 | 90 | template 91 | concept has_less_or_eq_comp = requires (T a, T b) { 92 | { a <= b } -> convertible; 93 | }; 94 | 95 | template 96 | struct emplace_no_dtor_from_elem { 97 | template 98 | constexpr void operator()(T&& elem, auto index_) const { 99 | a.template emplace_no_dtor( static_cast(elem) ); 100 | } 101 | A& a; 102 | }; 103 | 104 | template 105 | constexpr void destruct(T& obj){ 106 | if constexpr (not std::is_trivially_destructible_v) 107 | obj.~E(); 108 | } 109 | 110 | // =============================== variant union types 111 | 112 | // =================== base variant storage type 113 | // this type is used to build a N-ary tree of union. 114 | 115 | struct dummy_type{ static constexpr int elem_size = 0; }; // used to fill the back of union nodes 116 | 117 | using union_index_t = unsigned; 118 | 119 | #define TRAIT(trait) ( std::is_##trait##_v && std::is_##trait##_v ) 120 | 121 | #define SFM(signature, trait) \ 122 | signature = default; \ 123 | signature requires (TRAIT(trait) and not TRAIT(trivially_##trait)) {} 124 | 125 | // given the two members of type A and B of an union X 126 | // this create the proper conditionally trivial special members functions 127 | #define INJECT_UNION_SFM(X) \ 128 | SFM(constexpr X (const X &), copy_constructible) \ 129 | SFM(constexpr X (X&&), move_constructible) \ 130 | SFM(constexpr X& operator=(const X&), copy_assignable) \ 131 | SFM(constexpr X& operator=(X&&), move_assignable) \ 132 | SFM(constexpr ~X(), destructible) 133 | 134 | template 135 | struct node_trait; 136 | 137 | template <> 138 | struct node_trait { 139 | 140 | template 141 | static constexpr auto elem_size = not( std::is_same_v ) ? 2 : 1; 142 | 143 | template 144 | static constexpr char ctor_branch = 0; 145 | }; 146 | 147 | template <> 148 | struct node_trait { 149 | template 150 | static constexpr auto elem_size = A::elem_size + B::elem_size; 151 | 152 | template 153 | static constexpr char ctor_branch = (Index < A::elem_size) ? 1 : 2; 154 | }; 155 | 156 | template 157 | struct union_node { 158 | 159 | union { 160 | A a; 161 | B b; 162 | }; 163 | 164 | static constexpr auto elem_size = node_trait::template elem_size; 165 | 166 | constexpr union_node() = default; 167 | 168 | template 169 | requires (node_trait::template ctor_branch == 1) 170 | constexpr union_node(in_place_index_t, Args&&... args) 171 | : a{ in_place_index, static_cast(args)... } 172 | {} 173 | 174 | template 175 | requires (node_trait::template ctor_branch == 2) 176 | constexpr union_node(in_place_index_t, Args&&... args) 177 | : b{ in_place_index, static_cast(args)... } 178 | {} 179 | 180 | template 181 | requires (IsLeaf) 182 | constexpr union_node(in_place_index_t<0>, Args&&... args) 183 | : a{static_cast(args)...} 184 | {} 185 | 186 | template 187 | requires (IsLeaf) 188 | constexpr union_node(in_place_index_t<1>, Args&&... args) 189 | : b{static_cast(args)...} 190 | {} 191 | 192 | constexpr union_node(dummy_type) 193 | requires (std::is_same_v) 194 | : b{} 195 | {} 196 | 197 | template 198 | constexpr auto& get() 199 | { 200 | if constexpr (IsLeaf) 201 | { 202 | if constexpr ( Index == 0 ) 203 | return a; 204 | else 205 | return b; 206 | } 207 | else 208 | { 209 | if constexpr ( Index < A::elem_size ) 210 | return a.template get(); 211 | else 212 | return b.template get(); 213 | } 214 | } 215 | 216 | INJECT_UNION_SFM(union_node) 217 | }; 218 | 219 | #undef INJECT_UNION_SFM 220 | #undef SFM 221 | #undef TRAIT 222 | 223 | // =================== algorithm to build the tree of unions 224 | // take a sequence of types and perform an order preserving fold until only one type is left 225 | // the first parameter is the numbers of types remaining for the current pass 226 | 227 | constexpr unsigned char pick_next(unsigned remaining){ 228 | return remaining >= 2 ? 2 : remaining; 229 | } 230 | 231 | template 232 | struct make_tree; 233 | 234 | template 235 | struct make_tree<2, 1, IsFirstPass> { 236 | template 237 | using f = typename make_tree 238 | < 239 | pick_next(Remaining - 2), 240 | sizeof...(Ts) != 0, 241 | IsFirstPass 242 | > 243 | ::template f 244 | < 245 | Remaining - 2, 246 | Ts..., 247 | union_node 248 | >; 249 | }; 250 | 251 | // only one type left, stop 252 | template 253 | struct make_tree<0, 0, F> { 254 | template 255 | using f = A; 256 | }; 257 | 258 | // end of one pass, restart 259 | template 260 | struct make_tree<0, 1, IsFirstPass> { 261 | template 262 | using f = typename make_tree 263 | < 264 | pick_next(sizeof...(Ts)), 265 | (sizeof...(Ts) != 1), 266 | false // <- both first pass and tail call recurse into a tail call 267 | > 268 | ::template f; 269 | }; 270 | 271 | // one odd type left in the pass, put it at the back to preserve the order 272 | template <> 273 | struct make_tree<1, 1, false> { 274 | template 275 | using f = typename make_tree<0, sizeof...(Ts) != 0, false> 276 | ::template f<0, Ts..., A>; 277 | }; 278 | 279 | // one odd type left in the first pass, wrap it in an union 280 | template <> 281 | struct make_tree<1, 1, true> { 282 | template 283 | using f = typename make_tree<0, sizeof...(Ts) != 0, false> 284 | ::template f<0, Ts..., union_node>; 285 | }; 286 | 287 | template 288 | using make_tree_union = typename 289 | make_tree::template f; 290 | 291 | // ============================================================ 292 | 293 | // Ts... must be sorted in ascending size 294 | template 295 | using smallest_suitable_integer_type = 296 | type_pack_element<(static_cast(Num > std::numeric_limits::max()) + ...), 297 | Ts... 298 | >; 299 | 300 | // why do we need this again? i think something to do with GCC? 301 | namespace swap_trait { 302 | using std::swap; 303 | 304 | template 305 | concept able = requires (A a, A b) { swap(a, b); }; 306 | 307 | template 308 | inline constexpr bool nothrow = noexcept( swap(std::declval(), std::declval()) ); 309 | } 310 | 311 | #ifndef SWL_VARIANT_NO_STD_HASH 312 | template 313 | inline constexpr bool has_std_hash = requires (T t) { 314 | std::size_t( ::std::hash< std::remove_cvref_t >{}(t) ); 315 | }; 316 | #endif 317 | 318 | template 319 | inline constexpr T* addressof( T& obj ) noexcept { 320 | #if defined(__GNUC__) || defined( __clang__ ) 321 | return __builtin_addressof(obj); 322 | #elif defined (SWL_VARIANT_NO_CONSTEXPR_EMPLACE) 323 | // if & is overloaded, use the ugly version 324 | if constexpr ( requires { obj.operator&(); } ) 325 | return reinterpret_cast 326 | (&const_cast(reinterpret_cast(obj))); 327 | else 328 | return &obj; 329 | #else 330 | return std::address_of(obj); 331 | #endif 332 | } 333 | 334 | #endif // eof 335 | 336 | -------------------------------------------------------------------------------- /include/swl/variant_visit.hpp: -------------------------------------------------------------------------------- 1 | #ifdef SWL_CPP_LIBRARY_VARIANT_HPP 2 | 3 | // ========================= visit dispatcher 4 | 5 | template 6 | using rtype_visit = decltype( ( std::declval()( std::declval().template unsafe_get<0>()... ) ) ); 7 | 8 | template 9 | using rtype_index_visit = decltype( ( std::declval()( std::declval().template unsafe_get<0>(), 10 | std::integral_constant{} ) ) 11 | ); 12 | 13 | inline namespace v1 { 14 | 15 | #if defined(__GNUC__) || defined( __clang__ ) || defined( __INTEL_COMPILER ) 16 | #define DeclareUnreachable() __builtin_unreachable() 17 | #elif defined (_MSC_VER) 18 | #define DeclareUnreachable() __assume(false) 19 | #else 20 | #error "Compiler not supported, please file an issue." 21 | #endif 22 | 23 | #define DEC(N) X((N)) X((N) + 1) X((N) + 2) X((N) + 3) X((N) + 4) X((N) + 5) X((N) + 6) X((N) + 7) X((N) + 8) X((N) + 9) 24 | 25 | #define SEQ30(N) DEC( (N) + 0 ) DEC( (N) + 10 ) DEC( (N) + 20 ) 26 | #define SEQ100(N) SEQ30((N) + 0) SEQ30((N) + 30) SEQ30((N) + 60) DEC((N) + 90) 27 | #define SEQ200(N) SEQ100((N) + 0) SEQ100((N) + 100) 28 | #define SEQ400(N) SEQ200((N) + 0) SEQ200((N) + 200) 29 | #define CAT(M, N) M##N 30 | #define CAT2(M, N) CAT(M, N) 31 | #define INJECTSEQ(N) CAT2(SEQ, N)(0) 32 | 33 | // single-visitation 34 | 35 | template 36 | constexpr Rtype single_visit_tail(Fn&& fn, V&& v){ 37 | 38 | constexpr auto RemainingIndex = std::decay_t::size - Offset; 39 | 40 | #define X(N) case (N + Offset) : \ 41 | if constexpr (N < RemainingIndex) { \ 42 | return static_cast(fn)( static_cast(v).template unsafe_get() ); \ 43 | break; \ 44 | } else DeclareUnreachable(); 45 | 46 | #define SEQSIZE 200 47 | 48 | switch( v.index() ){ 49 | 50 | INJECTSEQ(SEQSIZE) 51 | 52 | default : 53 | if constexpr (SEQSIZE < RemainingIndex) 54 | return vimpl::single_visit_tail(static_cast(fn), static_cast(v)); 55 | else 56 | DeclareUnreachable(); 57 | } 58 | 59 | #undef X 60 | #undef SEQSIZE 61 | } 62 | 63 | template 64 | constexpr Rtype single_visit_w_index_tail(Fn&& fn, V&& v){ 65 | 66 | constexpr auto RemainingIndex = std::decay_t::size - Offset; 67 | 68 | #define X(N) case (N + Offset) : \ 69 | if constexpr (N < RemainingIndex) { \ 70 | return static_cast(fn)( static_cast(v).template unsafe_get(), std::integral_constant{} ); \ 71 | break; \ 72 | } else DeclareUnreachable(); 73 | 74 | #define SEQSIZE 200 75 | 76 | switch( v.index() ){ 77 | 78 | INJECTSEQ(SEQSIZE) 79 | 80 | default : 81 | if constexpr (SEQSIZE < RemainingIndex) 82 | return vimpl::single_visit_w_index_tail(static_cast(fn), static_cast(v)); 83 | else 84 | DeclareUnreachable(); 85 | } 86 | 87 | #undef X 88 | #undef SEQSIZE 89 | } 90 | 91 | template 92 | constexpr decltype(auto) visit(Fn&& fn, V&& v){ 93 | return vimpl::single_visit_tail<0, rtype_visit>(SWL_FWD(fn), SWL_FWD(v)); 94 | } 95 | 96 | template 97 | constexpr decltype(auto) multi_visit(Fn&& fn, Head&& head, Tail&&... tail){ 98 | 99 | // visit them one by one, starting with the last 100 | auto vis = [&fn, &head] (auto&&... args) -> decltype(auto) { 101 | return vimpl::visit( [&fn, &args...] (auto&& elem) -> decltype(auto) { 102 | return SWL_FWD(fn)( SWL_FWD(elem), SWL_FWD(args)... ); 103 | }, SWL_FWD(head) ); 104 | }; 105 | 106 | if constexpr (sizeof...(tail) == 0) 107 | return SWL_FWD(vis)(); 108 | else if constexpr (sizeof...(tail) == 1) 109 | return vimpl::visit( SWL_FWD(vis), SWL_FWD(tail)... ); 110 | else 111 | return vimpl::multi_visit(SWL_FWD(vis), SWL_FWD(tail)...); 112 | } 113 | 114 | #undef DEC 115 | #undef SEQ30 116 | #undef SEQ100 117 | #undef SEQ200 118 | #undef SEQ400 119 | #undef DeclareUnreachable 120 | #undef CAT 121 | #undef CAT2 122 | #undef INJECTSEQ 123 | 124 | } 125 | 126 | 127 | #endif -------------------------------------------------------------------------------- /tests/archetypes.ipp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #ifndef DEFINE_BASE 10 | #define DEFINE_BASE(Name) ::ArchetypeBases::NullBase 11 | #endif 12 | #ifndef DEFINE_EXPLICIT 13 | #define DEFINE_EXPLICIT 14 | #endif 15 | #ifndef DEFINE_NOEXCEPT 16 | #define DEFINE_NOEXCEPT 17 | #endif 18 | #ifndef DEFINE_CONSTEXPR 19 | #ifdef TEST_WORKAROUND_EDG_EXPLICIT_CONSTEXPR 20 | #define DEFINE_CONSTEXPR 21 | #else // TEST_WORKAROUND_EDG_EXPLICIT_CONSTEXPR 22 | #define DEFINE_CONSTEXPR constexpr 23 | #endif // TEST_WORKAROUND_EDG_EXPLICIT_CONSTEXPR 24 | #endif 25 | #ifndef DEFINE_ASSIGN_CONSTEXPR 26 | #if TEST_STD_VER >= 14 27 | #define DEFINE_ASSIGN_CONSTEXPR DEFINE_CONSTEXPR 28 | #else 29 | #define DEFINE_ASSIGN_CONSTEXPR 30 | #endif 31 | #endif 32 | #ifndef DEFINE_CTOR 33 | #define DEFINE_CTOR = default 34 | #endif 35 | #ifndef DEFINE_DEFAULT_CTOR 36 | #define DEFINE_DEFAULT_CTOR DEFINE_CTOR 37 | #endif 38 | #ifndef DEFINE_ASSIGN 39 | #define DEFINE_ASSIGN = default 40 | #endif 41 | #ifndef DEFINE_DTOR 42 | #define DEFINE_DTOR(Name) 43 | #endif 44 | 45 | struct AllCtors : DEFINE_BASE(AllCtors) { 46 | using Base = DEFINE_BASE(AllCtors); 47 | using Base::Base; 48 | using Base::operator=; 49 | DEFINE_EXPLICIT DEFINE_CONSTEXPR AllCtors() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 50 | DEFINE_EXPLICIT DEFINE_CONSTEXPR AllCtors(AllCtors const&) DEFINE_NOEXCEPT DEFINE_CTOR; 51 | DEFINE_EXPLICIT DEFINE_CONSTEXPR AllCtors(AllCtors &&) DEFINE_NOEXCEPT DEFINE_CTOR; 52 | DEFINE_ASSIGN_CONSTEXPR AllCtors& operator=(AllCtors const&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 53 | DEFINE_ASSIGN_CONSTEXPR AllCtors& operator=(AllCtors &&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 54 | DEFINE_DTOR(AllCtors) 55 | }; 56 | 57 | struct NoCtors : DEFINE_BASE(NoCtors) { 58 | using Base = DEFINE_BASE(NoCtors); 59 | DEFINE_EXPLICIT NoCtors() DEFINE_NOEXCEPT = delete; 60 | DEFINE_EXPLICIT NoCtors(NoCtors const&) DEFINE_NOEXCEPT = delete; 61 | NoCtors& operator=(NoCtors const&) DEFINE_NOEXCEPT = delete; 62 | DEFINE_DTOR(NoCtors) 63 | }; 64 | 65 | struct NoDefault : DEFINE_BASE(NoDefault) { 66 | using Base = DEFINE_BASE(NoDefault); 67 | using Base::Base; 68 | DEFINE_EXPLICIT DEFINE_CONSTEXPR NoDefault() DEFINE_NOEXCEPT = delete; 69 | DEFINE_DTOR(NoDefault) 70 | }; 71 | 72 | struct DefaultOnly : DEFINE_BASE(DefaultOnly) { 73 | using Base = DEFINE_BASE(DefaultOnly); 74 | using Base::Base; 75 | DEFINE_EXPLICIT DEFINE_CONSTEXPR DefaultOnly() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 76 | DefaultOnly(DefaultOnly const&) DEFINE_NOEXCEPT = delete; 77 | DefaultOnly& operator=(DefaultOnly const&) DEFINE_NOEXCEPT = delete; 78 | DEFINE_DTOR(DefaultOnly) 79 | }; 80 | 81 | struct Copyable : DEFINE_BASE(Copyable) { 82 | using Base = DEFINE_BASE(Copyable); 83 | using Base::Base; 84 | DEFINE_EXPLICIT DEFINE_CONSTEXPR Copyable() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 85 | DEFINE_EXPLICIT DEFINE_CONSTEXPR Copyable(Copyable const &) DEFINE_NOEXCEPT DEFINE_CTOR; 86 | Copyable &operator=(Copyable const &) DEFINE_NOEXCEPT DEFINE_ASSIGN; 87 | DEFINE_DTOR(Copyable) 88 | }; 89 | 90 | struct CopyOnly : DEFINE_BASE(CopyOnly) { 91 | using Base = DEFINE_BASE(CopyOnly); 92 | using Base::Base; 93 | DEFINE_EXPLICIT DEFINE_CONSTEXPR CopyOnly() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 94 | DEFINE_EXPLICIT DEFINE_CONSTEXPR CopyOnly(CopyOnly const &) DEFINE_NOEXCEPT DEFINE_CTOR; 95 | DEFINE_EXPLICIT DEFINE_CONSTEXPR CopyOnly(CopyOnly &&) DEFINE_NOEXCEPT = delete; 96 | CopyOnly &operator=(CopyOnly const &) DEFINE_NOEXCEPT DEFINE_ASSIGN; 97 | CopyOnly &operator=(CopyOnly &&) DEFINE_NOEXCEPT = delete; 98 | DEFINE_DTOR(CopyOnly) 99 | }; 100 | 101 | struct NonCopyable : DEFINE_BASE(NonCopyable) { 102 | using Base = DEFINE_BASE(NonCopyable); 103 | using Base::Base; 104 | DEFINE_EXPLICIT DEFINE_CONSTEXPR NonCopyable() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 105 | DEFINE_EXPLICIT DEFINE_CONSTEXPR NonCopyable(NonCopyable const &) DEFINE_NOEXCEPT = delete; 106 | NonCopyable &operator=(NonCopyable const &) DEFINE_NOEXCEPT = delete; 107 | DEFINE_DTOR(NonCopyable) 108 | }; 109 | 110 | struct MoveOnly : DEFINE_BASE(MoveOnly) { 111 | using Base = DEFINE_BASE(MoveOnly); 112 | using Base::Base; 113 | DEFINE_EXPLICIT DEFINE_CONSTEXPR MoveOnly() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 114 | DEFINE_EXPLICIT DEFINE_CONSTEXPR MoveOnly(MoveOnly &&) DEFINE_NOEXCEPT DEFINE_CTOR; 115 | MoveOnly &operator=(MoveOnly &&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 116 | DEFINE_DTOR(MoveOnly) 117 | }; 118 | 119 | struct CopyAssignable : DEFINE_BASE(CopyAssignable) { 120 | using Base = DEFINE_BASE(CopyAssignable); 121 | using Base::Base; 122 | DEFINE_EXPLICIT DEFINE_CONSTEXPR CopyAssignable() DEFINE_NOEXCEPT = delete; 123 | CopyAssignable& operator=(CopyAssignable const&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 124 | DEFINE_DTOR(CopyAssignable) 125 | }; 126 | 127 | struct CopyAssignOnly : DEFINE_BASE(CopyAssignOnly) { 128 | using Base = DEFINE_BASE(CopyAssignOnly); 129 | using Base::Base; 130 | DEFINE_EXPLICIT DEFINE_CONSTEXPR CopyAssignOnly() DEFINE_NOEXCEPT = delete; 131 | CopyAssignOnly& operator=(CopyAssignOnly const&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 132 | CopyAssignOnly& operator=(CopyAssignOnly &&) DEFINE_NOEXCEPT = delete; 133 | DEFINE_DTOR(CopyAssignOnly) 134 | }; 135 | 136 | struct MoveAssignOnly : DEFINE_BASE(MoveAssignOnly) { 137 | using Base = DEFINE_BASE(MoveAssignOnly); 138 | using Base::Base; 139 | DEFINE_EXPLICIT DEFINE_CONSTEXPR MoveAssignOnly() DEFINE_NOEXCEPT = delete; 140 | MoveAssignOnly& operator=(MoveAssignOnly const&) DEFINE_NOEXCEPT = delete; 141 | MoveAssignOnly& operator=(MoveAssignOnly &&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 142 | DEFINE_DTOR(MoveAssignOnly) 143 | }; 144 | 145 | struct ConvertingType : DEFINE_BASE(ConvertingType) { 146 | using Base = DEFINE_BASE(ConvertingType); 147 | using Base::Base; 148 | DEFINE_EXPLICIT DEFINE_CONSTEXPR ConvertingType() DEFINE_NOEXCEPT DEFINE_DEFAULT_CTOR; 149 | DEFINE_EXPLICIT DEFINE_CONSTEXPR ConvertingType(ConvertingType const&) DEFINE_NOEXCEPT DEFINE_CTOR; 150 | DEFINE_EXPLICIT DEFINE_CONSTEXPR ConvertingType(ConvertingType &&) DEFINE_NOEXCEPT DEFINE_CTOR; 151 | ConvertingType& operator=(ConvertingType const&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 152 | ConvertingType& operator=(ConvertingType &&) DEFINE_NOEXCEPT DEFINE_ASSIGN; 153 | template 154 | DEFINE_EXPLICIT DEFINE_CONSTEXPR ConvertingType(Args&&...) DEFINE_NOEXCEPT {} 155 | template 156 | ConvertingType& operator=(Arg&&) DEFINE_NOEXCEPT { return *this; } 157 | DEFINE_DTOR(ConvertingType) 158 | }; 159 | 160 | template