diff options
Diffstat (limited to '')
-rw-r--r-- | src/base/result.h | 1032 |
1 files changed, 1032 insertions, 0 deletions
diff --git a/src/base/result.h b/src/base/result.h new file mode 100644 index 0000000..e3e8ac5 --- /dev/null +++ b/src/base/result.h @@ -0,0 +1,1032 @@ +/* + Mathieu Stefani, 03 mai 2016 + + This header provides a Result type that can be used to replace exceptions in + code that has to handle error. + + Result<T, E> can be used to return and propagate an error to the caller. + Result<T, E> is an algebraic data type that can either Ok(T) to represent + success or Err(E) to represent an error. +*/ + +#pragma once + +#include <exception> +#include <functional> +#include <type_traits> + +#include <stdio.h> + +namespace types { +template<typename T> +struct Ok { + Ok(const T& val) : val(val) {} + Ok(T&& val) : val(std::move(val)) {} + + T val; +}; + +template<> +struct Ok<void> {}; + +template<typename E> +struct Err { + Err(const E& val) : val(val) {} + Err(E&& val) : val(std::move(val)) {} + + E val; +}; + +template<> +struct Err<void> {}; +}; // namespace types + +template<typename T, typename CleanT = typename std::decay<T>::type> +types::Ok<CleanT> +Ok(T&& val) +{ + return types::Ok<CleanT>(std::forward<T>(val)); +} + +inline types::Ok<void> +Ok() +{ + return {}; +} + +template<typename E, typename CleanE = typename std::decay<E>::type> +types::Err<CleanE> +Err(E&& val) +{ + return types::Err<CleanE>(std::forward<E>(val)); +} + +inline types::Err<void> +Err() +{ + return {}; +} + +template<typename T, typename E> +struct Result; + +namespace details { + +template<typename...> +struct void_t { + typedef void type; +}; + +namespace impl { +template<typename Func> +struct result_of; + +template<typename Ret, typename Cls, typename... Args> +struct result_of<Ret (Cls::*)(Args...)> : public result_of<Ret(Args...)> {}; + +template<typename Ret, typename... Args> +struct result_of<std::function<Ret(Args...)>> { + typedef Ret type; +}; +} // namespace impl + +template<typename Func> +struct result_of : public impl::result_of<decltype(&Func::operator())> {}; + +template<typename Ret, typename Cls, typename... Args> +struct result_of<Ret (Cls::*)(Args...) const> { + typedef Ret type; +}; + +template<typename Ret, typename... Args> +struct result_of<Ret (*)(Args...)> { + typedef Ret type; +}; + +template<typename R> +struct ResultOkType { + typedef typename std::decay<R>::type type; +}; + +template<typename T, typename E> +struct ResultOkType<Result<T, E>> { + typedef T type; +}; + +template<typename R> +struct ResultErrType { + typedef R type; +}; + +template<typename T, typename E> +struct ResultErrType<Result<T, E>> { + typedef typename std::remove_reference<E>::type type; +}; + +template<typename R> +struct IsResult : public std::false_type {}; +template<typename T, typename E> +struct IsResult<Result<T, E>> : public std::true_type {}; + +namespace ok { + +namespace impl { + +template<typename T> +struct Map; + +template<typename Ret, typename Cls, typename Arg> +struct Map<Ret (Cls::*)(Arg) const> : public Map<Ret(Arg)> {}; + +template<typename Ret, typename Cls, typename Arg> +struct Map<Ret (Cls::*)(Arg)> : public Map<Ret(Arg)> {}; + +// General implementation +template<typename Ret, typename Arg> +struct Map<Ret(Arg)> { + static_assert( + !IsResult<Ret>::value, + "Can not map a callback returning a Result, use then instead"); + + template<typename T, typename E, typename Func> + static Result<Ret, E> map(const Result<T, E>& result, Func func) + { + static_assert( + std::is_same<T, Arg>::value || std::is_convertible<T, Arg>::value, + "Incompatible types detected"); + + if (result.isOk()) { + auto res = func(result.storage().template get<T>()); + return types::Ok<Ret>(std::move(res)); + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +// Specialization for callback returning void +template<typename Arg> +struct Map<void(Arg)> { + template<typename T, typename E, typename Func> + static Result<void, E> map(const Result<T, E>& result, Func func) + { + if (result.isOk()) { + func(result.storage().template get<T>()); + return types::Ok<void>(); + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +// Specialization for a void Result +template<typename Ret> +struct Map<Ret(void)> { + template<typename T, typename E, typename Func> + static Result<Ret, E> map(const Result<T, E>& result, Func func) + { + static_assert(std::is_same<T, void>::value, + "Can not map a void callback on a non-void Result"); + + if (result.isOk()) { + auto ret = func(); + return types::Ok<Ret>(std::move(ret)); + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +// Specialization for callback returning void on a void Result +template<> +struct Map<void(void)> { + template<typename T, typename E, typename Func> + static Result<void, E> map(const Result<T, E>& result, Func func) + { + static_assert(std::is_same<T, void>::value, + "Can not map a void callback on a non-void Result"); + + if (result.isOk()) { + func(); + return types::Ok<void>(); + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +// General specialization for a callback returning a Result +template<typename U, typename E, typename Arg> +struct Map<Result<U, E>(Arg)> { + template<typename T, typename Func> + static Result<U, E> map(const Result<T, E>& result, Func func) + { + static_assert( + std::is_same<T, Arg>::value || std::is_convertible<T, Arg>::value, + "Incompatible types detected"); + + if (result.isOk()) { + auto res = func(result.storage().template get<T>()); + return res; + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +// Specialization for a void callback returning a Result +template<typename U, typename E> +struct Map<Result<U, E>(void)> { + template<typename T, typename Func> + static Result<U, E> map(const Result<T, E>& result, Func func) + { + static_assert(std::is_same<T, void>::value, + "Can not call a void-callback on a non-void Result"); + + if (result.isOk()) { + auto res = func(); + return res; + } + + return types::Err<E>(result.storage().template get<E>()); + } +}; + +} // namespace impl + +template<typename Func> +struct Map; + +template<typename Ret, typename... Args> +struct Map<Ret (*)(Args...)> : public impl::Map<Ret(Args...)> {}; + +template<typename Ret, typename... Args> +struct Map<Ret(Args...)> : public impl::Map<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Map<Ret (Cls::*)(Args...)> : public impl::Map<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Map<Ret (Cls::*)(Args...) const> : public impl::Map<Ret(Args...)> {}; + +template<typename Ret, typename... Args> +struct Map<std::function<Ret(Args...)>> : public impl::Map<Ret(Args...)> {}; + +} // namespace ok + +namespace err { + +namespace impl { + +template<typename T> +struct Map; + +template<typename Ret, typename Cls, typename Arg> +struct Map<Ret (Cls::*)(Arg) const> { + static_assert( + !IsResult<Ret>::value, + "Can not map a callback returning a Result, use orElse instead"); + + template<typename T, typename E, typename Func> + static Result<T, Ret> map(const Result<T, E>& result, Func func) + { + if (result.isErr()) { + auto res = func(result.storage().template get<E>()); + return types::Err<Ret>(res); + } + + return types::Ok<T>(result.storage().template get<T>()); + } + + template<typename E, typename Func> + static Result<void, Ret> map(const Result<void, E>& result, Func func) + { + if (result.isErr()) { + auto res = func(result.storage().template get<E>()); + return types::Err<Ret>(res); + } + + return types::Ok<void>(); + } +}; + +} // namespace impl + +template<typename Func> +struct Map : public impl::Map<decltype(&Func::operator())> {}; + +} // namespace err + +namespace And { + +namespace impl { + +template<typename Func> +struct Then; + +template<typename Ret, typename... Args> +struct Then<Ret (*)(Args...)> : public Then<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Then<Ret (Cls::*)(Args...)> : public Then<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Then<Ret (Cls::*)(Args...) const> : public Then<Ret(Args...)> {}; + +template<typename Ret, typename Arg> +struct Then<Ret(Arg)> { + static_assert(std::is_same<Ret, void>::value, + "then() should not return anything, use map() instead"); + + template<typename T, typename E, typename Func> + static Result<T, E> then(const Result<T, E>& result, Func func) + { + if (result.isOk()) { + func(result.storage().template get<T>()); + } + return result; + } +}; + +template<typename Ret> +struct Then<Ret(void)> { + static_assert(std::is_same<Ret, void>::value, + "then() should not return anything, use map() instead"); + + template<typename T, typename E, typename Func> + static Result<T, E> then(const Result<T, E>& result, Func func) + { + static_assert(std::is_same<T, void>::value, + "Can not call a void-callback on a non-void Result"); + + if (result.isOk()) { + func(); + } + + return result; + } +}; + +} // namespace impl + +template<typename Func> +struct Then : public impl::Then<decltype(&Func::operator())> {}; + +template<typename Ret, typename... Args> +struct Then<Ret (*)(Args...)> : public impl::Then<Ret(Args...)> {}; + +template<typename Ret, typename Arg> +struct Then<Ret(Arg)> : public impl::Then<Ret(Arg)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Then<Ret (Cls::*)(Args...)> : public impl::Then<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Then<Ret (Cls::*)(Args...) const> : public impl::Then<Ret(Args...)> {}; + +template<typename Ret, typename... Args> +struct Then<std::function<Ret(Args...)>> : public impl::Then<Ret(Args...)> {}; + +} // namespace And + +namespace Or { + +namespace impl { + +template<typename Func> +struct Else; + +template<typename Ret, typename... Args> +struct Else<Ret (*)(Args...)> : public Else<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Else<Ret (Cls::*)(Args...)> : public Else<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Else<Ret (Cls::*)(Args...) const> : public Else<Ret(Args...)> {}; + +template<typename T, typename F, typename Arg> +struct Else<Result<T, F>(Arg)> { + template<typename E, typename Func> + static Result<T, F> orElse(const Result<T, E>& result, Func func) + { + static_assert( + std::is_same<E, Arg>::value || std::is_convertible<E, Arg>::value, + "Incompatible types detected"); + + if (result.isErr()) { + auto res = func(result.storage().template get<E>()); + return res; + } + + return types::Ok<T>(result.storage().template get<T>()); + } + + template<typename E, typename Func> + static Result<void, F> orElse(const Result<void, E>& result, Func func) + { + if (result.isErr()) { + auto res = func(result.storage().template get<E>()); + return res; + } + + return types::Ok<void>(); + } +}; + +template<typename T, typename F> +struct Else<Result<T, F>(void)> { + template<typename E, typename Func> + static Result<T, F> orElse(const Result<T, E>& result, Func func) + { + static_assert(std::is_same<T, void>::value, + "Can not call a void-callback on a non-void Result"); + + if (result.isErr()) { + auto res = func(); + return res; + } + + return types::Ok<T>(result.storage().template get<T>()); + } + + template<typename E, typename Func> + static Result<void, F> orElse(const Result<void, E>& result, Func func) + { + if (result.isErr()) { + auto res = func(); + return res; + } + + return types::Ok<void>(); + } +}; + +} // namespace impl + +template<typename Func> +struct Else : public impl::Else<decltype(&Func::operator())> {}; + +template<typename Ret, typename... Args> +struct Else<Ret (*)(Args...)> : public impl::Else<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Else<Ret (Cls::*)(Args...)> : public impl::Else<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Else<Ret (Cls::*)(Args...) const> : public impl::Else<Ret(Args...)> {}; + +} // namespace Or + +namespace Other { + +namespace impl { + +template<typename Func> +struct Wise; + +template<typename Ret, typename... Args> +struct Wise<Ret (*)(Args...)> : public Wise<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Wise<Ret (Cls::*)(Args...)> : public Wise<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Wise<Ret (Cls::*)(Args...) const> : public Wise<Ret(Args...)> {}; + +template<typename Ret, typename Arg> +struct Wise<Ret(Arg)> { + template<typename T, typename E, typename Func> + static Result<T, E> otherwise(const Result<T, E>& result, Func func) + { + static_assert( + std::is_same<E, Arg>::value || std::is_convertible<E, Arg>::value, + "Incompatible types detected"); + + static_assert( + std::is_same<Ret, void>::value, + "callback should not return anything, use mapError() for that"); + + if (result.isErr()) { + func(result.storage().template get<E>()); + } + return result; + } +}; + +} // namespace impl + +template<typename Func> +struct Wise : public impl::Wise<decltype(&Func::operator())> {}; + +template<typename Ret, typename... Args> +struct Wise<Ret (*)(Args...)> : public impl::Wise<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Wise<Ret (Cls::*)(Args...)> : public impl::Wise<Ret(Args...)> {}; + +template<typename Ret, typename Cls, typename... Args> +struct Wise<Ret (Cls::*)(Args...) const> : public impl::Wise<Ret(Args...)> {}; + +} // namespace Other + +template<typename T, typename E, typename Func> +decltype(auto) +map(const Result<T, E>& result, Func func) +{ + return ok::Map<Func>::map(result, func); +} + +template<typename T, + typename E, + typename Func, + typename Ret + = Result<T, + typename details::ResultErrType< + typename details::result_of<Func>::type>::type>> +Ret +mapError(const Result<T, E>& result, Func func) +{ + return err::Map<Func>::map(result, func); +} + +template<typename T, typename E, typename Func> +Result<T, E> +then(const Result<T, E>& result, Func func) +{ + return And::Then<Func>::then(result, func); +} + +template<typename T, typename E, typename Func> +Result<T, E> +otherwise(const Result<T, E>& result, Func func) +{ + return Other::Wise<Func>::otherwise(result, func); +} + +template<typename T, + typename E, + typename Func, + typename Ret + = Result<T, + typename details::ResultErrType< + typename details::result_of<Func>::type>::type>> +Ret +orElse(const Result<T, E>& result, Func func) +{ + return Or::Else<Func>::orElse(result, func); +} + +struct ok_tag {}; +struct err_tag {}; + +template<typename T, typename E> +struct Storage { + static constexpr size_t Size = sizeof(T) > sizeof(E) ? sizeof(T) + : sizeof(E); + static constexpr size_t Align = sizeof(T) > sizeof(E) ? alignof(T) + : alignof(E); + + typedef typename std::aligned_storage<Size, Align>::type type; + + Storage() : initialized_(false) {} + + void construct(types::Ok<T> ok) + { + new (&storage_) T(std::move(ok.val)); + initialized_ = true; + } + void construct(types::Err<E> err) + { + new (&storage_) E(err.val); + initialized_ = true; + } + + template<typename U> + void rawConstruct(U&& val) + { + typedef typename std::decay<U>::type CleanU; + + new (&storage_) CleanU(std::forward<U>(val)); + initialized_ = true; + } + + template<typename U> + const U& get() const + { + return *reinterpret_cast<const U*>(&storage_); + } + + template<typename U> + U& get() + { + return *reinterpret_cast<U*>(&storage_); + } + + void destroy(ok_tag) + { + if (initialized_) { + get<T>().~T(); + initialized_ = false; + } + } + + void destroy(err_tag) + { + if (initialized_) { + get<E>().~E(); + initialized_ = false; + } + } + + type storage_; + bool initialized_; +}; + +template<typename E> +struct Storage<void, E> { + typedef typename std::aligned_storage<sizeof(E), alignof(E)>::type type; + + void construct(types::Ok<void>) { initialized_ = true; } + + void construct(types::Err<E> err) + { + new (&storage_) E(err.val); + initialized_ = true; + } + + template<typename U> + void rawConstruct(U&& val) + { + typedef typename std::decay<U>::type CleanU; + + new (&storage_) CleanU(std::forward<U>(val)); + initialized_ = true; + } + + void destroy(ok_tag) { initialized_ = false; } + void destroy(err_tag) + { + if (initialized_) { + get<E>().~E(); + initialized_ = false; + } + } + + template<typename U, + typename = std::enable_if_t<!std::is_same<U, void>::value>> + const U& get() const + { + return *reinterpret_cast<const U*>(&storage_); + } + + template<typename U, + typename = std::enable_if_t<!std::is_same<U, void>::value>> + typename std::add_lvalue_reference<U>::type get() + { + return *reinterpret_cast<U*>(&storage_); + } + + template<typename U, + typename = std::enable_if_t<std::is_same<U, void>::value>> + void get() + { + } + + type storage_; + bool initialized_; +}; + +template<typename T, typename E> +struct Constructor { + static void move(Storage<T, E>&& src, Storage<T, E>& dst, ok_tag) + { + dst.rawConstruct(std::move(src.template get<T>())); + src.destroy(ok_tag()); + } + + static void copy(const Storage<T, E>& src, Storage<T, E>& dst, ok_tag) + { + dst.rawConstruct(src.template get<T>()); + } + + static void move(Storage<T, E>&& src, Storage<T, E>& dst, err_tag) + { + dst.rawConstruct(std::move(src.template get<E>())); + src.destroy(err_tag()); + } + + static void copy(const Storage<T, E>& src, Storage<T, E>& dst, err_tag) + { + dst.rawConstruct(src.template get<E>()); + } +}; + +template<typename E> +struct Constructor<void, E> { + static void move(Storage<void, E>&& src, Storage<void, E>& dst, ok_tag) {} + + static void copy(const Storage<void, E>& src, Storage<void, E>& dst, ok_tag) + { + } + + static void move(Storage<void, E>&& src, Storage<void, E>& dst, err_tag) + { + dst.rawConstruct(std::move(src.template get<E>())); + src.destroy(err_tag()); + } + + static void copy(const Storage<void, E>& src, + Storage<void, E>& dst, + err_tag) + { + dst.rawConstruct(src.template get<E>()); + } +}; + +} // namespace details + +namespace local_concept { + +template<typename T, typename = void> +struct EqualityComparable : std::false_type {}; + +template<typename T> +struct EqualityComparable< + T, + typename std::enable_if< + true, + typename details::void_t<decltype(std::declval<T>() + == std::declval<T>())>::type>::type> + : std::true_type {}; + +} // namespace local_concept + +template<typename T, typename E> +struct Result { + static_assert(!std::is_same<E, void>::value, + "void error type is not allowed"); + + typedef details::Storage<T, E> storage_type; + + Result(types::Ok<T> ok) : ok_(true) { storage_.construct(std::move(ok)); } + + Result(types::Err<E> err) : ok_(false) + { + storage_.construct(std::move(err)); + } + + Result(Result&& other) + { + if (other.isOk()) { + details::Constructor<T, E>::move( + std::move(other.storage_), storage_, details::ok_tag()); + ok_ = true; + } else { + details::Constructor<T, E>::move( + std::move(other.storage_), storage_, details::err_tag()); + ok_ = false; + } + } + + Result(const Result& other) + { + if (other.isOk()) { + details::Constructor<T, E>::copy( + other.storage_, storage_, details::ok_tag()); + ok_ = true; + } else { + details::Constructor<T, E>::copy( + other.storage_, storage_, details::err_tag()); + ok_ = false; + } + } + + ~Result() + { + if (ok_) + storage_.destroy(details::ok_tag()); + else + storage_.destroy(details::err_tag()); + } + + bool isOk() const { return ok_; } + + bool isErr() const { return !ok_; } + + T expect(const char* str) + { + if (!isOk()) { + ::fprintf(stderr, "%s\n", str); + abort(); + } + return expect_impl(std::is_same<T, void>()); + } + + template<typename Func> + auto map(Func func) + { + using return_type = decltype(func(T{})); + + if (this->isOk()) { + auto value = std::move(this->storage().template get<T>()); + auto res = func(std::move(value)); + return Result<return_type, E>( + types::Ok<return_type>(std::move(res))); + } + + return Result<return_type, E>( + types::Err<E>(this->storage().template get<E>())); + } + + template<typename Func, + typename Ret + = Result<T, + typename details::ResultErrType< + typename details::result_of<Func>::type>::type>> + Ret mapError(Func func) const + { + return details::mapError(*this, func); + } + + template<typename Func> + Result<void, E> then(Func func) + { + if (this->isOk()) { + func(std::move(this->storage().template get<T>())); + + return Ok(); + } + + return Err(std::move(this->storage().template get<E>())); + } + + template<typename Func> + Result<typename std::result_of<Func>::type, E> then(Func func) + { + if (this->isOk()) { + return Ok(func(std::move(this->storage().template get<T>()))); + } + + return Err(std::move(this->storage().template get<E>())); + } + + template<typename Func> + void otherwise(Func func) + { + if (this->isOk()) { + return; + } + + func(std::move(this->storage().template get<E>())); + } + + template<typename Func, + typename Ret + = Result<T, + typename details::ResultErrType< + typename details::result_of<Func>::type>::type>> + Ret orElse(Func func) const + { + return details::orElse(*this, func); + } + + storage_type& storage() { return storage_; } + + const storage_type& storage() const { return storage_; } + + template<typename U = T> + typename std::enable_if<!std::is_same<U, void>::value, T>::type unwrapOr( + const U& defaultValue) const + { + if (isOk()) { + return storage().template get<T>(); + } + return defaultValue; + } + + template<typename Func> + auto unwrapOrElse(Func func) const + { + if (isOk()) { + return storage().template get<T>(); + } + return func(this->storage().template get<E>()); + } + + template<typename U = T> + typename std::enable_if<!std::is_same<U, void>::value, U>::type unwrap() + const + { + if (isOk()) { + return std::move(storage().template get<U>()); + } + + ::fprintf(stderr, "Attempting to unwrap an error Result\n"); + abort(); + } + + template<typename U = T> + typename std::enable_if<!std::is_same<U, void>::value, U>::type unwrap() + { + if (isOk()) { + return std::move(storage().template get<U>()); + } + + ::fprintf(stderr, "Attempting to unwrap an error Result\n"); + abort(); + } + + template<typename U = T> + typename std::enable_if<std::is_same<U, void>::value, U>::type unwrap() + const + { + if (isOk()) { + return; + } + + ::fprintf(stderr, "Attempting to unwrap an error Result\n"); + abort(); + } + + E unwrapErr() const + { + if (isErr()) { + return storage().template get<E>(); + } + + ::fprintf(stderr, "Attempting to unwrapErr an ok Result\n"); + abort(); + } + +private: + T expect_impl(std::true_type) const {} + T expect_impl(std::false_type) + { + return std::move(storage_.template get<T>()); + } + + bool ok_; + storage_type storage_; +}; + +template<typename T, typename E> +bool +operator==(const Result<T, E>& lhs, const Result<T, E>& rhs) +{ + static_assert(local_concept::EqualityComparable<T>::value, + "T must be EqualityComparable for Result to be comparable"); + static_assert(local_concept::EqualityComparable<E>::value, + "E must be EqualityComparable for Result to be comparable"); + + if (lhs.isOk() && rhs.isOk()) { + return lhs.storage().template get<T>() + == rhs.storage().template get<T>(); + } + if (lhs.isErr() && rhs.isErr()) { + return lhs.storage().template get<E>() + == rhs.storage().template get<E>(); + } +} + +template<typename T, typename E> +bool +operator==(const Result<T, E>& lhs, types::Ok<T> ok) +{ + static_assert(local_concept::EqualityComparable<T>::value, + "T must be EqualityComparable for Result to be comparable"); + + if (!lhs.isOk()) + return false; + + return lhs.storage().template get<T>() == ok.val; +} + +template<typename E> +bool +operator==(const Result<void, E>& lhs, types::Ok<void>) +{ + return lhs.isOk(); +} + +template<typename T, typename E> +bool +operator==(const Result<T, E>& lhs, types::Err<E> err) +{ + static_assert(local_concept::EqualityComparable<E>::value, + "E must be EqualityComparable for Result to be comparable"); + if (!lhs.isErr()) + return false; + + return lhs.storage().template get<E>() == err.val; +} + +#define TRY(...) \ + ({ \ + auto res = __VA_ARGS__; \ + if (!res.isOk()) { \ + typedef typename ::details::ResultErrType<decltype(res)>::type E; \ + return ::types::Err<E>(res.storage().template get<E>()); \ + } \ + res.unwrap(); \ + }) |