A bug was discovered in glaze which prevented valid toml output. We have switched to toml++ and reflect-cpp. The interface has remained the same so this should not break any code
448 lines
12 KiB
C++
448 lines
12 KiB
C++
#ifndef RFL_RESULT_HPP_
|
|
#define RFL_RESULT_HPP_
|
|
|
|
#ifdef REFLECTCPP_USE_STD_EXPECTED
|
|
#include <expected>
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <functional>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
namespace rfl {
|
|
|
|
/// Defines the error class to be returned when something went wrong
|
|
class Error {
|
|
public:
|
|
Error(const std::string& _what) : what_(_what) {}
|
|
Error(std::string&& _what) : what_(std::move(_what)) {}
|
|
|
|
Error(const Error& e) = default;
|
|
Error(Error&& e) = default;
|
|
|
|
Error& operator=(const Error&) = default;
|
|
Error& operator=(Error&&) = default;
|
|
|
|
/// Returns the error message, equivalent to .what() in std::exception.
|
|
const std::string& what() const & { return what_; }
|
|
/// Moves the error message out of Error object and leaves what_ in a moved from state
|
|
std::string what() && { return std::move(what_); }
|
|
|
|
private:
|
|
/// Documents what went wrong
|
|
std::string what_;
|
|
};
|
|
|
|
/// To be returned when there is nothing to return, but there might be an error.
|
|
struct Nothing {};
|
|
|
|
/// This implementation is for cases where std::expected is defined
|
|
#ifdef REFLECTCPP_USE_STD_EXPECTED
|
|
|
|
template <class E>
|
|
using Unexpected = std::unexpected<E>;
|
|
|
|
template <class T>
|
|
using Result = std::expected<T, rfl::Error>;
|
|
|
|
/// This implementation is for cases where std::expected is not defined
|
|
#else // REFLECTCPP_USE_STD_EXPECTED
|
|
|
|
template <class E>
|
|
struct Unexpected {
|
|
Unexpected(E&& _err) : err_{std::forward<E>(_err)} {}
|
|
|
|
Unexpected(const E& _err) : err_{_err} {}
|
|
|
|
Unexpected(Unexpected&&) = default;
|
|
|
|
Unexpected(const Unexpected&) = default;
|
|
|
|
Unexpected& operator=(Unexpected&&) = default;
|
|
|
|
Unexpected& operator=(const Unexpected&) = default;
|
|
|
|
const E& error() const& { return err_; }
|
|
|
|
E&& error() && { return std::move(err_); }
|
|
|
|
E& error() & { return err_; }
|
|
|
|
private:
|
|
E err_;
|
|
};
|
|
|
|
/// The Result class is used for monadic error handling.
|
|
template <class T>
|
|
class Result {
|
|
static_assert(!std::is_same<T, Error>(), "The result type cannot be Error.");
|
|
|
|
using TOrErr = std::array<unsigned char, std::max(sizeof(T), sizeof(Error))>;
|
|
|
|
public:
|
|
// using Type = T;
|
|
using value_type = T;
|
|
using error_type = rfl::Error;
|
|
|
|
Result(const T& _val) : success_(true) { new (&get_t()) T(_val); }
|
|
|
|
Result(T&& _val) noexcept : success_(true) {
|
|
new (&get_t()) T(std::move(_val));
|
|
}
|
|
|
|
Result(const Unexpected<Error>& _err) : success_(false) {
|
|
new (&get_err()) Error(_err.error());
|
|
}
|
|
Result(Unexpected<Error>&& _err) : success_(false) {
|
|
new (&get_err()) Error(std::move(_err.error()));
|
|
}
|
|
|
|
Result(Result<T>&& _other) noexcept : success_(_other.success_) {
|
|
move_from_other(_other);
|
|
}
|
|
|
|
Result(const Result<T>& _other) : success_(_other.success_) {
|
|
copy_from_other(_other);
|
|
}
|
|
|
|
template <class U, typename std::enable_if<std::is_convertible_v<U, T>,
|
|
bool>::type = true>
|
|
Result(Result<U>&& _other) : success_(_other && true) {
|
|
auto temp = std::forward<Result<U> >(_other).transform(
|
|
[](U&& _u) { return T(std::forward<U>(_u)); });
|
|
move_from_other(temp);
|
|
}
|
|
|
|
template <class U, typename std::enable_if<std::is_convertible_v<U, T>,
|
|
bool>::type = true>
|
|
Result(const Result<U>& _other) : success_(_other && true) {
|
|
auto temp = _other.transform([](const U& _u) { return T(_u); });
|
|
move_from_other(temp);
|
|
}
|
|
|
|
~Result() { destroy(); }
|
|
|
|
/// 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::move(*this).get_t()));
|
|
} else {
|
|
return Result_U(std::move(*this).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());
|
|
}
|
|
}
|
|
|
|
/// 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 std::move(*this).get_t(); }
|
|
|
|
/// 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) {
|
|
if (this == &_other) {
|
|
return *this;
|
|
}
|
|
destroy();
|
|
success_ = _other.success_;
|
|
copy_from_other(_other);
|
|
return *this;
|
|
}
|
|
|
|
/// Assigns the underlying object.
|
|
Result<T>& operator=(Result<T>&& _other) noexcept {
|
|
if (this == &_other) {
|
|
return *this;
|
|
}
|
|
destroy();
|
|
success_ = _other.success_;
|
|
move_from_other(_other);
|
|
return *this;
|
|
}
|
|
|
|
Result<T>& operator=(Unexpected<Error>&& _err) noexcept {
|
|
destroy();
|
|
success_ = false;
|
|
new (&get_err()) Error(_err.error());
|
|
return *this;
|
|
}
|
|
|
|
Result<T>& operator=(const Unexpected<Error>& _err) noexcept {
|
|
destroy();
|
|
success_ = false;
|
|
new (&get_err()) Error(_err.error());
|
|
return *this;
|
|
}
|
|
|
|
/// Assigns the underlying object.
|
|
template <class U, typename std::enable_if<std::is_convertible_v<U, T>,
|
|
bool>::type = true>
|
|
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::move(*this).get_t();
|
|
} else {
|
|
return _f(std::move(*this).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());
|
|
}
|
|
}
|
|
|
|
/// 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 = std::invoke_result_t<F, T>;
|
|
if (success_) {
|
|
return rfl::Result<U>(_f(std::move(*this).get_t()));
|
|
} else {
|
|
return rfl::Result<U>(rfl::Unexpected(std::move(*this).get_err()));
|
|
}
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
}
|
|
|
|
/// 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 std::move(*this).get_t();
|
|
} else {
|
|
throw std::runtime_error(get_err().what());
|
|
}
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
}
|
|
|
|
/// 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::move(*this).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;
|
|
}
|
|
}
|
|
|
|
template <class G = rfl::Error>
|
|
rfl::Error error_or(G&& _default) && {
|
|
if (success_) {
|
|
return std::forward<G>(_default);
|
|
} else {
|
|
return std::move(*this).get_err();
|
|
}
|
|
}
|
|
|
|
// As specified by the standard :
|
|
// https://en.cppreference.com/w/cpp/utility/expected
|
|
// Observers
|
|
template <class G = rfl::Error>
|
|
rfl::Error error_or(G&& _default) const& {
|
|
if (success_) {
|
|
return std::forward<G>(_default);
|
|
} else {
|
|
return get_err();
|
|
}
|
|
}
|
|
|
|
bool has_value() const noexcept { return success_; }
|
|
|
|
Error& error() && {
|
|
if (success_) throw std::runtime_error("Expected does not contain value");
|
|
return std::move(*this).get_err();
|
|
}
|
|
|
|
Error& error() & {
|
|
if (success_) throw std::runtime_error("Expected does not contain value");
|
|
return get_err();
|
|
}
|
|
|
|
const Error& error() const& {
|
|
if (success_) throw std::runtime_error("Expected does not contain value");
|
|
return get_err();
|
|
}
|
|
|
|
T* operator->() noexcept { return &get_t(); }
|
|
|
|
const T* operator->() const noexcept { return &get_t(); }
|
|
|
|
template <class F>
|
|
rfl::Result<T> transform_error(F&& f) && {
|
|
static_assert(
|
|
std::is_same<std::invoke_result_t<F, rfl::Error>, rfl::Error>(),
|
|
"A function passed to transform_error must return an error.");
|
|
if (!has_value()) {
|
|
return rfl::Result<T>{std::invoke(f, std::move(*this).get_err())};
|
|
} else {
|
|
return rfl::Result<T>{std::move(*this).value()};
|
|
}
|
|
}
|
|
|
|
template <class F>
|
|
rfl::Result<T> transform_error(F&& f) const& {
|
|
static_assert(
|
|
std::is_same<std::invoke_result_t<F, rfl::Error>, rfl::Error>(),
|
|
"A function passed to transform_error must return an error.");
|
|
if (!has_value()) {
|
|
return rfl::Result<T>{std::invoke(f, get_err())};
|
|
} else {
|
|
return rfl::Result<T>{value()};
|
|
}
|
|
}
|
|
|
|
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_) {
|
|
if constexpr (std::is_destructible_v<std::remove_cv_t<T> >) {
|
|
get_t().~T();
|
|
}
|
|
} else {
|
|
get_err().~Error();
|
|
}
|
|
}
|
|
|
|
T&& get_t() && noexcept {
|
|
return std::move(*std::launder(reinterpret_cast<T*>(t_or_err_.data())));
|
|
}
|
|
|
|
T& get_t() & noexcept {
|
|
return *std::launder(reinterpret_cast<T*>(t_or_err_.data()));
|
|
}
|
|
|
|
const T& get_t() const& noexcept {
|
|
return *std::launder(reinterpret_cast<const T*>(t_or_err_.data()));
|
|
}
|
|
|
|
Error&& get_err() && noexcept {
|
|
return std::move(*std::launder(reinterpret_cast<Error*>(t_or_err_.data())));
|
|
}
|
|
|
|
Error& get_err() & noexcept {
|
|
return *std::launder(reinterpret_cast<Error*>(t_or_err_.data()));
|
|
}
|
|
|
|
const Error& get_err() const& noexcept {
|
|
return *std::launder(reinterpret_cast<const Error*>(t_or_err_.data()));
|
|
}
|
|
|
|
void move_from_other(Result<T>& _other) noexcept {
|
|
if (success_) {
|
|
new (&get_t()) T(std::move(_other.get_t()));
|
|
} else {
|
|
new (&get_err()) Error(std::move(_other.get_err()));
|
|
}
|
|
}
|
|
/// Signifies whether this was a success.
|
|
bool success_;
|
|
|
|
/// The underlying data, can either be T or Error.
|
|
alignas(std::max(alignof(T), alignof(Error))) TOrErr t_or_err_;
|
|
};
|
|
|
|
#endif
|
|
|
|
/// Shorthand for unexpected error.
|
|
inline Unexpected<Error> error(const std::string& _what) {
|
|
return Unexpected<Error>(Error(_what));
|
|
}
|
|
|
|
inline Unexpected<Error> error(std::string&& _what) {
|
|
return Unexpected<Error>(Error(std::move(_what)));
|
|
}
|
|
|
|
/// Shorthand for unexpected error.
|
|
inline Unexpected<Error> error(const Error& _err) {
|
|
return Unexpected<Error>(_err);
|
|
}
|
|
|
|
} // namespace rfl
|
|
|
|
#endif
|