draconisplusplus/include/rfl/Result.hpp

339 lines
9.5 KiB
C++
Raw Normal View History

2024-05-31 22:59:00 -04:00
#ifndef RFL_RESULT_HPP_
#define RFL_RESULT_HPP_
#include <array>
#include <iostream>
#include <optional>
#include <ranges>
#include <span>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
#include "internal/is_array.hpp"
#include "internal/to_std_array.hpp"
namespace rfl {
2024-06-08 14:10:59 -04:00
/// To be returned
class Error {
public:
Error(const std::string& _what) : what_(_what) {}
~Error() = default;
/// Returns the error message, equivalent to .what() in std::exception.
const std::string& what() const { return what_; }
private:
/// Documents what went wrong
std::string what_;
};
/// Can be used when we are simply interested in whether an operation was
/// successful.
struct Nothing {};
/// The Result class is used for monadic error handling.
template <class T>
class Result {
2024-06-16 00:13:15 -04:00
static_assert(!std::is_same<T, Error>(), "The result type cannot be Error.");
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
using TOrErr = std::array<unsigned char, std::max(sizeof(T), sizeof(Error))>;
2024-06-08 14:10:59 -04:00
public:
using Type = T;
Result(const T& _val) : success_(true) { new (&get_t()) T(_val); }
2024-06-16 00:13:15 -04:00
Result(T&& _val) noexcept : success_(true) { new (&get_t()) T(std::move(_val)); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
Result(const Error& _err) : success_(false) { new (&get_err()) Error(_err); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
Result(Error&& _err) noexcept : success_(false) { new (&get_err()) Error(std::move(_err)); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
Result(Result<T>&& _other) noexcept : success_(_other.success_) { move_from_other(_other); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
Result(const Result<T>& _other) : success_(_other.success_) { copy_from_other(_other); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
template <class U, typename std::enable_if<std::is_convertible_v<U, T>, bool>::type = true>
2024-06-08 14:10:59 -04:00
Result(Result<U>&& _other) : success_(_other && true) {
2024-06-16 00:13:15 -04:00
auto temp =
std::forward<Result<U>>(_other).transform([](U&& _u) { return T(std::forward<U>(_u)); });
2024-06-08 14:10:59 -04:00
move_from_other(temp);
}
2024-06-16 00:13:15 -04:00
template <class U, typename std::enable_if<std::is_convertible_v<U, T>, bool>::type = true>
2024-06-08 14:10:59 -04:00
Result(const Result<U>& _other) : success_(_other && true) {
auto temp = _other.transform([](const U& _u) { return T(_u); });
move_from_other(temp);
}
~Result() { destroy(); }
/// Returns Result<U>, if successful and error otherwise.
/// Inspired by .and(...) in the Rust std::result type.
template <class U>
Result<U> and_other(const Result<U>& _r) const noexcept {
const auto f = [&](const auto& _) { return _r; };
return and_then(f);
}
/// Monadic operation - F must be a function of type T -> Result<U>.
template <class F>
auto and_then(const F& _f) {
/// Result_U is expected to be of type Result<U>.
using Result_U = typename std::invoke_result<F, T>::type;
if (success_) {
return Result_U(_f(std::forward<T>(get_t())));
} else {
return Result_U(std::forward<Error>(get_err()));
}
}
/// Monadic operation - F must be a function of type T -> Result<U>.
template <class F>
auto and_then(const F& _f) const {
/// Result_U is expected to be of type Result<U>.
using Result_U = typename std::invoke_result<F, T>::type;
if (success_) {
return Result_U(_f(get_t()));
} else {
return Result_U(get_err());
}
}
/// Results types can be iterated over, which even make it possible to use
/// them within a std::range.
T* begin() noexcept {
if (success_) {
return &get_t();
} else {
return nullptr;
}
}
/// Results types can be iterated over, which even make it possible to use
/// them within a std::range.
const T* begin() const noexcept {
if (success_) {
return &get_t();
} else {
return nullptr;
}
}
/// Results types can be iterated over, which even make it possible to use
/// them within a std::range.
T* end() noexcept {
if (success_) {
return &get_t() + 1;
} else {
return nullptr;
}
}
/// Results types can be iterated over, which even make it possible to use
/// them within a std::range.
const T* end() const noexcept {
if (success_) {
return &get_t() + 1;
} else {
return nullptr;
}
}
/// Returns an std::optional<error> if this does in fact contain an error
/// or std::nullopt otherwise.
std::optional<Error> error() const noexcept {
if (success_) {
return std::nullopt;
} else {
return get_err();
}
}
/// Returns true if the result contains a value, false otherwise.
operator bool() const noexcept { return success_; }
/// Allows access to the underlying value. Careful: Will result in undefined
/// behavior, if the result contains an error.
T& operator*() noexcept { return get_t(); }
/// Allows read access to the underlying value. Careful: Will result in
/// undefined behavior, if the result contains an error.
const T& operator*() const noexcept { return get_t(); }
/// Assigns the underlying object.
Result<T>& operator=(const Result<T>& _other) {
2024-06-09 18:55:00 -04:00
if (this == &_other) {
return *this;
}
2024-06-08 14:10:59 -04:00
destroy();
success_ = _other.success_;
copy_from_other(_other);
2024-05-31 22:59:00 -04:00
return *this;
}
2024-06-08 14:10:59 -04:00
/// Assigns the underlying object.
Result<T>& operator=(Result<T>&& _other) noexcept {
2024-06-09 18:55:00 -04:00
if (this == &_other) {
return *this;
}
2024-06-08 14:10:59 -04:00
destroy();
success_ = _other.success_;
move_from_other(_other);
2024-05-31 22:59:00 -04:00
return *this;
}
2024-06-08 14:10:59 -04:00
/// Assigns the underlying object.
2024-06-16 00:13:15 -04:00
template <class U, typename std::enable_if<std::is_convertible_v<U, T>, bool>::type = true>
2024-06-08 14:10:59 -04:00
auto& operator=(const Result<U>& _other) {
const auto to_t = [](const U& _u) -> T { return _u; };
t_or_err_ = _other.transform(to_t).t_or_err_;
return *this;
}
/// Expects a function that takes of type Error -> Result<T> and returns
/// Result<T>.
template <class F>
Result<T> or_else(const F& _f) {
if (success_) {
return std::forward<T>(get_t());
} else {
return _f(std::forward<Error>(get_err()));
}
}
/// Expects a function that takes of type Error -> Result<T> and returns
/// Result<T>.
template <class F>
Result<T> or_else(const F& _f) const {
if (success_) {
return get_t();
} else {
return _f(get_err());
2024-05-31 22:59:00 -04:00
}
}
2024-06-08 14:10:59 -04:00
/// Returns the value contained if successful or the provided result r if
/// not.
Result<T> or_other(const Result<T>& _r) const noexcept {
const auto f = [&](const auto& _) { return _r; };
return or_else(f);
}
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
/// Functor operation - F must be a function of type T -> U.
template <class F>
auto transform(const F& _f) {
/// Result_U is expected to be of type Result<U>.
using U = typename std::invoke_result<F, T>::type;
if (success_) {
return rfl::Result<U>(_f(std::forward<T>(get_t())));
} else {
return rfl::Result<U>(std::forward<Error>(get_err()));
}
}
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
/// Functor operation - F must be a function of type T -> U.
template <class F>
auto transform(const F& _f) const {
/// Result_U is expected to be of type Result<U>.
using U = typename std::invoke_result<F, T>::type;
if (success_) {
return rfl::Result<U>(_f(get_t()));
} else {
return rfl::Result<U>(get_err());
}
}
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
/// Returns the value if the result does not contain an error, throws an
/// exceptions if not. Similar to .unwrap() in Rust.
T& value() {
if (success_) {
return get_t();
} else {
throw std::runtime_error(get_err().what());
}
}
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
/// Returns the value if the result does not contain an error, throws an
/// exceptions if not. Similar to .unwrap() in Rust.
const T& value() const {
if (success_) {
return get_t();
} else {
throw std::runtime_error(get_err().what());
}
}
/// Returns the value or a default.
T value_or(T&& _default) noexcept {
if (success_) {
return std::forward<T>(get_t());
} else {
return std::forward<T>(_default);
}
}
/// Returns the value or a default.
T value_or(const T& _default) const noexcept {
if (success_) {
return get_t();
} else {
return _default;
}
}
private:
void copy_from_other(const Result<T>& _other) {
if (success_) {
new (&get_t()) T(_other.get_t());
} else {
new (&get_err()) Error(_other.get_err());
}
}
void destroy() {
if (success_) {
2024-06-16 00:13:15 -04:00
if constexpr (std::is_destructible_v<T> /*&& !internal::is_array_v<T>*/) {
2024-06-08 14:10:59 -04:00
get_t().~T();
}
} else {
get_err().~Error();
}
}
T& get_t() noexcept { return *(reinterpret_cast<T*>(t_or_err_.data())); }
2024-06-16 00:13:15 -04:00
const T& get_t() const noexcept { return *(reinterpret_cast<const T*>(t_or_err_.data())); }
2024-06-08 14:10:59 -04:00
2024-06-16 00:13:15 -04:00
Error& get_err() noexcept { return *(reinterpret_cast<Error*>(t_or_err_.data())); }
2024-06-08 14:10:59 -04:00
const Error& get_err() const noexcept {
return *(reinterpret_cast<const Error*>(t_or_err_.data()));
}
void move_from_other(Result<T>& _other) {
if (success_) {
new (&get_t()) T(std::move(_other.get_t()));
} else {
new (&get_err()) Error(std::move(_other.get_err()));
}
2024-05-31 22:59:00 -04:00
}
2024-06-08 14:10:59 -04:00
private:
/// Signifies whether this was a success.
bool success_;
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
/// The underlying data, can either be T or Error.
alignas(std::max(alignof(T), alignof(Error))) TOrErr t_or_err_;
};
2024-05-31 22:59:00 -04:00
2024-06-08 14:10:59 -04:00
} // namespace rfl
2024-05-31 22:59:00 -04:00
#endif