musique/musique/result.hh
2022-09-23 18:55:25 +02:00

89 lines
2.2 KiB
C++

#ifndef MUSIQUE_RESULT_HH
#define MUSIQUE_RESULT_HH
#include <tl/expected.hpp>
#include <musique/errors.hh>
/// Holds either T or Error
template<typename T>
struct [[nodiscard("This value may contain critical error, so it should NOT be ignored")]] Result : tl::expected<T, Error>
{
using Storage = tl::expected<T, Error>;
constexpr Result() = default;
template<typename ...Args> requires (not std::is_void_v<T>) && std::is_constructible_v<T, Args...>
constexpr Result(Args&& ...args)
: Storage( T{ std::forward<Args>(args)... } )
{
}
template<typename Arg> requires std::is_constructible_v<Storage, Arg>
constexpr Result(Arg &&arg)
: Storage(std::forward<Arg>(arg))
{
}
inline Result(Error error)
: Storage(tl::unexpected(std::move(error)))
{
}
template<typename Arg>
requires requires (Arg a) {
{ Error { .details = std::move(a) } };
}
inline Result(Arg a)
: Storage(tl::unexpected(Error { .details = std::move(a) }))
{
}
// Internal function used for definition of Try macro
inline auto value() &&
{
if constexpr (not std::is_void_v<T>) {
// NOTE This line in ideal world should be `return Storage::value()`
// but C++ does not infer that this is rvalue context.
// `std::add_rvalue_reference_t<Storage>::value()`
// also does not work, so this is probably the best way to express this:
return std::move(*static_cast<Storage*>(this)).value();
}
}
/// Fill error location if it's empty and we have an error
inline Result<T> with_location(Location location) &&
{
if (!Storage::has_value()) {
if (auto& target = Storage::error().location; !target || target == Location{}) {
target = location;
}
}
return *this;
}
inline tl::expected<T, Error> to_expected() &&
{
return *static_cast<Storage*>(this);
}
template<typename Map>
requires is_template_v<Result, std::invoke_result_t<Map, T&&>>
auto and_then(Map &&map) &&
{
return std::move(*static_cast<Storage*>(this)).and_then(
[map = std::forward<Map>(map)](T &&value) {
return std::move(map)(std::move(value)).to_expected();
});
}
using Storage::and_then;
operator std::optional<Error>() &&
{
return Storage::has_value() ? std::nullopt : std::optional(Storage::error());
}
};
#endif