X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FExpected.h;h=3796336788c6686f91f07d674630e7c44083db89;hp=c2d4e42d07784fbcd5739198bc843e1a159fa186;hb=4c6781bf95a7e5e0af802e162f4cb3b310203bf4;hpb=f2925b23df8d85ebca72d62a69f1282528c086de diff --git a/folly/Expected.h b/folly/Expected.h index c2d4e42d..37963367 100644 --- a/folly/Expected.h +++ b/folly/Expected.h @@ -29,12 +29,17 @@ #include #include +#include + +#include #include +#include #include #include #include #include #include +#include #define FOLLY_EXPECTED_ID(X) FB_CONCATENATE(FB_CONCATENATE(Folly, X), __LINE__) @@ -93,6 +98,10 @@ using ExpectedErrorType = // Details... namespace expected_detail { + +template +struct PromiseReturn; + #ifdef _MSC_VER // MSVC 2015 can't handle the StrictConjunction, so we have // to use std::conjunction instead. @@ -656,12 +665,18 @@ class BadExpectedAccess : public std::logic_error { BadExpectedAccess() : std::logic_error("bad Expected access") {} }; +namespace expected_detail { + +[[noreturn]] void throwBadExpectedAccess(); + +} // namespace expected_detail + /** * Unexpected - a helper type used to disambiguate the construction of * Expected objects in the error state. */ template -class Unexpected final { +class Unexpected final : ColdClass { template friend class Unexpected; template @@ -1034,6 +1049,12 @@ class Expected final : expected_detail::ExpectedStorage { return *this; } + // Used only when an Expected is used with coroutines on MSVC + /* implicit */ Expected(const expected_detail::PromiseReturn& p) + : Expected{} { + p.promise_->value_ = this; + } + template ::value)> void emplace(Ts&&... ts) { @@ -1046,7 +1067,7 @@ class Expected final : expected_detail::ExpectedStorage { void swap(Expected& that) noexcept( expected_detail::StrictAllOf::value) { if (this->uninitializedByException() || that.uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } using std::swap; if (*this) { @@ -1087,11 +1108,11 @@ class Expected final : expected_detail::ExpectedStorage { * Accessors */ constexpr bool hasValue() const noexcept { - return expected_detail::Which::eValue == this->which_; + return LIKELY(expected_detail::Which::eValue == this->which_); } constexpr bool hasError() const noexcept { - return expected_detail::Which::eError == this->which_; + return UNLIKELY(expected_detail::Which::eError == this->which_); } using Base::uninitializedByException; @@ -1186,7 +1207,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval(), std::declval()...)) { if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return expected_detail::ExpectedHelper::then_( base(), static_cast(fns)...); @@ -1197,7 +1218,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval(), std::declval()...)) { if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return expected_detail::ExpectedHelper::then_( base(), static_cast(fns)...); @@ -1208,7 +1229,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval(), std::declval()...)) { if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return expected_detail::ExpectedHelper::then_( std::move(base()), static_cast(fns)...); @@ -1222,7 +1243,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( base(), static_cast(yes), static_cast(no))); @@ -1233,7 +1254,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( base(), static_cast(yes), static_cast(no))); @@ -1244,7 +1265,7 @@ class Expected final : expected_detail::ExpectedStorage { std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); if (this->uninitializedByException()) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( std::move(base()), static_cast(yes), static_cast(no))); @@ -1256,13 +1277,13 @@ class Expected final : expected_detail::ExpectedStorage { if (LIKELY(hasError())) { throw typename Unexpected::BadExpectedAccess(this->error_); } - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } } void requireError() const { if (UNLIKELY(!hasError())) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } } @@ -1276,12 +1297,11 @@ inline typename std::enable_if::value, bool>::type operator==( const Expected& lhs, const Expected& rhs) { - if (UNLIKELY(lhs.which_ != rhs.which_)) { - return UNLIKELY(lhs.uninitializedByException()) ? false - : throw BadExpectedAccess(); - } if (UNLIKELY(lhs.uninitializedByException())) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); + } + if (UNLIKELY(lhs.which_ != rhs.which_)) { + return false; } if (UNLIKELY(lhs.hasError())) { return true; // All error states are considered equal @@ -1305,7 +1325,7 @@ operator<( const Expected& rhs) { if (UNLIKELY( lhs.uninitializedByException() || rhs.uninitializedByException())) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } if (UNLIKELY(lhs.hasError())) { return !rhs.hasError(); @@ -1413,3 +1433,99 @@ bool operator>(const Value& other, const Expected&) = delete; #undef FOLLY_REQUIRES #undef FOLLY_REQUIRES_TRAILING + +// Enable the use of folly::Expected with `co_await` +// Inspired by https://github.com/toby-allsopp/coroutine_monad +#if FOLLY_HAS_COROUTINES +#include + +namespace folly { +namespace expected_detail { +template +struct Promise; + +template +struct PromiseReturn { + Optional> storage_; + Promise* promise_; + /* implicit */ PromiseReturn(Promise& promise) noexcept + : promise_(&promise) { + promise_->value_ = &storage_; + } + PromiseReturn(PromiseReturn&& that) noexcept + : PromiseReturn{*that.promise_} {} + ~PromiseReturn() {} + /* implicit */ operator Expected() & { + return std::move(*storage_); + } +}; + +template +struct Promise { + Optional>* value_ = nullptr; + Promise() = default; + Promise(Promise const&) = delete; + // This should work regardless of whether the compiler generates: + // folly::Expected retobj{ p.get_return_object(); } // MSVC + // or: + // auto retobj = p.get_return_object(); // clang + PromiseReturn get_return_object() noexcept { + return *this; + } + std::experimental::suspend_never initial_suspend() const noexcept { + return {}; + } + std::experimental::suspend_never final_suspend() const { + return {}; + } + template + void return_value(U&& u) { + value_->emplace(static_cast(u)); + } + void unhandled_exception() { + // Technically, throwing from unhandled_exception is underspecified: + // https://github.com/GorNishanov/CoroutineWording/issues/17 + throw; + } +}; + +template +struct Awaitable { + Expected o_; + + explicit Awaitable(Expected o) : o_(std::move(o)) {} + + bool await_ready() const noexcept { + return o_.hasValue(); + } + Value await_resume() { + return std::move(o_.value()); + } + + // Explicitly only allow suspension into a Promise + template + void await_suspend(std::experimental::coroutine_handle> h) { + *h.promise().value_ = makeUnexpected(std::move(o_.error())); + // Abort the rest of the coroutine. resume() is not going to be called + h.destroy(); + } +}; +} // namespace expected_detail + +template +expected_detail::Awaitable +/* implicit */ operator co_await(Expected o) { + return expected_detail::Awaitable{std::move(o)}; +} +} // namespace folly + +// This makes folly::Expected useable as a coroutine return type... +namespace std { +namespace experimental { +template +struct coroutine_traits, Args...> { + using promise_type = folly::expected_detail::Promise; +}; +} // namespace experimental +} // namespace std +#endif // FOLLY_HAS_COROUTINES