| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | /* Copyright 2023 Jack A Bernard Jr. | |||
| 2 | * | |||
| 3 | * Licensed under the Apache License, Version 2.0 (the License); | |||
| 4 | * you may not use this file except in compliance with the License. | |||
| 5 | * You may obtain a copy of the License at | |||
| 6 | * | |||
| 7 | * http://www.apache.org/licenses/LICENSE-2.0 | |||
| 8 | * | |||
| 9 | * Unless required by applicable law or agreed to in writing, software | |||
| 10 | * distributed under the License is distributed on an AS IS BASIS, | |||
| 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| 12 | * See the License for the specific language governing permissions and | |||
| 13 | * limitations under the License. | |||
| 14 | */ | |||
| 15 | ||||
| 16 | /// @mainpage Sumty 🍵 | |||
| 17 | /// | |||
| 18 | /// `sumty` is a header-only C++20 library that provides "better" sum types. | |||
| 19 | /// The C++ standard library provides `std::variant`, `std::optional`, and | |||
| 20 | /// `std::exepcted` (as of C++23), but these have some limitiations that | |||
| 21 | /// `sumty` aims to remove. | |||
| 22 | /// | |||
| 23 | /// `sumty` provides four sum types, 3 of which correspond to one of the | |||
| 24 | /// STL types mentioned above. | |||
| 25 | /// | |||
| 26 | /// * @ref sumty::variant "variant" - improved `std::variant` | |||
| 27 | /// * @ref sumty::option "option" - improved `std::optional` | |||
| 28 | /// * @ref sumty::result "result" - improved `std::expected` | |||
| 29 | /// * @ref sumty::error_set "error_set" - no STL equivalent but similar to | |||
| 30 | /// `std::variant` | |||
| 31 | /// | |||
| 32 | /// These sum types differ from those in the STL in a number of ways. | |||
| 33 | /// Primarily, `sumty`'s sum types extend alternatives to support | |||
| 34 | /// references (lvalue and rvalue) and `void`. `sumty` also guarantees a | |||
| 35 | /// few size optimizations that applies to all sum types, the boil down | |||
| 36 | /// to taking advantage of empty types and the invariant that lvalue | |||
| 37 | /// references are not null. Some size optimization examples are listed | |||
| 38 | /// below. | |||
| 39 | /// | |||
| 40 | /// * `sizeof(variant<std::monostate, std::monostate>) == sizeof(bool)` | |||
| 41 | /// * `sizeof(option<int&>) == sizeof(void*)` | |||
| 42 | /// * `sizeof(result<void, void>) == sizeof(bool)` | |||
| 43 | /// * `sizeof(error_set<empty1, empty2, empty3>) == sizeof(uint8_t)` | |||
| 44 | ||||
| 45 | #ifndef SUMTY_UTILS_HPP | |||
| 46 | #define SUMTY_UTILS_HPP | |||
| 47 | ||||
| 48 | #include <cstddef> | |||
| 49 | #include <functional> | |||
| 50 | #include <type_traits> | |||
| 51 | #include <utility> | |||
| 52 | ||||
| 53 | #if defined(__GNUC__) && !defined(__clang__) | |||
| 54 | #pragma GCC diagnostic push | |||
| 55 | #pragma GCC diagnostic ignored "-Wterminate" | |||
| 56 | #endif | |||
| 57 | ||||
| 58 | #if defined(_MSC_VER) | |||
| 59 | #pragma warning(push) | |||
| 60 | #pragma warning(disable : 4297) | |||
| 61 | #pragma warning(disable : 4645) | |||
| 62 | #pragma warning(disable : 4702) | |||
| 63 | #endif | |||
| 64 | ||||
| 65 | #if defined(_MSC_VER) && !defined(__clang__) | |||
| 66 | #define SUMTY_NO_UNIQ_ADDR [[msvc::no_unique_address]] | |||
| 67 | #else | |||
| 68 | #define SUMTY_NO_UNIQ_ADDR [[no_unique_address]] | |||
| 69 | #endif | |||
| 70 | ||||
| 71 | namespace sumty { | |||
| 72 | ||||
| 73 | using std::in_place_index_t; | |||
| 74 | using std::in_place_t; | |||
| 75 | using std::in_place_type_t; | |||
| 76 | using in_place_error_t = in_place_index_t<1>; | |||
| 77 | ||||
| 78 | using std::in_place; | |||
| 79 | using std::in_place_index; | |||
| 80 | using std::in_place_type; | |||
| 81 | static inline constexpr in_place_error_t in_place_error = in_place_index<1>; | |||
| 82 | ||||
| 83 | /// @relates variant | |||
| 84 | template <size_t N> | |||
| 85 | struct index_t { | |||
| 86 | template <typename S> | |||
| 87 | constexpr | |||
| 88 | #ifndef DOXYGEN | |||
| 89 | decltype(auto) | |||
| 90 | #else | |||
| 91 | DEDUCED | |||
| 92 | #endif | |||
| 93 | 6 | operator()(S&& s) const | ||
| 94 | #ifndef DOXYGEN | |||
| 95 | requires requires { std::forward<S>(s)[index_t<N>{}]; } | |||
| 96 | #endif | |||
| 97 | { | |||
| 98 | 6 | return std::forward<S>(s)[index_t<N>{}]; | ||
| 99 | } | |||
| 100 | }; | |||
| 101 | ||||
| 102 | /// @relates index_t | |||
| 103 | template <size_t N> | |||
| 104 | static inline constexpr index_t<N> index{}; | |||
| 105 | ||||
| 106 | /// @relates index_t | |||
| 107 | template <size_t N> | |||
| 108 | static inline constexpr index_t<N> index_v{}; | |||
| 109 | ||||
| 110 | /// @relates variant | |||
| 111 | template <typename T> | |||
| 112 | struct type_t { | |||
| 113 | template <typename S> | |||
| 114 | constexpr | |||
| 115 | #ifndef DOXYGEN | |||
| 116 | decltype(auto) | |||
| 117 | #else | |||
| 118 | DEDUCED | |||
| 119 | #endif | |||
| 120 | 8 | operator()(S&& s) const | ||
| 121 | #ifndef DOXYGEN | |||
| 122 | requires requires { std::forward<S>(s)[type_t<T>{}]; } | |||
| 123 | #endif | |||
| 124 | { | |||
| 125 | 8 | return std::forward<S>(s)[type_t<T>{}]; | ||
| 126 | } | |||
| 127 | }; | |||
| 128 | ||||
| 129 | /// @relates type_t | |||
| 130 | template <typename T> | |||
| 131 | static inline constexpr type_t<T> type{}; | |||
| 132 | ||||
| 133 | /// @relates type_t | |||
| 134 | template <typename T> | |||
| 135 | static inline constexpr type_t<T> type_v{}; | |||
| 136 | ||||
| 137 | /// @relates variant | |||
| 138 | struct void_t { | |||
| 139 | constexpr void_t() noexcept = default; | |||
| 140 | ||||
| 141 | constexpr void_t(const void_t&) noexcept = default; | |||
| 142 | ||||
| 143 | constexpr void_t(void_t&&) noexcept = default; | |||
| 144 | ||||
| 145 | constexpr ~void_t() noexcept = default; | |||
| 146 | ||||
| 147 | constexpr void_t& operator=(const void_t&) noexcept = default; | |||
| 148 | ||||
| 149 | constexpr void_t& operator=(void_t&&) noexcept = default; | |||
| 150 | ||||
| 151 | template <typename T> | |||
| 152 | requires(std::is_default_constructible_v<T>) | |||
| 153 | 6 | explicit constexpr operator T() const | ||
| 154 | noexcept(std::is_nothrow_default_constructible_v<T>) { | |||
| 155 | 6 | return T{}; | ||
| 156 | } | |||
| 157 | }; | |||
| 158 | ||||
| 159 | /// @relates void_t | |||
| 160 | static inline constexpr void_t void_v{}; | |||
| 161 | ||||
| 162 | /// @relates option | |||
| 163 | struct none_t {}; | |||
| 164 | ||||
| 165 | /// @relates none_t | |||
| 166 | static inline constexpr none_t none{}; | |||
| 167 | ||||
| 168 | /// @relates none_t | |||
| 169 | static inline constexpr none_t none_v{}; | |||
| 170 | ||||
| 171 | /// @brief A type that can never be instantiated | |||
| 172 | /// | |||
| 173 | /// @details | |||
| 174 | /// The @ref never type is a special type that has no possible values. In | |||
| 175 | /// isolation, it has no purpose, but it can be used with a sum type to | |||
| 176 | /// represent an alternative that is never used. The sum types of `sumty` | |||
| 177 | /// optimize around @ref never, using the assumption that an alternative of the | |||
| 178 | /// type @ref never will never be used. | |||
| 179 | /// | |||
| 180 | /// The primary use case for @ref never is as the error type for a @ref result. | |||
| 181 | /// A generic API might require that a function return a @ref result, but the | |||
| 182 | /// user's function may not have error code paths. In this case, the user could | |||
| 183 | /// return a `result<T, never>`. Since sumty optimizes around @ref never, the | |||
| 184 | /// `result<T, never>` will have the same size as `T`, and also be an empty type | |||
| 185 | /// if `T` is an empty type. | |||
| 186 | /// | |||
| 187 | /// Despite there being no way to instantiate a @ref never (without invoking | |||
| 188 | /// undefined behavior), @ref never is copyable and moveable. This ensure that | |||
| 189 | /// using @ref never as an alternative of a sum type does not make that type | |||
| 190 | /// uncopyable or immovable. | |||
| 191 | /// | |||
| 192 | /// @ref never can also be implicitly converted to any other type. Although | |||
| 193 | /// such a conversion should never be possible at runtime, it allows sum types | |||
| 194 | /// with a @ref never alternative to be converted. For example, a | |||
| 195 | /// `result<T, never>` can be implicitly converted to a | |||
| 196 | /// `result<T, my_error>` (assuming that `my_error` is a valid type). Since the | |||
| 197 | /// @ref never error type can never be instantiated, the runtime conversion is | |||
| 198 | /// always on the ok alternative (`T` -> `T` in our example). | |||
| 199 | struct never { | |||
| 200 | private: | |||
| 201 | // NOLINTNEXTLINE(bugprone-exception-escape) | |||
| 202 | ✗ | [[noreturn]] static constexpr void unreachable() noexcept { | ||
| 203 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (std::is_constant_evaluated()) { | |
| 204 | #ifndef _MSC_VER | |||
| 205 | ✗ | throw 0; // NOLINT(hicpp-exception-baseclass) | ||
| 206 | #else | |||
| 207 | return; | |||
| 208 | #endif | |||
| 209 | } else { | |||
| 210 | #ifdef __cpp_lib_unreachable | |||
| 211 | std::unreachable(); | |||
| 212 | #elif defined(_MSC_VER) && !defined(__clang__) | |||
| 213 | __assume(false); | |||
| 214 | #elif defined(__GNUC__) | |||
| 215 | ✗ | __builtin_unreachable(); | ||
| 216 | #else | |||
| 217 | for (;;) {} | |||
| 218 | #endif | |||
| 219 | } | |||
| 220 | } | |||
| 221 | ||||
| 222 | public: | |||
| 223 | never() = delete; | |||
| 224 | ||||
| 225 | // These special constructors/operators are intentionally non-trivial | |||
| 226 | // so that instantiation of this type is always UB. This type already | |||
| 227 | // has no valid constructor for creating a new instance from scratch. | |||
| 228 | // Making these non-trivial ensures that it is also UB to instantiate | |||
| 229 | // this type by reinterpretation, whether that be by using | |||
| 230 | // reinterpret_cast, std::bit_cast, or anything else. Thus, the type | |||
| 231 | // can never be instantiated, but does not prevent copyability or | |||
| 232 | // moveability when used in a sum type. | |||
| 233 | ||||
| 234 | [[noreturn]] constexpr never([[maybe_unused]] const never& other) noexcept { | |||
| 235 | unreachable(); | |||
| 236 | } | |||
| 237 | ||||
| 238 | [[noreturn]] constexpr never([[maybe_unused]] never&& other) noexcept { unreachable(); } | |||
| 239 | ||||
| 240 | [[noreturn]] constexpr ~never() noexcept { unreachable(); } | |||
| 241 | ||||
| 242 | // NOLINTNEXTLINE(cert-oop54-cpp) | |||
| 243 | constexpr never& operator=([[maybe_unused]] const never& rhs) noexcept { | |||
| 244 | unreachable(); | |||
| 245 | return *this; | |||
| 246 | } | |||
| 247 | ||||
| 248 | constexpr never& operator=([[maybe_unused]] never&& rhs) noexcept { | |||
| 249 | unreachable(); | |||
| 250 | return *this; | |||
| 251 | } | |||
| 252 | ||||
| 253 | // This type can be implicitly converted to any other type, except | |||
| 254 | // that in reality this can never happen, since this type can never | |||
| 255 | // be instantiated. For example, this allows a `result<T, never>` to | |||
| 256 | // convert to `result<T, E>`, for any `E`. | |||
| 257 | template <typename T> | |||
| 258 | // NOLINTNEXTLINE(bugprone-exception-escape, hicpp-explicit-conversions) | |||
| 259 | ✗ | [[noreturn]] constexpr operator T() const noexcept { | ||
| 260 | ✗ | unreachable(); | ||
| 261 | throw 0; // NOLINT(hicpp-exception-baseclass) | |||
| 262 | } | |||
| 263 | }; | |||
| 264 | ||||
| 265 | /// @relates variant | |||
| 266 | template <typename... T> | |||
| 267 | struct overload : T... { | |||
| 268 | using T::operator()...; | |||
| 269 | }; | |||
| 270 | ||||
| 271 | template <typename... T> | |||
| 272 | overload(T...) -> overload<T...>; | |||
| 273 | ||||
| 274 | /// @brief Invokes a callable and returns the result, transforming `void` to @ref void_t | |||
| 275 | /// | |||
| 276 | /// @details | |||
| 277 | /// This function is mostly equivalent to `std::invoke`. The difference is that if the | |||
| 278 | /// return type of the invocable is `void`, this function instead returns an instance of | |||
| 279 | /// @ref void_t. | |||
| 280 | /// | |||
| 281 | /// When dealing with generic code, `void` often presents a problem since an | |||
| 282 | /// instantiation of `void` can't exist, despite the fact that functions successfully | |||
| 283 | /// return `void`. In order to simplify generic code, this function ensures that calling | |||
| 284 | /// a function that returns `void` instead returns a value that can exist, can be bound | |||
| 285 | /// to a variable, and can be passed into another function. | |||
| 286 | /// | |||
| 287 | /// ## Example | |||
| 288 | /// ``` | |||
| 289 | /// option<void> opt; | |||
| 290 | /// | |||
| 291 | /// opt.emplace(invoke([]{})); | |||
| 292 | /// | |||
| 293 | /// assert(opt.has_value()); | |||
| 294 | /// ``` | |||
| 295 | /// | |||
| 296 | /// @param f The invocable object | |||
| 297 | /// @param args The arguments to be passed to the invocable | |||
| 298 | template <typename F, typename... Args> | |||
| 299 | constexpr | |||
| 300 | #ifndef DOXYGEN | |||
| 301 | std::conditional_t<std::is_void_v<std::invoke_result_t<F, Args...>>, | |||
| 302 | void_t, | |||
| 303 | std::invoke_result_t<F, Args...>> | |||
| 304 | #else | |||
| 305 | DEDUCED | |||
| 306 | #endif | |||
| 307 | 6 | invoke(F&& f, Args&&... args) { | ||
| 308 | using ret_t = std::invoke_result_t<F, Args...>; | |||
| 309 | if constexpr (std::is_void_v<ret_t>) { | |||
| 310 | 1 | std::invoke(std::forward<F>(f), std::forward<Args>(args)...); | ||
| 311 | 1 | return void_v; | ||
| 312 | } else { | |||
| 313 | 5 | return std::invoke(std::forward<F>(f), std::forward<Args>(args)...); | ||
| 314 | } | |||
| 315 | } | |||
| 316 | ||||
| 317 | } // namespace sumty | |||
| 318 | ||||
| 319 | #if defined(__GNUC__) && !defined(__clang__) | |||
| 320 | #pragma GCC diagnostic pop | |||
| 321 | #endif | |||
| 322 | ||||
| 323 | #if defined(_MSC_VER) | |||
| 324 | #pragma warning(pop) | |||
| 325 | #endif | |||
| 326 | ||||
| 327 | #endif | |||
| 328 |