From: Lee Howes Date: Mon, 30 Oct 2017 21:28:33 +0000 (-0700) Subject: Split SemiFuture and Future into separate types. Add BasicFuture shared between them. X-Git-Tag: v2017.11.06.00~35 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=46af81b61d7c026deb2735019ae991d36e7b0d39 Split SemiFuture and Future into separate types. Add BasicFuture shared between them. Summary: To avoid the risk of bugs caused by a Future being cast to a SemiFuture, and losing some of the properties in the process, this splits SemiFuture and Future into unrelated types, sharing a private superclass for code reuse. * Add BasicFuture in futures::detail * Make superclass privately inherited. * Unset executor when constructing SemiFuture from Future. Reviewed By: yfeldblum Differential Revision: D6177780 fbshipit-source-id: dea3116aeec0572bb973c2a561e17785199e86f2 --- diff --git a/folly/futures/Future-inl.h b/folly/futures/Future-inl.h index 7c8c7af6..17f47231 100644 --- a/folly/futures/Future-inl.h +++ b/folly/futures/Future-inl.h @@ -129,102 +129,25 @@ inline auto makeCoreCallbackState(Promise&& p, F&& f) noexcept( return CoreCallbackState>>( std::move(p), std::forward(f)); } -} // namespace detail -} // namespace futures - -template -SemiFuture::type> makeSemiFuture(T&& t) { - return makeSemiFuture(Try::type>(std::forward(t))); -} - -// makeSemiFutureWith(SemiFuture()) -> SemiFuture -template -typename std::enable_if< - isSemiFuture::type>::value, - typename std::result_of::type>::type -makeSemiFutureWith(F&& func) { - using InnerType = - typename isSemiFuture::type>::Inner; - try { - return std::forward(func)(); - } catch (std::exception& e) { - return makeSemiFuture( - exception_wrapper(std::current_exception(), e)); - } catch (...) { - return makeSemiFuture( - exception_wrapper(std::current_exception())); - } -} - -// makeSemiFutureWith(T()) -> SemiFuture -// makeSemiFutureWith(void()) -> SemiFuture -template -typename std::enable_if< - !(isSemiFuture::type>::value), - SemiFuture::type>>>::type -makeSemiFutureWith(F&& func) { - using LiftedResult = Unit::LiftT::type>; - return makeSemiFuture( - makeTryWith([&func]() mutable { return std::forward(func)(); })); -} - -template -SemiFuture makeSemiFuture(std::exception_ptr const& e) { - return makeSemiFuture(Try(e)); -} template -SemiFuture makeSemiFuture(exception_wrapper ew) { - return makeSemiFuture(Try(std::move(ew))); -} - -template -typename std:: - enable_if::value, SemiFuture>::type - makeSemiFuture(E const& e) { - return makeSemiFuture(Try(make_exception_wrapper(e))); -} - -template -SemiFuture makeSemiFuture(Try&& t) { - return SemiFuture(new futures::detail::Core(std::move(t))); -} - -template -SemiFuture SemiFuture::makeEmpty() { - return SemiFuture(futures::detail::EmptyConstruct{}); -} - -template -SemiFuture::SemiFuture(SemiFuture&& other) noexcept : core_(other.core_) { +FutureBase::FutureBase(SemiFuture&& other) noexcept : core_(other.core_) { other.core_ = nullptr; } template -SemiFuture& SemiFuture::operator=(SemiFuture&& other) noexcept { - std::swap(core_, other.core_); - return *this; -} - -template -SemiFuture::SemiFuture(Future&& other) noexcept : core_(other.core_) { +FutureBase::FutureBase(Future&& other) noexcept : core_(other.core_) { other.core_ = nullptr; } -template -SemiFuture& SemiFuture::operator=(Future&& other) noexcept { - std::swap(core_, other.core_); - return *this; -} - template template -SemiFuture::SemiFuture(T2&& val) +FutureBase::FutureBase(T2&& val) : core_(new futures::detail::Core(Try(std::forward(val)))) {} template template -SemiFuture::SemiFuture( +FutureBase::FutureBase( typename std::enable_if::value>::type*) : core_(new futures::detail::Core(Try(T()))) {} @@ -233,52 +156,52 @@ template < class... Args, typename std::enable_if::value, int>:: type> -SemiFuture::SemiFuture(in_place_t, Args&&... args) +FutureBase::FutureBase(in_place_t, Args&&... args) : core_( new futures::detail::Core(in_place, std::forward(args)...)) { } template -SemiFuture::~SemiFuture() { - detach(); +template +void FutureBase::assign(FutureType& other) noexcept { + std::swap(core_, other.core_); } -// This must be defined after the constructors to avoid a bug in MSVC -// https://connect.microsoft.com/VisualStudio/feedback/details/3142777/out-of-line-constructor-definition-after-implicit-reference-causes-incorrect-c2244 -inline SemiFuture makeSemiFuture() { - return makeSemiFuture(Unit{}); +template +FutureBase::~FutureBase() { + detach(); } template -T& SemiFuture::value() & { +T& FutureBase::value() & { throwIfInvalid(); return core_->getTry().value(); } template -T const& SemiFuture::value() const& { +T const& FutureBase::value() const& { throwIfInvalid(); return core_->getTry().value(); } template -T&& SemiFuture::value() && { +T&& FutureBase::value() && { throwIfInvalid(); return std::move(core_->getTry().value()); } template -T const&& SemiFuture::value() const&& { +T const&& FutureBase::value() const&& { throwIfInvalid(); return std::move(core_->getTry().value()); } template -inline Future SemiFuture::via(Executor* executor, int8_t priority) && { +inline Future FutureBase::via(Executor* executor, int8_t priority) && { throwIfInvalid(); setExecutor(executor, priority); @@ -289,36 +212,23 @@ inline Future SemiFuture::via(Executor* executor, int8_t priority) && { } template -inline Future SemiFuture::via(Executor* executor, int8_t priority) & { - throwIfInvalid(); - Promise p; - auto f = p.getFuture(); - auto func = [p = std::move(p)](Try&& t) mutable { - p.setTry(std::move(t)); - }; - using R = futures::detail::callableResult; - thenImplementation(std::move(func), typename R::Arg()); - return std::move(f).via(executor, priority); -} - -template -bool SemiFuture::isReady() const { +bool FutureBase::isReady() const { throwIfInvalid(); return core_->ready(); } template -bool SemiFuture::hasValue() { +bool FutureBase::hasValue() { return getTry().hasValue(); } template -bool SemiFuture::hasException() { +bool FutureBase::hasException() { return getTry().hasException(); } template -void SemiFuture::detach() { +void FutureBase::detach() { if (core_) { core_->detachFuture(); core_ = nullptr; @@ -326,21 +236,21 @@ void SemiFuture::detach() { } template -Try& SemiFuture::getTry() { +Try& FutureBase::getTry() { throwIfInvalid(); return core_->getTry(); } template -void SemiFuture::throwIfInvalid() const { +void FutureBase::throwIfInvalid() const { if (!core_) { throwNoState(); -} + } } template -Optional> SemiFuture::poll() { +Optional> FutureBase::poll() { Optional> o; if (core_->ready()) { o = std::move(core_->getTry()); @@ -349,104 +259,21 @@ Optional> SemiFuture::poll() { } template -void SemiFuture::raise(exception_wrapper exception) { +void FutureBase::raise(exception_wrapper exception) { core_->raise(std::move(exception)); } template template -void SemiFuture::setCallback_(F&& func) { +void FutureBase::setCallback_(F&& func) { throwIfInvalid(); core_->setCallback(std::forward(func)); } template -SemiFuture::SemiFuture(futures::detail::EmptyConstruct) noexcept +FutureBase::FutureBase(futures::detail::EmptyConstruct) noexcept : core_(nullptr) {} -template -Future Future::makeEmpty() { - return Future(futures::detail::EmptyConstruct{}); -} - -template -Future::Future(Future&& other) noexcept - : SemiFuture(std::move(other)) {} - -template -Future& Future::operator=(Future&& other) noexcept { - SemiFuture::operator=(SemiFuture{std::move(other)}); - return *this; -} - -template -template < - class T2, - typename std::enable_if< - !std::is_same::type>::value && - std::is_constructible::value && - std::is_convertible::value, - int>::type> -Future::Future(Future&& other) - : Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {} - -template -template < - class T2, - typename std::enable_if< - !std::is_same::type>::value && - std::is_constructible::value && - !std::is_convertible::value, - int>::type> -Future::Future(Future&& other) - : Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {} - -template -template < - class T2, - typename std::enable_if< - !std::is_same::type>::value && - std::is_constructible::value, - int>::type> -Future& Future::operator=(Future&& other) { - return operator=( - std::move(other).then([](T2&& v) { return T(std::move(v)); })); -} - -// TODO: isSemiFuture -template -template -Future::Future(T2&& val) : SemiFuture(std::forward(val)) {} - -template -template -Future::Future(typename std::enable_if::value>::type*) - : SemiFuture() {} - -template -template < - class... Args, - typename std::enable_if::value, int>:: - type> -Future::Future(in_place_t, Args&&... args) - : SemiFuture(in_place, std::forward(args)...) {} - -template -Future::~Future() { -} - -// unwrap - -template -template -typename std::enable_if::value, - Future::Inner>>::type -Future::unwrap() { - return then([](Future::Inner> internal_future) { - return internal_future; - }); -} - // then // Variant: returns a value @@ -454,7 +281,7 @@ Future::unwrap() { template template typename std::enable_if::type -SemiFuture::thenImplementation( +FutureBase::thenImplementation( F&& func, futures::detail::argResult) { static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); @@ -517,7 +344,7 @@ SemiFuture::thenImplementation( template template typename std::enable_if::type -SemiFuture::thenImplementation( +FutureBase::thenImplementation( F&& func, futures::detail::argResult) { static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); @@ -550,6 +377,181 @@ SemiFuture::thenImplementation( return f; } +} // namespace detail +} // namespace futures + +template +SemiFuture::type> makeSemiFuture(T&& t) { + return makeSemiFuture(Try::type>(std::forward(t))); +} + +// makeSemiFutureWith(SemiFuture()) -> SemiFuture +template +typename std::enable_if< + isSemiFuture::type>::value, + typename std::result_of::type>::type +makeSemiFutureWith(F&& func) { + using InnerType = + typename isSemiFuture::type>::Inner; + try { + return std::forward(func)(); + } catch (std::exception& e) { + return makeSemiFuture( + exception_wrapper(std::current_exception(), e)); + } catch (...) { + return makeSemiFuture( + exception_wrapper(std::current_exception())); + } +} + +// makeSemiFutureWith(T()) -> SemiFuture +// makeSemiFutureWith(void()) -> SemiFuture +template +typename std::enable_if< + !(isSemiFuture::type>::value), + SemiFuture::type>>>::type +makeSemiFutureWith(F&& func) { + using LiftedResult = Unit::LiftT::type>; + return makeSemiFuture( + makeTryWith([&func]() mutable { return std::forward(func)(); })); +} + +template +SemiFuture makeSemiFuture(std::exception_ptr const& e) { + return makeSemiFuture(Try(e)); +} + +template +SemiFuture makeSemiFuture(exception_wrapper ew) { + return makeSemiFuture(Try(std::move(ew))); +} + +template +typename std:: + enable_if::value, SemiFuture>::type + makeSemiFuture(E const& e) { + return makeSemiFuture(Try(make_exception_wrapper(e))); +} + +template +SemiFuture makeSemiFuture(Try&& t) { + return SemiFuture(new futures::detail::Core(std::move(t))); +} + +// This must be defined after the constructors to avoid a bug in MSVC +// https://connect.microsoft.com/VisualStudio/feedback/details/3142777/out-of-line-constructor-definition-after-implicit-reference-causes-incorrect-c2244 +inline SemiFuture makeSemiFuture() { + return makeSemiFuture(Unit{}); +} + +template +SemiFuture SemiFuture::makeEmpty() { + return SemiFuture(futures::detail::EmptyConstruct{}); +} + +template +SemiFuture::SemiFuture(SemiFuture&& other) noexcept + : futures::detail::FutureBase(std::move(other)) {} + +template +SemiFuture::SemiFuture(Future&& other) noexcept + : futures::detail::FutureBase(std::move(other)) { + // SemiFuture should not have an executor on construction + if (this->core_) { + this->setExecutor(nullptr); + } +} + +template +SemiFuture& SemiFuture::operator=(SemiFuture&& other) noexcept { + this->assign(other); + return *this; +} + +template +SemiFuture& SemiFuture::operator=(Future&& other) noexcept { + this->assign(other); + // SemiFuture should not have an executor on construction + if (this->core_) { + this->setExecutor(nullptr); + } + return *this; +} + +template +Future Future::makeEmpty() { + return Future(futures::detail::EmptyConstruct{}); +} + +template +Future::Future(Future&& other) noexcept + : futures::detail::FutureBase(std::move(other)) {} + +template +Future& Future::operator=(Future&& other) noexcept { + this->assign(other); + return *this; +} + +template +template < + class T2, + typename std::enable_if< + !std::is_same::type>::value && + std::is_constructible::value && + std::is_convertible::value, + int>::type> +Future::Future(Future&& other) + : Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {} + +template +template < + class T2, + typename std::enable_if< + !std::is_same::type>::value && + std::is_constructible::value && + !std::is_convertible::value, + int>::type> +Future::Future(Future&& other) + : Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {} + +template +template < + class T2, + typename std::enable_if< + !std::is_same::type>::value && + std::is_constructible::value, + int>::type> +Future& Future::operator=(Future&& other) { + return operator=( + std::move(other).then([](T2&& v) { return T(std::move(v)); })); +} + +// unwrap + +template +template +typename std:: + enable_if::value, Future::Inner>>::type + Future::unwrap() { + return then([](Future::Inner> internal_future) { + return internal_future; + }); +} + +template +inline Future Future::via(Executor* executor, int8_t priority) & { + this->throwIfInvalid(); + Promise p; + auto f = p.getFuture(); + auto func = [p = std::move(p)](Try&& t) mutable { + p.setTry(std::move(t)); + }; + using R = futures::detail::callableResult; + this->template thenImplementation( + std::move(func), typename R::Arg()); + return std::move(f).via(executor, priority); +} template template @@ -733,10 +735,6 @@ auto via(Executor* x, Func&& func) return via(x).then(std::forward(func)); } -template -Future::Future(futures::detail::EmptyConstruct) noexcept - : SemiFuture(futures::detail::EmptyConstruct{}) {} - // makeFuture template @@ -1262,10 +1260,10 @@ Future Future::within(Duration dur, E e, Timekeeper* tk) { template Future Future::delayed(Duration dur, Timekeeper* tk) { return collectAll(*this, futures::sleep(dur, tk)) - .then([](std::tuple, Try> tup) { - Try& t = std::get<0>(tup); - return makeFuture(std::move(t)); - }); + .then([](std::tuple, Try> tup) { + Try& t = std::get<0>(tup); + return makeFuture(std::move(t)); + }); } namespace futures { @@ -1294,7 +1292,7 @@ void waitImpl(FutureType& f, Duration dur) { Promise promise; auto ret = promise.getFuture(); auto baton = std::make_shared(); - f.setCallback_([ baton, promise = std::move(promise) ](Try && t) mutable { + f.setCallback_([baton, promise = std::move(promise)](Try&& t) mutable { promise.setTry(std::move(t)); baton->post(); }); @@ -1347,12 +1345,12 @@ SemiFuture&& SemiFuture::wait(Duration dur) && { } template -T SemiFuture::get() { +T SemiFuture::get() && { return std::move(wait().value()); } template -T SemiFuture::get(Duration dur) { +T SemiFuture::get(Duration dur) && { wait(dur); if (this->isReady()) { return std::move(this->value()); @@ -1397,6 +1395,21 @@ Future&& Future::waitVia(DrivableExecutor* e) && { return std::move(*this); } +template +T Future::get() { + return std::move(wait().value()); +} + +template +T Future::get(Duration dur) { + wait(dur); + if (this->isReady()) { + return std::move(this->value()); + } else { + throwTimedOut(); + } +} + template T Future::getVia(DrivableExecutor* e) { return std::move(waitVia(e).value()); diff --git a/folly/futures/Future.h b/folly/futures/Future.h index 0c4092aa..39957470 100644 --- a/folly/futures/Future.h +++ b/folly/futures/Future.h @@ -45,42 +45,41 @@ template class Future; template -class SemiFuture { +class SemiFuture; + +namespace futures { +namespace detail { +template +class FutureBase { public: typedef T value_type; - static SemiFuture makeEmpty(); // equivalent to moved-from - - // not copyable - SemiFuture(SemiFuture const&) = delete; - SemiFuture& operator=(SemiFuture const&) = delete; - - // movable - SemiFuture(SemiFuture&&) noexcept; - SemiFuture& operator=(SemiFuture&&) noexcept; - - // safe move-constructabilty from Future - /* implicit */ SemiFuture(Future&&) noexcept; - SemiFuture& operator=(Future&&) noexcept; - /// Construct a Future from a value (perfect forwarding) template < class T2 = T, typename = typename std::enable_if< !isFuture::type>::value>::type> - /* implicit */ SemiFuture(T2&& val); + /* implicit */ FutureBase(T2&& val); template - /* implicit */ SemiFuture( - typename std::enable_if::value>::type* = nullptr); + /* implicit */ FutureBase( + typename std::enable_if::value>::type*); template < class... Args, typename std::enable_if::value, int>:: type = 0> - explicit SemiFuture(in_place_t, Args&&... args); + explicit FutureBase(in_place_t, Args&&... args); + + FutureBase(FutureBase const&) = delete; + FutureBase(SemiFuture&&) noexcept; + FutureBase(Future&&) noexcept; - ~SemiFuture(); + // not copyable + FutureBase(Future const&) = delete; + FutureBase(SemiFuture const&) = delete; + + ~FutureBase(); /// Returns a reference to the result, with a reference category and const- /// qualification equivalent to the reference category and const-qualification @@ -120,13 +119,6 @@ class SemiFuture { Executor* executor, int8_t priority = Executor::MID_PRI) &&; - /// This variant creates a new future, where the ref-qualifier && version - /// moves `this` out. This one is less efficient but avoids confusing users - /// when "return f.via(x);" fails. - inline Future via( - Executor* executor, - int8_t priority = Executor::MID_PRI) &; - /** True when the result (or exception) is ready. */ bool isReady() const; @@ -144,28 +136,6 @@ class SemiFuture { /// Note that this moves the Try out. Optional> poll(); - /// Block until the future is fulfilled. Returns the value (moved out), or - /// throws the exception. The future must not already have a callback. - T get(); - - /// Block until the future is fulfilled, or until timed out. Returns the - /// value (moved out), or throws the exception (which might be a TimedOut - /// exception). - T get(Duration dur); - - /// Block until this Future is complete. Returns a reference to this Future. - SemiFuture& wait() &; - - /// Overload of wait() for rvalue Futures - SemiFuture&& wait() &&; - - /// Block until this Future is complete or until the given Duration passes. - /// Returns a reference to this Future - SemiFuture& wait(Duration) &; - - /// Overload of wait(Duration) for rvalue Futures - SemiFuture&& wait(Duration) &&; - /// This is not the method you're looking for. /// /// This needs to be public because it's used by make* and when*, and it's @@ -199,25 +169,27 @@ class SemiFuture { } protected: - typedef futures::detail::Core* corePtr; + friend class Promise; + template + friend class SemiFuture; + template + friend class Future; + + using corePtr = futures::detail::Core*; // shared core state object corePtr core_; - explicit SemiFuture(corePtr obj) : core_(obj) {} + explicit FutureBase(corePtr obj) : core_(obj) {} - explicit SemiFuture(futures::detail::EmptyConstruct) noexcept; + explicit FutureBase(futures::detail::EmptyConstruct) noexcept; void detach(); void throwIfInvalid() const; - friend class Promise; - template - friend class SemiFuture; - - template - friend SemiFuture makeSemiFuture(Try&&); + template + void assign(FutureType&) noexcept; Executor* getExecutor() { return core_->getExecutor(); @@ -239,21 +211,133 @@ class SemiFuture { typename std::enable_if::type thenImplementation(F&& func, futures::detail::argResult); }; +} // namespace detail +} // namespace futures template -class Future : public SemiFuture { +class SemiFuture : private futures::detail::FutureBase { + private: + using Base = futures::detail::FutureBase; + public: - typedef T value_type; + static SemiFuture makeEmpty(); // equivalent to moved-from - static Future makeEmpty(); // equivalent to moved-from + // Export public interface of FutureBase + // FutureBase is inherited privately to avoid subclasses being cast to + // a FutureBase pointer + using typename Base::value_type; - // not copyable - Future(Future const&) = delete; - Future& operator=(Future const&) = delete; + /// Construct a Future from a value (perfect forwarding) + template < + class T2 = T, + typename = typename std::enable_if< + !isFuture::type>::value>::type> + /* implicit */ SemiFuture(T2&& val) : Base(std::forward(val)) {} + template + /* implicit */ SemiFuture( + typename std::enable_if::value>::type* p = nullptr) + : Base(p) {} + + template < + class... Args, + typename std::enable_if::value, int>:: + type = 0> + explicit SemiFuture(in_place_t, Args&&... args) + : Base(in_place, std::forward(args)...) {} + + SemiFuture(SemiFuture const&) = delete; // movable - Future(Future&&) noexcept; - Future& operator=(Future&&) noexcept; + SemiFuture(SemiFuture&&) noexcept; + // safe move-constructabilty from Future + /* implicit */ SemiFuture(Future&&) noexcept; + + using Base::cancel; + using Base::getTry; + using Base::hasException; + using Base::hasValue; + using Base::isActive; + using Base::isReady; + using Base::poll; + using Base::raise; + using Base::setCallback_; + using Base::value; + using Base::via; + + SemiFuture& operator=(SemiFuture const&) = delete; + SemiFuture& operator=(SemiFuture&&) noexcept; + SemiFuture& operator=(Future&&) noexcept; + + /// Block until the future is fulfilled. Returns the value (moved out), or + /// throws the exception. The future must not already have a callback. + T get() &&; + + /// Block until the future is fulfilled, or until timed out. Returns the + /// value (moved out), or throws the exception (which might be a TimedOut + /// exception). + T get(Duration dur) &&; + + /// Block until this Future is complete. Returns a reference to this Future. + SemiFuture& wait() &; + + /// Overload of wait() for rvalue Futures + SemiFuture&& wait() &&; + + /// Block until this Future is complete or until the given Duration passes. + /// Returns a reference to this Future + SemiFuture& wait(Duration) &; + + /// Overload of wait(Duration) for rvalue Futures + SemiFuture&& wait(Duration) &&; + + private: + template + friend class futures::detail::FutureBase; + + using typename Base::corePtr; + + template + friend SemiFuture makeSemiFuture(Try&&); + + explicit SemiFuture(corePtr obj) : Base(obj) {} + + explicit SemiFuture(futures::detail::EmptyConstruct) noexcept + : Base(futures::detail::EmptyConstruct{}) {} +}; + +template +class Future : private futures::detail::FutureBase { + private: + using Base = futures::detail::FutureBase; + + public: + // Export public interface of FutureBase + // FutureBase is inherited privately to avoid subclasses being cast to + // a FutureBase pointer + using typename Base::value_type; + + /// Construct a Future from a value (perfect forwarding) + template < + class T2 = T, + typename = typename std::enable_if< + !isFuture::type>::value>::type> + /* implicit */ Future(T2&& val) : Base(std::forward(val)) {} + + template + /* implicit */ Future( + typename std::enable_if::value>::type* p = nullptr) + : Base(p) {} + + template < + class... Args, + typename std::enable_if::value, int>:: + type = 0> + explicit Future(in_place_t, Args&&... args) + : Base(in_place, std::forward(args)...) {} + + Future(Future const&) = delete; + // movable + Future(Future&&) noexcept; // converting move template < @@ -280,25 +364,25 @@ class Future : public SemiFuture { int>::type = 0> Future& operator=(Future&&); - /// Construct a Future from a value (perfect forwarding) - template < - class T2 = T, - typename = typename std::enable_if< - !isFuture::type>::value && - !isSemiFuture::type>::value>::type> - /* implicit */ Future(T2&& val); + using Base::cancel; + using Base::getTry; + using Base::hasException; + using Base::hasValue; + using Base::isActive; + using Base::isReady; + using Base::poll; + using Base::raise; + using Base::setCallback_; + using Base::value; + using Base::via; - template - /* implicit */ Future( - typename std::enable_if::value>::type* = nullptr); + static Future makeEmpty(); // equivalent to moved-from - template < - class... Args, - typename std::enable_if::value, int>:: - type = 0> - explicit Future(in_place_t, Args&&... args); + // not copyable + Future& operator=(Future const&) = delete; - ~Future(); + // movable + Future& operator=(Future&&) noexcept; /// Call e->drive() repeatedly until the future is fulfilled. Examples /// of DrivableExecutor include EventBase and ManualExecutor. Returns a @@ -313,9 +397,16 @@ class Future : public SemiFuture { /// Unwraps the case of a Future> instance, and returns a simple /// Future instance. template - typename std::enable_if::value, - Future::Inner>>::type - unwrap(); + typename std:: + enable_if::value, Future::Inner>>::type + unwrap(); + + /// This variant creates a new future, where the ref-qualifier && version + /// moves `this` out. This one is less efficient but avoids confusing users + /// when "return f.via(x);" fails. + inline Future via( + Executor* executor, + int8_t priority = Executor::MID_PRI) &; /** When this Future has completed, execute func which is a function that takes one of: @@ -354,8 +445,9 @@ class Future : public SemiFuture { /// /// f1.then(std::bind(&Worker::doWork, w)); template - Future::Inner> - then(R(Caller::*func)(Args...), Caller *instance); + Future::Inner> then( + R (Caller::*func)(Args...), + Caller* instance); /// Execute the callback via the given Executor. The executor doesn't stick. /// @@ -495,6 +587,15 @@ class Future : public SemiFuture { /// now. The optional Timekeeper is as with futures::sleep(). Future delayed(Duration, Timekeeper* = nullptr); + /// Block until the future is fulfilled. Returns the value (moved out), or + /// throws the exception. The future must not already have a callback. + T get(); + + /// Block until the future is fulfilled, or until timed out. Returns the + /// value (moved out), or throws the exception (which might be a TimedOut + /// exception). + T get(Duration dur); + /// Block until this Future is complete. Returns a reference to this Future. Future& wait() &; @@ -587,15 +688,18 @@ class Future : public SemiFuture { } protected: - typedef futures::detail::Core* corePtr; + friend class Promise; + template + friend class futures::detail::FutureBase; + template + friend class Future; - explicit Future(corePtr obj) : SemiFuture(obj) {} + using typename Base::corePtr; - explicit Future(futures::detail::EmptyConstruct) noexcept; + explicit Future(corePtr obj) : Base(obj) {} - friend class Promise; - template friend class Future; - friend class SemiFuture; + explicit Future(futures::detail::EmptyConstruct) noexcept + : Base(futures::detail::EmptyConstruct{}) {} template friend Future makeFuture(Try&&); diff --git a/folly/futures/Promise.h b/folly/futures/Promise.h index 905ce5dd..7bb58d1e 100644 --- a/folly/futures/Promise.h +++ b/folly/futures/Promise.h @@ -29,6 +29,8 @@ template class Future; namespace futures { namespace detail { +template +class FutureBase; struct EmptyConstruct {}; template class CoreCallbackState; @@ -110,8 +112,11 @@ class Promise { private: typedef typename Future::corePtr corePtr; template + friend class futures::detail::FutureBase; + template friend class SemiFuture; - template friend class Future; + template + friend class Future; template friend class futures::detail::CoreCallbackState; diff --git a/folly/futures/test/SemiFutureTest.cpp b/folly/futures/test/SemiFutureTest.cpp index 1471d42b..29afb08f 100644 --- a/folly/futures/test/SemiFutureTest.cpp +++ b/folly/futures/test/SemiFutureTest.cpp @@ -203,7 +203,7 @@ TEST(SemiFuture, MakeFutureFromSemiFutureLValue) { Promise p; std::atomic result{0}; auto f = SemiFuture{p.getFuture()}; - auto future = f.via(&e).then([&](int value) { + auto future = std::move(f).via(&e).then([&](int value) { result = value; return value; });