/* 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(); \ })