Files
libconfig/build-config/reflect-cpp/include/rfl/Result.hpp
Emily Boudreaux ec13264050 feat(reflect-cpp): Switched from glaze -> reflect cpp
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
2025-12-06 10:55:46 -05:00

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