BRICKS
Small, useful blocks of code, to build bigger things.
Loading...
Searching...
No Matches
result.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <functional>
5#include <initializer_list>
6#include <stdexcept>
7#include <type_traits>
8#include <variant>
9
10#include "type_traits.hpp"
11
12namespace bricks {
13
24class bad_result_access : public std::runtime_error {
25 public:
26 using std::runtime_error::runtime_error;
27};
28
29namespace detail {
30
37template <typename T, typename tag>
38class value_container {
39 public:
40 using value_type = T;
41
42 explicit value_container(T value) noexcept(std::is_nothrow_move_constructible_v<T>)
43 : value_(std::move(value))
44 {
45 }
46 auto operator=(T value) noexcept(std::is_nothrow_move_assignable_v<T>) -> value_container&
47 {
48 value_ = std::move(value);
49 return *this;
50 }
51
52 [[nodiscard]] constexpr auto get() noexcept -> T& { return value_; }
53 [[nodiscard]] constexpr auto get() const noexcept -> const T& { return value_; }
54
55 [[nodiscard]] constexpr auto operator==(const value_container& other) const noexcept -> bool
56 {
57 return value_ == other.value_;
58 }
59 [[nodiscard]] constexpr auto operator!=(const value_container& other) const noexcept -> bool
60 {
61 return !(*this == other);
62 }
63
64 private:
65 T value_;
66};
67
68struct ok_tag {};
69struct err_tag {};
70
71} // namespace detail
72
73template <typename T>
74using ok = detail::value_container<T, detail::ok_tag>;
75
76template <typename E>
77using err = detail::value_container<E, detail::err_tag>;
78
91template <typename T, typename E>
92class result {
93 using variant_t = std::variant<ok<T>, err<E>>;
94
95 public:
97 using value_type = T;
99 using error_type = E;
100
101 result() = delete;
102
103 result(const result&) noexcept(std::is_nothrow_copy_constructible_v<variant_t>) = default;
104 auto operator=(const result&) noexcept(std::is_nothrow_copy_assignable_v<variant_t>)
105 -> result& = default;
106
107 result(result&&) noexcept = default;
108 auto operator=(result&&) noexcept -> result& = default;
109
110 ~result() = default;
111
125 template <typename U, typename std::enable_if_t<
126 std::is_same_v<U, ok<T>> || std::is_same_v<U, err<E>>, bool> = true>
127 // cppcheck-suppress noExplicitConstructor
128 constexpr result(U in) noexcept(std::is_nothrow_move_constructible_v<U>) // NOLINT
129 : value_(std::move(in))
130 {
131 }
132
133 template <typename U = T, typename std::enable_if_t<!std::is_same_v<U, E>, bool> = true>
134 // cppcheck-suppress noExplicitConstructor
135 constexpr result(value_type in) noexcept(std::is_nothrow_move_constructible_v<ok<T>>) // NOLINT
136 : value_(ok<T>{std::move(in)})
137 {
138 }
139
140 template <typename U = E, typename std::enable_if_t<!std::is_same_v<T, U>, bool> = true>
141 // cppcheck-suppress noExplicitConstructor
142 constexpr result(error_type in) noexcept(std::is_nothrow_move_constructible_v<err<E>>) // NOLINT
143 : value_(err<E>{std::move(in)})
144 {
145 }
146
161 template <typename F>
162 [[nodiscard]] static auto from_try_or(F&& f, error_type error_value) noexcept(
163 std::is_nothrow_constructible_v<err<E>, error_type>) -> result
164 {
165 static_assert(std::is_invocable_v<F>, "f must be invocable");
166 static_assert(std::is_constructible_v<T, std::invoke_result_t<F>>,
167 "value_type must be constructible from the result of f");
168
169 try {
170 return ok<T>{std::invoke(std::forward<F>(f))};
171 } catch (...) {
172 return err<E>{error_value};
173 }
174 }
175
189 template <typename F>
190 [[nodiscard]] static auto from_try_or_default(F&& f) noexcept(
191 std::is_nothrow_invocable_v<decltype(result<T, E>::from_try_or<F>)>&&
192 std::is_nothrow_constructible_v<error_type>) -> result
193 {
194 static_assert(std::is_default_constructible_v<error_type>,
195 "error_type must be default constructible");
196
197 return from_try_or(std::forward<F>(f), error_type{});
198 }
199
214 template <typename F, typename OnError>
215 [[nodiscard]] static auto from_try_or_else(F&& f, OnError&& on_error) noexcept(
216 std::is_nothrow_invocable_v<OnError>&&
217 std::is_nothrow_constructible_v<result<T, E>, std::invoke_result_t<OnError>>) -> result
218 {
219 static_assert(std::is_invocable_v<F>, "f must be invocable");
220 static_assert(std::is_constructible_v<ok<T>, std::invoke_result_t<F>>,
221 "value_type must be constructible from the result of F");
222 static_assert(std::is_invocable_v<OnError>, "on_error must be invocable");
223
224 try {
225 return ok<T>{std::invoke(std::forward<F>(f))};
226 } catch (...) {
227 return {std::invoke(std::forward<OnError>(on_error))};
228 }
229 }
230
242 template <typename U>
243 constexpr auto operator=(U in) noexcept -> result&
244 {
245 using param_t = std::decay_t<U>;
246 if constexpr (std::is_same_v<param_t, ok<T>> || std::is_same_v<param_t, err<E>>) {
247 value_ = std::move(in);
248 } else if constexpr (std::is_same_v<param_t, T> || std::is_convertible_v<param_t, T>) {
249 value_ = ok<T>{std::move(in)};
250 } else if constexpr (std::is_same_v<param_t, E> || std::is_convertible_v<param_t, E>) {
251 value_ = err<E>{std::move(in)};
252 } else if constexpr (std::is_same_v<T, E>) {
253 static_assert(always_false_v<U>,
254 "Since the value and error types are the same, use ok<T> or "
255 "err<E> instead of T or E for the assignment.");
256 } else {
257 static_assert(always_false_v<U>, "Unsupported type for the assignment.");
258 }
259 return *this;
260 }
261
271 [[nodiscard]] constexpr auto is_value() const noexcept -> bool
272 {
273 return std::holds_alternative<ok<T>>(value_);
274 }
275
285 [[nodiscard]] constexpr auto is_error() const noexcept -> bool
286 {
287 return std::holds_alternative<err<E>>(value_);
288 }
289
301 [[nodiscard]] constexpr auto expect(const std::string& msg) const -> value_type
302 {
303 if (is_error()) {
304 throw bad_result_access{msg};
305 }
306 return std::get<ok<T>>(value_).get();
307 }
308
319 [[nodiscard]] constexpr auto unwrap() const -> value_type
320 {
321 return expect("Called `unwrap` on a result that is an error.");
322 }
323
335 [[nodiscard]] constexpr auto expect_error(const std::string& msg) const -> error_type
336 {
337 if (is_value()) {
338 throw bad_result_access{msg};
339 }
340 return std::get<err<E>>(value_).get();
341 }
342
353 [[nodiscard]] constexpr auto unwrap_error() const -> error_type
354 {
355 return expect_error("Called `unwrap_error` on a result that is a value.");
356 }
357
370 [[nodiscard]] constexpr auto unwrap_or(value_type default_value) const noexcept -> value_type
371 {
372 if (is_error()) {
373 return default_value;
374 }
375 return std::get<ok<T>>(value_).get();
376 }
377
389 [[nodiscard]] constexpr auto unwrap_or_default() const noexcept -> value_type
390 {
391 static_assert(std::is_default_constructible_v<value_type>,
392 "The value type must be default constructible.");
393
394 return unwrap_or(value_type{});
395 }
396
410 template <typename F>
411 [[nodiscard]] constexpr auto unwrap_or_else(F&& f) const -> value_type
412 {
413 if (is_error()) {
414 return f(std::get<err<E>>(value_).get());
415 }
416 return std::get<ok<T>>(value_).get();
417 }
418
431 template <typename F>
432 [[nodiscard]] constexpr auto map(F&& f) const
434 {
435 if (is_error()) {
436 return {std::get<err<E>>(value_).get()};
437 }
438 return {ok<std::invoke_result_t<F, value_type>>{f(std::get<ok<T>>(value_).get())}};
439 }
440
453 template <typename F>
454 [[nodiscard]] constexpr auto map_error(F&& f) const
456 {
457 if (is_value()) {
458 return {std::get<ok<T>>(value_).get()};
459 }
460 return {err<std::invoke_result_t<F, error_type>>{f(std::get<err<E>>(value_).get())}};
461 }
462
473 template <typename F>
474 [[nodiscard]] constexpr auto map_or(std::invoke_result_t<F, value_type> default_value,
475 F&& f) const -> std::invoke_result_t<F, value_type>
476 {
477 if (is_error()) {
478 return default_value;
479 }
480 return f(std::get<ok<T>>(value_).get());
481 }
482
496 template <typename F, typename G>
497 [[nodiscard]] constexpr auto map_or_else(G&& default_f, F&& f) const
498 -> std::invoke_result_t<F, value_type>
499 {
500 if (is_error()) {
501 return default_f(std::get<err<E>>(value_).get());
502 }
503 return f(std::get<ok<T>>(value_).get());
504 }
505
517 template <typename U>
518 [[nodiscard]] constexpr auto and_instead(const result<U, error_type>& res) const
520 {
521 if (is_error()) {
522 return {std::get<err<E>>(value_).get()};
523 }
524 return {res};
525 }
526
538 template <typename Op>
539 [[nodiscard]] constexpr auto and_then(Op&& op) const
541 {
542 if (is_error()) {
543 return {std::get<err<E>>(value_).get()};
544 }
545 return {op(std::get<ok<T>>(value_).get())};
546 }
547
559 template <typename F>
560 [[nodiscard]] constexpr auto or_instead(const result<value_type, F>& res) const
562 {
563 if (is_value()) {
564 return {std::get<ok<T>>(value_).get()};
565 }
566 return {res};
567 }
568
580 template <typename Op>
581 [[nodiscard]] constexpr auto or_else(Op&& op) const
583 {
584 if (is_value()) {
585 return {std::get<ok<T>>(value_).get()};
586 }
587 return {op(std::get<err<E>>(value_).get())};
588 }
589
590 [[nodiscard]] constexpr auto operator==(const result& other) const noexcept -> bool
591 {
592 return value_ == other.value_;
593 }
594 [[nodiscard]] constexpr auto operator!=(const result& other) const noexcept -> bool
595 {
596 return !(*this == other);
597 }
598
599 friend constexpr auto std::hash<result>::operator()(const result& r) const noexcept
600 -> std::size_t;
601
602 private:
603 variant_t value_;
604};
605
623template <typename F, typename std::enable_if_t<std::is_invocable_v<F>, bool> = true>
624[[nodiscard]] static auto result_from_try(F&& f) noexcept
625 -> result<std::invoke_result_t<F>, std::exception_ptr>
626{
627 try {
628 return ok<std::invoke_result_t<F>>{std::invoke(std::forward<F>(f))};
629 } catch (...) {
630 return err<std::exception_ptr>{std::current_exception()};
631 }
632}
633
634} // namespace bricks
635
643template <typename T, typename E>
644struct std::hash<bricks::result<T, E>> {
645 [[nodiscard]] constexpr auto operator()(const bricks::result<T, E>& r) const noexcept
646 -> std::size_t
647 {
648 return hash<std::variant<bricks::ok<T>, bricks::err<E>>>{}(r.value_);
649 }
650};
651
656template <typename T, typename tag>
657struct std::hash<bricks::detail::value_container<T, tag>> {
658 [[nodiscard]] constexpr auto operator()(
659 const bricks::detail::value_container<T, tag>& r) const noexcept -> std::size_t
660 {
661 return hash<T>{}(r.get());
662 }
663};
This is the type of the error thrown when accessing a bad result.
Definition result.hpp:24
A class to represent the result of an operation.
Definition result.hpp:92
static auto from_try_or_default(F &&f) noexcept(std::is_nothrow_invocable_v< decltype(result< T, E >::from_try_or< F >)> &&std::is_nothrow_constructible_v< error_type >) -> result
Construct a new result object from an operation that might throw.
Definition result.hpp:190
static auto from_try_or(F &&f, error_type error_value) noexcept(std::is_nothrow_constructible_v< err< E >, error_type >) -> result
Construct a new result object from an operation that might throw.
Definition result.hpp:162
constexpr auto is_value() const noexcept -> bool
Check if the result is a value.
Definition result.hpp:271
T value_type
The value type of the result.
Definition result.hpp:97
constexpr auto or_instead(const result< value_type, F > &res) const -> result< value_type, F >
Returns res if the result is an error, otherwise returns the value.
Definition result.hpp:560
constexpr auto map_or(std::invoke_result_t< F, value_type > default_value, F &&f) const -> std::invoke_result_t< F, value_type >
Returns the provided default (if an error) or applies a function to the contained value.
Definition result.hpp:474
constexpr auto map_error(F &&f) const -> result< value_type, std::invoke_result_t< F, error_type > >
Maps a result<T, E> to result<T, F> by applying a function to a contained error.
Definition result.hpp:454
constexpr auto unwrap() const -> value_type
Returns the value of the result.
Definition result.hpp:319
constexpr auto map(F &&f) const -> result< std::invoke_result_t< F, value_type >, error_type >
Maps a result<T, E> to result<U, E> by applying a function to a contained value.
Definition result.hpp:432
constexpr auto and_then(Op &&op) const -> result< typename std::invoke_result_t< Op, value_type >::value_type, error_type >
Calls op if the result is a value, otherwise returns the error.
Definition result.hpp:539
result()=delete
static auto from_try_or_else(F &&f, OnError &&on_error) noexcept(std::is_nothrow_invocable_v< OnError > &&std::is_nothrow_constructible_v< result< T, E >, std::invoke_result_t< OnError > >) -> result
Construct a new result from an operation that might throw or the result of a function.
Definition result.hpp:215
constexpr auto is_error() const noexcept -> bool
Check if the result is an error.
Definition result.hpp:285
constexpr auto expect_error(const std::string &msg) const -> error_type
Returns the error of the result.
Definition result.hpp:335
constexpr auto expect(const std::string &msg) const -> value_type
Returns the value of the result.
Definition result.hpp:301
constexpr auto or_else(Op &&op) const -> result< value_type, typename std::invoke_result_t< Op, error_type >::error_type >
Calls op if the result is an error, otherwise returns the value.
Definition result.hpp:581
constexpr auto operator==(const result &other) const noexcept -> bool
Definition result.hpp:590
E error_type
The error type of the result.
Definition result.hpp:99
constexpr auto operator!=(const result &other) const noexcept -> bool
Definition result.hpp:594
constexpr auto unwrap_error() const -> error_type
Returns the error of the result.
Definition result.hpp:353
auto operator=(const result &) noexcept(std::is_nothrow_copy_assignable_v< variant_t >) -> result &=default
constexpr auto unwrap_or_else(F &&f) const -> value_type
Returns the value of the result or the result of a function.
Definition result.hpp:411
constexpr auto unwrap_or_default() const noexcept -> value_type
Returns the value of the result or a default constructed value.
Definition result.hpp:389
constexpr result(error_type in) noexcept(std::is_nothrow_move_constructible_v< err< E > >)
Definition result.hpp:142
constexpr auto map_or_else(G &&default_f, F &&f) const -> std::invoke_result_t< F, value_type >
Maps the result<T, E> to U by applying fallback function default_f to a contained error,...
Definition result.hpp:497
constexpr auto unwrap_or(value_type default_value) const noexcept -> value_type
Returns the value of the result.
Definition result.hpp:370
result(result &&) noexcept=default
constexpr auto and_instead(const result< U, error_type > &res) const -> result< U, error_type >
Returns res if the result is a value, otherwise returns the error.
Definition result.hpp:518
constexpr auto operator=(U in) noexcept -> result &
Assign a value to the result.
Definition result.hpp:243
constexpr result(value_type in) noexcept(std::is_nothrow_move_constructible_v< ok< T > >)
Definition result.hpp:135
result(const result &) noexcept(std::is_nothrow_copy_constructible_v< variant_t >)=default
Definition algorithm.hpp:12
detail::value_container< T, detail::ok_tag > ok
Definition result.hpp:74
detail::value_container< E, detail::err_tag > err
Definition result.hpp:77
constexpr bool always_false_v
Helper variable template for static assertions.
Definition type_traits.hpp:34
constexpr auto operator()(const bricks::result< T, E > &r) const noexcept -> std::size_t
Definition result.hpp:645