├── .github
└── recursive-variant-authority.png
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cmake
├── config.cmake.in
└── helper.cmake
├── include
└── rva
│ └── variant.hpp
└── test
├── test_replace.cpp
├── test_rva.cpp
└── test_variant.cpp
/.github/recursive-variant-authority.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeinred/recursive-variant/9159bc55de7c2e08bc03ebaedbf393653771f4ad/.github/recursive-variant-authority.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .ccls-cache/
3 |
4 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 |
3 | project(
4 | "rva"
5 | VERSION 0.9.5
6 | DESCRIPTION "A Simple Library for Recursive Variant Types"
7 | HOMEPAGE_URL "https://github.com/codeinred/recursive-variant/"
8 | LANGUAGES CXX
9 | )
10 |
11 | add_library(rva INTERFACE)
12 | add_library(rva::rva ALIAS rva)
13 |
14 | # For ${CMAKE_INSTALL_
} variables that are standarized
15 | include(GNUInstallDirs)
16 |
17 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
19 |
20 | target_compile_features(
21 | rva
22 | INTERFACE
23 | cxx_std_20
24 | )
25 |
26 | target_include_directories(
27 | rva
28 | INTERFACE
29 | $
30 | $
31 | )
32 |
33 | # Installation
34 | # See: https://dominikberner.ch/cmake-interface-lib/
35 |
36 | install(
37 | TARGETS rva
38 | EXPORT ${PROJECT_NAME}_Targets
39 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
40 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
41 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
42 | )
43 |
44 | include(CMakePackageConfigHelpers)
45 | write_basic_package_version_file(
46 | "${PROJECT_NAME}ConfigVersion.cmake"
47 | VERSION ${PROJECT_VERSION}
48 | COMPATIBILITY SameMajorVersion
49 | )
50 |
51 | configure_package_config_file(
52 | "${PROJECT_SOURCE_DIR}/cmake/config.cmake.in"
53 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
54 | INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake
55 | )
56 |
57 | install(
58 | EXPORT ${PROJECT_NAME}_Targets
59 | FILE ${PROJECT_NAME}Targets.cmake
60 | NAMESPACE rva::
61 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake
62 | )
63 |
64 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
65 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
66 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake
67 | )
68 |
69 | install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/rva
70 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
71 | )
72 |
73 |
74 | include(helper)
75 |
76 | option(
77 | BUILD_TESTING
78 | "Build testing for RVA"
79 | ON)
80 |
81 | if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
82 | include(CTest)
83 |
84 | find_or_fetch(
85 | Catch2
86 | https://github.com/catchorg/catch2.git
87 | devel
88 | 3.0.0)
89 | FetchContent_MakeAvailable(${remote_dependencies})
90 |
91 | add_executable(
92 | test_rva
93 | test/test_rva.cpp)
94 | target_link_libraries(
95 | test_rva
96 | rva::rva
97 | Catch2::Catch2WithMain)
98 |
99 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/lib/cmake/Catch2)
100 | list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
101 | include(CTest)
102 | include(Catch)
103 | catch_discover_tests(test_rva)
104 | endif()
105 |
106 |
107 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Boost Software License - Version 1.0 - August 17th, 2003
2 |
3 | © 2021 Alecto Irene Perez
4 |
5 | Permission is hereby granted, free of charge, to any person or organization
6 | obtaining a copy of the software and accompanying documentation covered by this
7 | license (the "Software") to use, reproduce, display, distribute, execute, and
8 | transmit the Software, and to prepare derivative works of the Software, and to
9 | permit third-parties to whom the Software is furnished to do so, all subject to
10 | the following:
11 |
12 | The copyright notices in the Software and this entire statement, including the
13 | above license grant, this restriction and the following disclaimer, must be
14 | included in all copies of the Software, in whole or in part, and all derivative
15 | works of the Software, especially those created in whole or in part by Deep
16 | Neural Networks, Language Models, or other such programs advertised as "AI" or
17 | as "Artificial Intelligence" or as "Machine Learning", either with or without
18 | human input or intervention, unless such copies or derivative works are solely
19 | in the form of machine-executable object code generated by a source language
20 | processor.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
24 | FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
25 | COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES
26 | OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
27 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `rva::variant` — Recursive Sum Types for C++
2 |
3 | _Provided by the Recursive Variant Authority. We stand united in opposition to
4 | the [TVA](https://youtu.be/nW948Va-l10). May your variants never be pruned._
5 |
6 | Variants are exceedingly useful in C++, but they suffer from a singular and
7 | fundamental shortcoming: unlike sum types in many other languages, there's no
8 | mechanism to define recursive variants in C++.
9 |
10 | The Recursive Variant Authority provides a solution in the form of
11 | `rva::variant`, which allows you to write arbitrary recursive sum types with
12 | functionality identical to that provided by `std::variant`.
13 |
14 | 
15 |
16 | ## Understanding Recursive Variants
17 |
18 | To understand what a recursive variant is, and why it's useful, consider
19 | something like a value in json. It can be any of the following:
20 |
21 | - A null value
22 | - A string
23 | - A number (usually represented as a double)
24 | - A boolean value (either true or false),
25 | - An object (aka, a map between strings and json values)
26 | - An array of values.
27 |
28 | In a language like haskell, we could copy that definition pretty much verbatim
29 | (accounting for syntax, of course):
30 |
31 | ```hs
32 | data JsonValue = JsonNull
33 | | JsonBoolean Bool
34 | | JsonNumber Double
35 | | JsonStr String
36 | | JsonObject (Map String JsonValue)
37 | | JsonArray [JsonValue]
38 | ```
39 |
40 | It's important to note that `JsonValue` is being used in it's own definition:
41 | it's a recursive type.
42 |
43 | ## Recursive types in C++
44 |
45 | While this may seem a bit strange at first, recursive types aren't all that
46 | unusual. C++ has them too, and it's common for a type to store pointers to
47 | itself in situations such as a binary tree or a linked list. We can even define
48 | `json_value` as a recursive type using a union:
49 |
50 | ```cpp
51 | class json_value {
52 | int state = -1;
53 | union {
54 | /* state 0 */ std::nullptr_t m_null;
55 | /* state 1 */ bool m_bool;
56 | /* state 2 */ double m_number;
57 | /* state 3 */ std::string m_string;
58 | /* state 4 */ std::map m_object;
59 | /* state 5 */ std::vector m_array;
60 | };
61 |
62 | public:
63 | json_value(std::nullptr_t) : state(0), m_null() {}
64 | json_value(bool b) : state(1), m_bool(b) {}
65 | json_value(double d) : state(2), m_number(d) {}
66 | json_value(std::string const& str) : state(3), m_string(str) {}
67 | json_value(std::map const& map) : state(4), m_object(map) {}
68 | json_value(std::vector const& arr) : state(5), m_object(m_array) {}
69 |
70 | // Now we need to write move constructors...
71 | json_value(std::string&& str) : state(3), m_string(std::move(str)) {}
72 | json_value(std::map&& map) : state(4), m_object(std::move(map)) {}
73 | json_value(std::vector&& arr) : state(5), m_object(std::move(m_array)) {}
74 |
75 | // Now assignment operators...
76 |
77 | // Now comparison operators...
78 |
79 | // .emplace() might be nice
80 |
81 | // Oh hey did we forget copy and move assignment for json_value?
82 |
83 | // maybe we should have a way to check what the index of the currently active element is
84 | };
85 | ```
86 |
87 | ## `std::variant` is only a partial solution
88 |
89 | It quickly becomes apparent that the class we're writing is essentially a
90 | specialization of `std::variant`, which (thankfully) simplifies a lot of the
91 | code:
92 |
93 | ```cpp
94 | class json_value {
95 | std::variant<
96 | std::nullptr_t,
97 | bool,
98 | double,
99 | std::string,
100 | std::map,
101 | std::vector> value;
102 |
103 | public:
104 | json_value() = default;
105 | json_value(json_value const&) = default;
106 | json_value(json_value&&) = default;
107 | template
108 | json_value(T&& object) : value(std::forward(object)) {}
109 |
110 |
111 | // Now assignment operators...
112 |
113 | // Now comparison operators...
114 |
115 | // .emplace() might be nice
116 |
117 | // maybe we should have a way to check what the index of the currently active element is
118 | };
119 | ```
120 |
121 | This is better, and signifigantly less bug prone, but there's still a lot of
122 | boilerplate code that needs to be written.
123 |
124 | Here's the thing: at it's core, some types (like json_value) are best expressed
125 | as sum types! Anything we write to wrap one will just be a specialization of a
126 | sum type, and will usually involve a lot of boilerplate code to do the wrapping.
127 |
128 | At this point, it might be prudent to ask: can we define `json_value` directly
129 | as a `std::variant`? Would a recursive `using` declaration work? I wish it did.
130 | I really, really wish it did.
131 |
132 | ```cpp
133 | // LIES!!! This code doesn't work
134 | // error: use of undeclared identifier 'json_value'
135 | using json_value = std::variant<
136 | std::nullptr_t, // json null
137 | bool, // json boolean
138 | double, // json number
139 | std::string, // json string
140 | std::map, // json object
141 | std::vector>; // json array
142 | ```
143 |
144 | ## The Recursive Variant Authority provides a complete solution!
145 |
146 | `rva::variant` allows you to write recursive variants _without any boilerplate_
147 | by passing `rva::self_t` in the places where you want your recursive type to go!
148 | When you write a statement like this, `rva::variant` will replace instances of
149 | `rva::self_t` with a properly specified instance of it's type, and it does this
150 | as a paper-thin wrapper over `std::variant` that provides all your boilerplate
151 | code for you. And there was a _lot_ of boilerplate code to write.
152 |
153 | ```cpp
154 | using json_value = rva::variant<
155 | std::nullptr_t, // json null
156 | bool, // json boolean
157 | double, // json number
158 | std::string, // json string
159 | std::map, // json object, type is std::map
160 | std::vector>; // json array, type is std::vector
161 | ```
162 |
163 | `rva::variant` provides the full set of functionality given by `std::variant`,
164 | including:
165 |
166 | - `visit`,
167 | - `get`,
168 | - `get_if`,
169 | - `holds_alternative`,
170 | - `std::variant_size`,
171 | - `std::variant_alternative`,
172 | - `std::hash`)
173 |
174 | And it does so as a drop-in replacement (or as close to one as the standard
175 | would allow)!
176 |
177 | The Recursive Variant Authority hopes that you enjoy your time using
178 | `rva::variant`, and we encourage you to make full use of it's capabilities when
179 | writing your code.
180 |
181 | ## Usage & Installation
182 |
183 | The most straight-forward way to use `rva::variant` is by using
184 | [CMake's FetchContent interface](https://cmake.org/cmake/help/v3.21/module/FetchContent.html)
185 | to find and fetch the library:
186 |
187 | ```cmake
188 | FetchContent_Declare(
189 | rva
190 | GIT_REPOSITORY https://github.com/codeinred/recursive-variant.git
191 | GIT_TAG main
192 | )
193 | FetchContent_MakeAvailable(rva)
194 | ```
195 |
196 | Alternatively, you can install it as a CMake package like so. _Please note that
197 | it's not necessary to build it in release mode, as it's a header-only library._
198 |
199 | ```bash
200 | git clone https://github.com/codeinred/recursive-variant.git rva
201 | cd rva
202 | cmake -B build -DBUILD_TESTING=OFF
203 | cmake --build build
204 | sudo cmake --install build
205 | ```
206 |
207 | Once installed, the library can then be discovered as a CMake package:
208 |
209 | ```cmake
210 | find_package(rva REQUIRED)
211 | ```
212 |
213 | In either case, whether obtained via `FetchContent`, or installed as a package,
214 | you can use it via `target_link_libraries`. The library is header-only, but this
215 | will ensure that it's added to the include path for that target.
216 |
217 | ```cmake
218 | target_link_libraries( PRIVATE rva::rva)
219 | ```
220 |
221 | ### Running tests
222 |
223 | You may have noticed that the installation process "built" the library with
224 | testing off. Installing the library doesn't require running tests, however if
225 | you wish to run them, you may do so by ommitting the flag disabling testing, and
226 | then running `build/test_rva -s`. Testing is done via the
227 | [Catch2 Testing framework](https://github.com/catchorg/Catch2). You will see a
228 | lot of `{?}` in the test output, but that's just because Catch2 doesn't know how
229 | to print a variant.
230 |
231 | Tests may be found in the `test/` directory.
232 |
233 | ```bash
234 | git clone https://github.com/codeinred/recursive-variant.git rva
235 | cd rva
236 | cmake -B build
237 | cmake --build build -j 8
238 | build/test_rva -s
239 | ```
240 |
--------------------------------------------------------------------------------
/cmake/config.cmake.in:
--------------------------------------------------------------------------------
1 | @PACKAGE_INIT@
2 |
3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
4 | check_required_components("@PROJECT_NAME@")
5 |
--------------------------------------------------------------------------------
/cmake/helper.cmake:
--------------------------------------------------------------------------------
1 | ################################################
2 | ## Defaults, Definitions and helper functions ##
3 | ################################################
4 |
5 | # PROJECT_IS_TOP_LEVEL is a variable added in CMake 3.21 that checks if the
6 | # current project is the top-level project. This checks if it's built in,
7 | # and if not, adds a definition for it.
8 | if("${CMAKE_VERSION}" VERSION_LESS "3.21.0")
9 | if("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}")
10 | set(PROJECT_IS_TOP_LEVEL ON)
11 | else()
12 | set(PROJECT_IS_TOP_LEVEL OFF)
13 | endif()
14 | endif()
15 |
16 | # Defines some useful constants representing terminal codes to print things
17 | # in color.
18 | if(NOT WIN32)
19 | string(ASCII 27 Esc)
20 | set(ColorReset "${Esc}[m")
21 | set(ColorBold "${Esc}[1m")
22 | set(Red "${Esc}[31m")
23 | set(Green "${Esc}[32m")
24 | set(Yellow "${Esc}[33m")
25 | set(Blue "${Esc}[34m")
26 | set(Magenta "${Esc}[35m")
27 | set(Cyan "${Esc}[36m")
28 | set(White "${Esc}[37m")
29 | set(BoldRed "${Esc}[1;31m")
30 | set(BoldGreen "${Esc}[1;32m")
31 | set(BoldYellow "${Esc}[1;33m")
32 | set(BoldBlue "${Esc}[1;34m")
33 | set(BoldMagenta "${Esc}[1;35m")
34 | set(BoldCyan "${Esc}[1;36m")
35 | set(BoldWhite "${Esc}[1;37m")
36 | endif()
37 |
38 | # Define a function 'note' that prints a message in bold cyan
39 | function(note msg)
40 | message("🐈 ${BoldCyan}says: ${msg}${ColorReset}")
41 | endfunction()
42 |
43 | ####################################################
44 | ## Sec. 2: Dependency Management via FetchContent ##
45 | ####################################################
46 |
47 | set(remote_dependencies "")
48 |
49 | # If ALWAYS_FETCH is ON, then find_or_fetch will always fetch any remote
50 | # dependencies rather than using the ones provided by the system. This is
51 | # useful for creating a static executable.
52 | option(
53 | ALWAYS_FETCH
54 | "Tells find_or_fetch to always fetch packages"
55 | OFF)
56 |
57 |
58 | include(FetchContent)
59 | # find_or_fetch will search for a system installation of ${package} via
60 | # find_package. If it fails to find one, it'll use FetchContent to download and
61 | # build it locally.
62 | function(find_or_fetch package repo tag)
63 | if (NOT ALWAYS_FETCH)
64 | find_package(${package} ${ARGN} QUIET)
65 | endif()
66 |
67 | if (ALWAYS_FETCH OR NOT ${${package}_FOUND})
68 | note("Fetching dependency '${package}' from ${repo}")
69 | include(FetchContent)
70 | FetchContent_Declare(
71 | "${package}"
72 | GIT_REPOSITORY "${repo}"
73 | GIT_TAG "${tag}"
74 | )
75 | list(APPEND remote_dependencies "${package}")
76 | set (remote_dependencies ${remote_dependencies} PARENT_SCOPE)
77 | else()
78 | note("Using system cmake package for dependency '${package}'")
79 | endif()
80 | endfunction()
81 |
82 | function(always_fetch package repo tag)
83 | note("Fetching dependency '${package}' from ${repo}")
84 | include(FetchContent)
85 | FetchContent_Declare(
86 | "${package}"
87 | GIT_REPOSITORY "${repo}"
88 | GIT_TAG "${tag}"
89 | )
90 | list(APPEND remote_dependencies "${package}")
91 | set (remote_dependencies ${remote_dependencies} PARENT_SCOPE)
92 | endfunction()
93 |
94 | #####################################################################
95 | ## Sec. 3: Convinience Functions to add targets more automatically ##
96 | #####################################################################
97 |
98 |
99 | # Adds every top-level .cpp file in the given directory as an executable. Arguments
100 | # provided after the directory name are interpreted as libraries, and it'll link
101 | # targets in that directory against those libraries.
102 | function(add_source_dir dir)
103 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
104 | file(GLOB all_targets "${dir}/*.cpp")
105 | string(REPLACE ";" ", " library_list "${ARGN}")
106 | foreach(filename ${all_targets})
107 | get_filename_component(target ${filename} NAME_WLE)
108 | note("Adding '${target}' from ${dir}/${target}.cpp with libraries ${library_list}")
109 | add_executable("${target}" "${filename}")
110 | target_link_libraries("${target}" PRIVATE ${ARGN})
111 | endforeach()
112 | else()
113 | note("add_source_dir: Skipping ${dir}. Directory not found.")
114 | endif()
115 | endfunction()
116 |
117 | # Adds every top-level .cpp file in the given directory as an executable. Arguments
118 | # provided after the directory name are interpreted as libraries, and it'll link
119 | # targets in that directory against those libraries. Each target will also be
120 | # registered as a test via CTest
121 | function(add_test_dir dir)
122 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
123 | file(GLOB all_targets "${dir}/*.cpp")
124 | string(REPLACE ";" ", " library_list "${ARGN}")
125 | foreach(filename ${all_targets})
126 | get_filename_component(target ${filename} NAME_WLE)
127 | # Tests are named test_{name of test}
128 | set(target test_${target})
129 | note("Adding test '${target}' from ${dir}/${target}.cpp with libraries ${library_list}")
130 | add_executable("${target}" "${filename}")
131 | target_link_libraries("${target}" PRIVATE ${ARGN})
132 | add_test(NAME "${target}" COMMAND "${target}")
133 | endforeach()
134 | else()
135 | note("add_test_dir: Skipping ${dir}. Directory not found.")
136 | endif()
137 | endfunction()
138 |
139 |
140 | # Targets C++20 for a given target. also adds additional compiler options
141 | # in order to ensure greater levels of compatibility.
142 | function(target_cpp_20 target_name)
143 | target_compile_features(${target_name} INTERFACE cxx_std_20)
144 |
145 | # The /EHa flag enables standard C++ stack unwinding
146 | # See: https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-160
147 | if (MSVC)
148 | target_compile_options(${target_name} INTERFACE "/EHa")
149 | endif()
150 |
151 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
152 | # This definition is needed b/c the header needs it in order
153 | # to work on clang
154 | target_compile_definitions(${target_name} INTERFACE __cpp_impl_coroutine=1)
155 | endif()
156 |
157 | # Enables GCC support for coroutines (these are standard C++ now but GCC still
158 | # requires a flag for them)
159 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
160 | target_compile_options(${target_name} INTERFACE "-fcoroutines")
161 | endif()
162 | endfunction()
163 |
164 |
165 | function(add_submodules libname dir)
166 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
167 | file(GLOB all_modules "${dir}/*")
168 | foreach(module_dir ${all_modules})
169 | get_filename_component(module ${module_dir} NAME)
170 | note("Linked ${module} @ ${dir}/${module}")
171 | add_subdirectory("${dir}/${module}")
172 | target_link_libraries(${libname} INTERFACE ${module})
173 | endforeach()
174 | endif()
175 | endfunction()
176 |
--------------------------------------------------------------------------------
/include/rva/variant.hpp:
--------------------------------------------------------------------------------
1 | #ifndef RECURSIVE_VARIANT_AUTHORITY_VARIANT_HPP
2 | #define RECURSIVE_VARIANT_AUTHORITY_VARIANT_HPP
3 | #include
4 | #include
5 |
6 | namespace rva {
7 | /**
8 | * @brief replace is a template type used to implement replace_t. It provides
9 | * member, a using declaration named `type`.
10 | *
11 | * @tparam T the type to transform.
12 | * @tparam Find the type to find
13 | * @tparam Replace the type to replace it with.
14 | */
15 | template
16 | struct replace;
17 | /**
18 | * @brief replace is a template that takes a type T (which itself might be a
19 | * template), and replaces all instances of *Find* with *Replace*. For example:
20 | *
21 | * - `replace_t` -> `int`
22 | * - `replace_t, char, int>` -> `std::vector`
23 | *
24 | * @tparam T the type to transform.
25 | * @tparam Find the type to find
26 | * @tparam Replace the type to replace it with.
27 | */
28 | template
29 | using replace_t = typename replace::type;
30 |
31 | struct self_t {};
32 |
33 | // See: https://en.cppreference.com/w/cpp/utility/variant
34 | template
35 | class variant : public std::variant>...> {
36 | public:
37 | using base_type = std::variant>...>;
38 | constexpr static bool
39 | nothrow_swappable = std::is_nothrow_swappable_v;
40 |
41 | using base_type::base_type;
42 |
43 | // Observers
44 | using base_type::index;
45 | using base_type::valueless_by_exception;
46 |
47 | // Modifiers
48 | using base_type::operator=;
49 | using base_type::emplace;
50 | using base_type::swap;
51 |
52 | variant() = default;
53 | variant(variant const&) = default;
54 | variant(variant&&) = default;
55 | variant& operator=(variant const&) = default;
56 | variant& operator=(variant&&) = default;
57 |
58 | constexpr void swap(variant& other) noexcept(nothrow_swappable) {
59 | base_type::swap(other);
60 | }
61 | constexpr base_type& get_base() & noexcept { return *this; }
62 | constexpr base_type const& get_base() const& noexcept { return *this; }
63 | constexpr base_type&& get_base() && noexcept { return *this; }
64 | constexpr base_type const&& get_base() const&& noexcept { return *this; }
65 |
66 | constexpr base_type* get_pointer_to_base() noexcept { return this; }
67 | constexpr base_type const* get_pointer_to_base() const noexcept {
68 | return this;
69 | }
70 |
71 | auto operator<=>(variant const&) const = default;
72 | bool operator==(variant const&) const = default;
73 | };
74 |
75 | // See: https://en.cppreference.com/w/cpp/utility/variant/visit
76 | template
77 | constexpr decltype(auto) visit(Visitor&& visitor, Variants&&... variants) {
78 | return std::visit(
79 | std::forward(visitor),
80 | std::forward(variants).get_base()...);
81 | }
82 | template
83 | constexpr R visit(Visitor&& visitor, Variants&&... variants) {
84 | return std::visit(
85 | std::forward(visitor),
86 | std::forward(variants).get_base()...);
87 | }
88 |
89 | // See: https://en.cppreference.com/w/cpp/utility/variant/get
90 | template
91 | constexpr decltype(auto) get(rva::variant& v) {
92 | return std::get(std::forward(v).get_base());
93 | }
94 | template
95 | constexpr decltype(auto) get(rva::variant&& v) {
96 | return std::get(std::forward(v).get_base());
97 | }
98 | template
99 | constexpr decltype(auto) get(const rva::variant& v) {
100 | return std::get(std::forward(v).get_base());
101 | }
102 | template
103 | constexpr decltype(auto) get(const rva::variant&& v) {
104 | return std::get(std::forward(v).get_base());
105 | }
106 | template
107 | constexpr T& get(rva::variant& v) {
108 | return std::get(std::forward(v).get_base());
109 | }
110 | template
111 | constexpr T&& get(rva::variant&& v) {
112 | return std::get(std::forward(v).get_base());
113 | }
114 | template
115 | constexpr const T& get(const rva::variant& v) {
116 | return std::get(std::forward(v).get_base());
117 | }
118 | template
119 | constexpr const T&& get(const rva::variant&& v) {
120 | return std::get(std::forward(v).get_base());
121 | }
122 |
123 | // See: https://en.cppreference.com/w/cpp/utility/variant/get_if
124 | template
125 | constexpr auto* get_if(rva::variant* pv) noexcept {
126 | return std::get_if(pv->get_pointer_to_base());
127 | }
128 | template
129 | constexpr auto const* get_if(const rva::variant* pv) noexcept {
130 | return std::get_if(pv->get_pointer_to_base());
131 | }
132 | template
133 | constexpr auto* get_if(rva::variant* pv) noexcept {
134 | return std::get_if(pv->get_pointer_to_base());
135 | }
136 | template
137 | constexpr auto const* get_if(const rva::variant* pv) noexcept {
138 | return std::get_if(pv->get_pointer_to_base());
139 | }
140 |
141 | template
142 | constexpr bool holds_alternative(const rva::variant& v) noexcept {
143 | return std::holds_alternative(v.get_base());
144 | }
145 | } // namespace rva
146 |
147 | template
148 | struct std::hash> : std::hash> {
149 | using base_type = std::hash>;
150 | using base_type::base_type;
151 | hash() = default;
152 | hash(hash const&) = default;
153 | hash(hash&&) = default;
154 | size_t operator()(rva::variant const& v) const {
155 | return base_type::operator()(v.get_base());
156 | }
157 | };
158 |
159 | template
160 | struct std::variant_size>
161 | : std::integral_constant {};
162 | template
163 | struct std::variant_size>
164 | : std::integral_constant {};
165 |
166 | template
167 | struct std::variant_alternative>
168 | : std::variant_alternative::base_type> {};
169 | template
170 | struct std::variant_alternative>
171 | : std::variant_alternative::base_type> {};
172 |
173 | // Implementation for replace
174 | namespace rva {
175 | template
176 | struct replace {
177 | using type = T;
178 | };
179 | template
180 | struct replace {
181 | using type = Replace;
182 | };
183 | template
184 | struct replace {
185 | using type = Replace*;
186 | };
187 | template
188 | struct replace {
189 | using type = Replace&;
190 | };
191 | template
192 | struct replace {
193 | using type = Replace&&;
194 | };
195 | template
196 | struct replace {
197 | using type = Replace[];
198 | };
199 | template
200 | struct replace {
201 | using type = Replace[N];
202 | };
203 | template
204 | struct replace {
205 | using type = const Replace;
206 | };
207 | template
208 | struct replace {
209 | using type = const Replace*;
210 | };
211 | template
212 | struct replace {
213 | using type = const Replace&;
214 | };
215 | template
216 | struct replace {
217 | using type = const Replace[];
218 | };
219 | template
220 | struct replace {
221 | using type = const Replace[N];
222 | };
223 | template
224 | struct replace {
225 | using type = replace_t*;
226 | };
227 | template
228 | struct replace {
229 | using type = replace_t&;
230 | };
231 | template
232 | struct replace {
233 | using type = replace_t&&;
234 | };
235 | template
236 | struct replace {
237 | using type = replace_t[];
238 | };
239 | template
240 | struct replace {
241 | using type = replace_t[N];
242 | };
243 | template
244 | struct replace {
245 | using type = replace_t const;
246 | };
247 | template
248 | struct replace {
249 | using type = replace_t const*;
250 | };
251 | template
252 | struct replace {
253 | using type = replace_t const&;
254 | };
255 | template
256 | struct replace {
257 | using type = replace_t const[];
258 | };
259 | template
260 | struct replace {
261 | using type = replace_t const[N];
262 | };
263 |
264 | template class T, class... Ts, class Find, class Replace>
265 | struct replace, Find, Replace> {
266 | using type = T...>;
267 | };
268 |
269 | // Add shortcut for rva::variant to avoid replacing into instances of an
270 | // rva::variant that's given as a template parameter to another rva::variant
271 | template
272 | struct replace, Find, Replace> {
273 | using type = rva::variant;
274 | };
275 | } // namespace rva
276 |
277 | #endif
278 |
--------------------------------------------------------------------------------
/test/test_replace.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | TEST_CASE("Test replace") {
6 |
7 | STATIC_REQUIRE(std::is_same_v>);
8 | STATIC_REQUIRE(std::is_same_v>);
9 | STATIC_REQUIRE(std::is_same_v>);
10 | STATIC_REQUIRE(
11 | std::is_same_v>);
12 | STATIC_REQUIRE(
13 | std::is_same_v>);
14 | STATIC_REQUIRE(
15 | std::is_same_v>);
16 | STATIC_REQUIRE(std::is_same_v<
17 | int const(&)[],
18 | rva::replace_t>);
19 | STATIC_REQUIRE(
20 | std::is_same_v>);
21 | STATIC_REQUIRE(
22 | std::is_same_v>);
23 | STATIC_REQUIRE(
24 | std::is_same_v>);
25 | STATIC_REQUIRE(
26 | std::is_same_v>);
27 | STATIC_REQUIRE(
28 | std::is_same_v>);
29 | STATIC_REQUIRE(
30 | std::is_same_v<
31 | std::vector** const* const*> const****&,
32 | rva::replace_t<
33 | std::vector<
34 | std::vector** const* const*> const****&,
35 | char,
36 | int>>);
37 | STATIC_REQUIRE(std::is_same_v<
38 | int const(&)[5],
39 | rva::replace_t>);
40 |
41 | // Check recursive variants that are members of other recursive variants
42 | using V1 = rva::variant>;
43 | using V2 = rva::variant>;
44 |
45 | STATIC_REQUIRE(
46 | std::is_same_v>);
47 | }
48 |
--------------------------------------------------------------------------------
/test/test_rva.cpp:
--------------------------------------------------------------------------------
1 | #include "test_replace.cpp"
2 | #include "test_variant.cpp"
3 |
--------------------------------------------------------------------------------
/test/test_variant.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include