sumty  0.1.0
Better sum types for C++
utils.hpp
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  operator()(S&& s) const
94 #ifndef DOXYGEN
95  requires requires { std::forward<S>(s)[index_t<N>{}]; }
96 #endif
97  {
98  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  operator()(S&& s) const
121 #ifndef DOXYGEN
122  requires requires { std::forward<S>(s)[type_t<T>{}]; }
123 #endif
124  {
125  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  explicit constexpr operator T() const
154  noexcept(std::is_nothrow_default_constructible_v<T>) {
155  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  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  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  std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
311  return void_v;
312  } else {
313  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