From 5068d34c08f951a7ea6257d305a1627b09a95817 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 19:44:55 +0200 Subject: Adding upstream version 0.11.1. Signed-off-by: Daniel Baumann --- src/base/result.h | 983 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 983 insertions(+) create mode 100644 src/base/result.h (limited to 'src/base/result.h') diff --git a/src/base/result.h b/src/base/result.h new file mode 100644 index 0000000..27500de --- /dev/null +++ b/src/base/result.h @@ -0,0 +1,983 @@ +/* + 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 can be used to return and propagate an error to the caller. Result is an algebraic + data type that can either Ok(T) to represent success or Err(E) to represent an error. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace types { + template + struct Ok { + Ok(const T& val) : val(val) { } + Ok(T&& val) : val(std::move(val)) { } + + T val; + }; + + template<> + struct Ok { }; + + template + struct Err { + Err(const E& val) : val(val) { } + Err(E&& val) : val(std::move(val)) { } + + E val; + }; + + template<> + struct Err { }; +}; + +template::type> +types::Ok Ok(T&& val) { + return types::Ok(std::forward(val)); +} + +inline types::Ok Ok() { + return {}; +} + +template::type> +types::Err Err(E&& val) { + return types::Err(std::forward(val)); +} + +inline types::Err Err() { + return {}; +} + +template struct Result; + +namespace details { + +template struct void_t { typedef void type; }; + +namespace impl { + template struct result_of; + + template + struct result_of : public result_of { }; + + template + struct result_of> { + typedef Ret type; + }; +} + +template +struct result_of : public impl::result_of { }; + +template +struct result_of { + typedef Ret type; +}; + +template +struct result_of { + typedef Ret type; +}; + +template +struct ResultOkType { typedef typename std::decay::type type; }; + +template +struct ResultOkType> { + typedef T type; +}; + +template +struct ResultErrType { typedef R type; }; + +template +struct ResultErrType> { + typedef typename std::remove_reference::type type; +}; + +template struct IsResult : public std::false_type { }; +template +struct IsResult> : public std::true_type { }; + +namespace ok { + +namespace impl { + +template struct Map; + +template +struct Map : public Map { }; + +template +struct Map : public Map { }; + +// General implementation +template +struct Map { + + static_assert(!IsResult::value, + "Can not map a callback returning a Result, use then instead"); + + template + static Result map(const Result& result, Func func) { + + static_assert( + std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + if (result.isOk()) { + auto res = func(result.storage().template get()); + return types::Ok(std::move(res)); + } + + return types::Err(result.storage().template get()); + } +}; + +// Specialization for callback returning void +template +struct Map { + + template + static Result map(const Result& result, Func func) { + + if (result.isOk()) { + func(result.storage().template get()); + return types::Ok(); + } + + return types::Err(result.storage().template get()); + } +}; + +// Specialization for a void Result +template +struct Map { + + template + static Result map(const Result& result, Func func) { + static_assert(std::is_same::value, + "Can not map a void callback on a non-void Result"); + + if (result.isOk()) { + auto ret = func(); + return types::Ok(std::move(ret)); + } + + return types::Err(result.storage().template get()); + } +}; + +// Specialization for callback returning void on a void Result +template<> +struct Map { + + template + static Result map(const Result& result, Func func) { + static_assert(std::is_same::value, + "Can not map a void callback on a non-void Result"); + + if (result.isOk()) { + func(); + return types::Ok(); + } + + return types::Err(result.storage().template get()); + } +}; + +// General specialization for a callback returning a Result +template +struct Map (Arg)> { + + template + static Result map(const Result& result, Func func) { + static_assert( + std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + if (result.isOk()) { + auto res = func(result.storage().template get()); + return res; + } + + return types::Err(result.storage().template get()); + } +}; + +// Specialization for a void callback returning a Result +template +struct Map (void)> { + + template + static Result map(const Result& result, Func func) { + static_assert(std::is_same::value, "Can not call a void-callback on a non-void Result"); + + if (result.isOk()) { + auto res = func(); + return res; + } + + return types::Err(result.storage().template get()); + } + +}; + +} // namespace impl + +template struct Map; + +template +struct Map : public impl::Map { }; + +template +struct Map : public impl::Map { }; + +template +struct Map : public impl::Map { }; + +template +struct Map : public impl::Map { }; + +template +struct Map> : public impl::Map { }; + +} // namespace ok + + +namespace err { + +namespace impl { + +template struct Map; + +template +struct Map { + + static_assert(!IsResult::value, + "Can not map a callback returning a Result, use orElse instead"); + + template + static Result map(const Result& result, Func func) { + if (result.isErr()) { + auto res = func(result.storage().template get()); + return types::Err(res); + } + + return types::Ok(result.storage().template get()); + } + + template + static Result map(const Result& result, Func func) { + if (result.isErr()) { + auto res = func(result.storage().template get()); + return types::Err(res); + } + + return types::Ok(); + } + + +}; + +} // namespace impl + +template struct Map : public impl::Map { }; + +} // namespace err; + +namespace And { + +namespace impl { + + template struct Then; + + template + struct Then : public Then { }; + + template + struct Then : public Then { }; + + template + struct Then : public Then { }; + + template + struct Then { + static_assert(std::is_same::value, + "then() should not return anything, use map() instead"); + + template + static Result then(const Result& result, Func func) { + if (result.isOk()) { + func(result.storage().template get()); + } + return result; + } + }; + + template + struct Then { + static_assert(std::is_same::value, + "then() should not return anything, use map() instead"); + + template + static Result then(const Result& result, Func func) { + static_assert(std::is_same::value, "Can not call a void-callback on a non-void Result"); + + if (result.isOk()) { + func(); + } + + return result; + } + }; + + +} // namespace impl + +template +struct Then : public impl::Then { }; + +template +struct Then : public impl::Then { }; + +template +struct Then : public impl::Then { }; + +template +struct Then : public impl::Then { }; + +template +struct Then : public impl::Then { }; + +template +struct Then> : public impl::Then { }; + +} // namespace And + +namespace Or { + +namespace impl { + + template struct Else; + + template + struct Else : public Else { }; + + template + struct Else : public Else { }; + + template + struct Else : public Else { }; + + template + struct Else (Arg)> { + + template + static Result orElse(const Result& result, Func func) { + static_assert( + std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + if (result.isErr()) { + auto res = func(result.storage().template get()); + return res; + } + + return types::Ok(result.storage().template get()); + } + + template + static Result orElse(const Result& result, Func func) { + if (result.isErr()) { + auto res = func(result.storage().template get()); + return res; + } + + return types::Ok(); + } + + }; + + template + struct Else (void)> { + + template + static Result orElse(const Result& result, Func func) { + static_assert(std::is_same::value, + "Can not call a void-callback on a non-void Result"); + + if (result.isErr()) { + auto res = func(); + return res; + } + + return types::Ok(result.storage().template get()); + } + + template + static Result orElse(const Result& result, Func func) { + if (result.isErr()) { + auto res = func(); + return res; + } + + return types::Ok(); + } + + }; + +} // namespace impl + +template +struct Else : public impl::Else { }; + +template +struct Else : public impl::Else { }; + +template +struct Else : public impl::Else { }; + +template +struct Else : public impl::Else { }; + +} // namespace Or + +namespace Other { + +namespace impl { + + template struct Wise; + + template + struct Wise : public Wise { }; + + template + struct Wise : public Wise { }; + + template + struct Wise : public Wise { }; + + template + struct Wise { + + template + static Result otherwise(const Result& result, Func func) { + static_assert( + std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + static_assert(std::is_same::value, + "callback should not return anything, use mapError() for that"); + + if (result.isErr()) { + func(result.storage().template get()); + } + return result; + } + + }; + +} // namespace impl + +template +struct Wise : public impl::Wise { }; + +template +struct Wise : public impl::Wise { }; + +template +struct Wise : public impl::Wise { }; + +template +struct Wise : public impl::Wise { }; + +} // namespace Other + +template +decltype(auto) map(const Result& result, Func func) { + return ok::Map::map(result, func); +} + +template::type + >::type + > + > +Ret mapError(const Result& result, Func func) { + return err::Map::map(result, func); +} + +template +Result then(const Result& result, Func func) { + return And::Then::then(result, func); +} + +template +Result otherwise(const Result& result, Func func) { + return Other::Wise::otherwise(result, func); +} + +template::type + >::type + > +> +Ret orElse(const Result& result, Func func) { + return Or::Else::orElse(result, func); +} + +struct ok_tag { }; +struct err_tag { }; + +template +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::type type; + + Storage() + : initialized_(false) + { } + + void construct(types::Ok ok) + { + new (&storage_) T(std::move(ok.val)); + initialized_ = true; + } + void construct(types::Err err) + { + new (&storage_) E(err.val); + initialized_ = true; + } + + template + void rawConstruct(U&& val) { + typedef typename std::decay::type CleanU; + + new (&storage_) CleanU(std::forward(val)); + initialized_ = true; + } + + template + const U& get() const { + return *reinterpret_cast(&storage_); + } + + template + U& get() { + return *reinterpret_cast(&storage_); + } + + void destroy(ok_tag) { + if (initialized_) { + get().~T(); + initialized_ = false; + } + } + + void destroy(err_tag) { + if (initialized_) { + get().~E(); + initialized_ = false; + } + } + + type storage_; + bool initialized_; +}; + +template +struct Storage { + typedef typename std::aligned_storage::type type; + + void construct(types::Ok) + { + initialized_ = true; + } + + void construct(types::Err err) + { + new (&storage_) E(err.val); + initialized_ = true; + } + + template + void rawConstruct(U&& val) { + typedef typename std::decay::type CleanU; + + new (&storage_) CleanU(std::forward(val)); + initialized_ = true; + } + + void destroy(ok_tag) { initialized_ = false; } + void destroy(err_tag) { + if (initialized_) { + get().~E(); initialized_ = false; + } + } + + template::value>> + const U& get() const { + return *reinterpret_cast(&storage_); + } + + template::value>> + typename std::add_lvalue_reference::type get() { + return *reinterpret_cast(&storage_); + } + + template::value>> + void get() {} + + type storage_; + bool initialized_; +}; + +template +struct Constructor { + + static void move(Storage&& src, Storage& dst, ok_tag) { + dst.rawConstruct(std::move(src.template get())); + src.destroy(ok_tag()); + } + + static void copy(const Storage& src, Storage& dst, ok_tag) { + dst.rawConstruct(src.template get()); + } + + static void move(Storage&& src, Storage& dst, err_tag) { + dst.rawConstruct(std::move(src.template get())); + src.destroy(err_tag()); + } + + static void copy(const Storage& src, Storage& dst, err_tag) { + dst.rawConstruct(src.template get()); + } +}; + +template +struct Constructor { + static void move(Storage&& src, Storage& dst, ok_tag) { + } + + static void copy(const Storage& src, Storage& dst, ok_tag) { + } + + static void move(Storage&& src, Storage& dst, err_tag) { + dst.rawConstruct(std::move(src.template get())); + src.destroy(err_tag()); + } + + static void copy(const Storage& src, Storage& dst, err_tag) { + dst.rawConstruct(src.template get()); + } +}; + +} // namespace details + +namespace concept { + +template struct EqualityComparable : std::false_type { }; + +template +struct EqualityComparable() == std::declval())>::type + >::type +> : std::true_type +{ +}; + + +} // namespace concept + +template +struct Result { + + static_assert(!std::is_same::value, "void error type is not allowed"); + + typedef details::Storage storage_type; + + Result(types::Ok ok) + : ok_(true) + { + storage_.construct(std::move(ok)); + } + + Result(types::Err err) + : ok_(false) + { + storage_.construct(std::move(err)); + } + + Result(Result&& other) { + if (other.isOk()) { + details::Constructor::move(std::move(other.storage_), storage_, details::ok_tag()); + ok_ = true; + } else { + details::Constructor::move(std::move(other.storage_), storage_, details::err_tag()); + ok_ = false; + } + } + + Result(const Result& other) { + if (other.isOk()) { + details::Constructor::copy(other.storage_, storage_, details::ok_tag()); + ok_ = true; + } else { + details::Constructor::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()); + } + + template + auto map(Func func) + { + using return_type = decltype(func(T{})); + + if (this->isOk()) { + auto value = std::move(this->storage().template get()); + auto res = func(std::move(value)); + return Result( + types::Ok(std::move(res))); + } + + return Result( + types::Err(this->storage().template get())); + } + + template::type + >::type + > + > + Ret mapError(Func func) const { + return details::mapError(*this, func); + } + + template + Result then(Func func) { + if (this->isOk()) { + func(std::move(this->storage().template get())); + + return Ok(); + } + + return Err(std::move(this->storage().template get())); + } + + template + Result::type, E> then(Func func) { + if (this->isOk()) { + return Ok(func(std::move(this->storage().template get()))); + } + + return Err(std::move(this->storage().template get())); + } + + template + void otherwise(Func func) { + if (this->isOk()) { + return; + } + + func(std::move(this->storage().template get())); + } + + template::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 std::enable_if< + !std::is_same::value, + T + >::type + unwrapOr(const U& defaultValue) const { + if (isOk()) { + return storage().template get(); + } + return defaultValue; + } + + template + auto unwrapOrElse(Func func) const { + if (isOk()) { + return storage().template get(); + } + return func(this->storage().template get()); + } + + template + typename std::enable_if< + !std::is_same::value, + U + >::type + unwrap() const { + if (isOk()) { + return std::move(storage().template get()); + } + + ::fprintf(stderr, "Attempting to unwrap an error Result\n"); + abort(); + } + + template + typename std::enable_if< + !std::is_same::value, + U + >::type + unwrap() { + if (isOk()) { + return std::move(storage().template get()); + } + + ::fprintf(stderr, "Attempting to unwrap an error Result\n"); + abort(); + } + + template + typename std::enable_if::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(); + } + + ::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()); + } + + bool ok_; + storage_type storage_; +}; + +template +bool operator==(const Result& lhs, const Result& rhs) { + static_assert(concept::EqualityComparable::value, "T must be EqualityComparable for Result to be comparable"); + static_assert(concept::EqualityComparable::value, "E must be EqualityComparable for Result to be comparable"); + + if (lhs.isOk() && rhs.isOk()) { + return lhs.storage().template get() == rhs.storage().template get(); + } + if (lhs.isErr() && rhs.isErr()) { + return lhs.storage().template get() == rhs.storage().template get(); + } +} + +template +bool operator==(const Result& lhs, types::Ok ok) { + static_assert(concept::EqualityComparable::value, "T must be EqualityComparable for Result to be comparable"); + + if (!lhs.isOk()) return false; + + return lhs.storage().template get() == ok.val; +} + +template +bool operator==(const Result& lhs, types::Ok) { + return lhs.isOk(); +} + +template +bool operator==(const Result& lhs, types::Err err) { + static_assert(concept::EqualityComparable::value, "E must be EqualityComparable for Result to be comparable"); + if (!lhs.isErr()) return false; + + return lhs.storage().template get() == err.val; +} + +#define TRY(...) \ + ({ \ + auto res = __VA_ARGS__; \ + if (!res.isOk()) { \ + typedef typename ::details::ResultErrType::type E; \ + return ::types::Err(res.storage().template get()); \ + } \ + res.unwrap(); \ + }) -- cgit v1.2.3