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 |