Commit 46af81b6 authored by Lee Howes's avatar Lee Howes Committed by Facebook Github Bot

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
parent cceed93a
...@@ -129,102 +129,25 @@ inline auto makeCoreCallbackState(Promise<T>&& p, F&& f) noexcept( ...@@ -129,102 +129,25 @@ inline auto makeCoreCallbackState(Promise<T>&& p, F&& f) noexcept(
return CoreCallbackState<T, _t<std::decay<F>>>( return CoreCallbackState<T, _t<std::decay<F>>>(
std::move(p), std::forward<F>(f)); std::move(p), std::forward<F>(f));
} }
} // namespace detail
} // namespace futures
template <class T>
SemiFuture<typename std::decay<T>::type> makeSemiFuture(T&& t) {
return makeSemiFuture(Try<typename std::decay<T>::type>(std::forward<T>(t)));
}
// makeSemiFutureWith(SemiFuture<T>()) -> SemiFuture<T>
template <class F>
typename std::enable_if<
isSemiFuture<typename std::result_of<F()>::type>::value,
typename std::result_of<F()>::type>::type
makeSemiFutureWith(F&& func) {
using InnerType =
typename isSemiFuture<typename std::result_of<F()>::type>::Inner;
try {
return std::forward<F>(func)();
} catch (std::exception& e) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception(), e));
} catch (...) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception()));
}
}
// makeSemiFutureWith(T()) -> SemiFuture<T>
// makeSemiFutureWith(void()) -> SemiFuture<Unit>
template <class F>
typename std::enable_if<
!(isSemiFuture<typename std::result_of<F()>::type>::value),
SemiFuture<Unit::LiftT<typename std::result_of<F()>::type>>>::type
makeSemiFutureWith(F&& func) {
using LiftedResult = Unit::LiftT<typename std::result_of<F()>::type>;
return makeSemiFuture<LiftedResult>(
makeTryWith([&func]() mutable { return std::forward<F>(func)(); }));
}
template <class T>
SemiFuture<T> makeSemiFuture(std::exception_ptr const& e) {
return makeSemiFuture(Try<T>(e));
}
template <class T> template <class T>
SemiFuture<T> makeSemiFuture(exception_wrapper ew) { FutureBase<T>::FutureBase(SemiFuture<T>&& other) noexcept : core_(other.core_) {
return makeSemiFuture(Try<T>(std::move(ew)));
}
template <class T, class E>
typename std::
enable_if<std::is_base_of<std::exception, E>::value, SemiFuture<T>>::type
makeSemiFuture(E const& e) {
return makeSemiFuture(Try<T>(make_exception_wrapper<E>(e)));
}
template <class T>
SemiFuture<T> makeSemiFuture(Try<T>&& t) {
return SemiFuture<T>(new futures::detail::Core<T>(std::move(t)));
}
template <class T>
SemiFuture<T> SemiFuture<T>::makeEmpty() {
return SemiFuture<T>(futures::detail::EmptyConstruct{});
}
template <class T>
SemiFuture<T>::SemiFuture(SemiFuture<T>&& other) noexcept : core_(other.core_) {
other.core_ = nullptr; other.core_ = nullptr;
} }
template <class T> template <class T>
SemiFuture<T>& SemiFuture<T>::operator=(SemiFuture<T>&& other) noexcept { FutureBase<T>::FutureBase(Future<T>&& other) noexcept : core_(other.core_) {
std::swap(core_, other.core_);
return *this;
}
template <class T>
SemiFuture<T>::SemiFuture(Future<T>&& other) noexcept : core_(other.core_) {
other.core_ = nullptr; other.core_ = nullptr;
} }
template <class T>
SemiFuture<T>& SemiFuture<T>::operator=(Future<T>&& other) noexcept {
std::swap(core_, other.core_);
return *this;
}
template <class T> template <class T>
template <class T2, typename> template <class T2, typename>
SemiFuture<T>::SemiFuture(T2&& val) FutureBase<T>::FutureBase(T2&& val)
: core_(new futures::detail::Core<T>(Try<T>(std::forward<T2>(val)))) {} : core_(new futures::detail::Core<T>(Try<T>(std::forward<T2>(val)))) {}
template <class T> template <class T>
template <typename T2> template <typename T2>
SemiFuture<T>::SemiFuture( FutureBase<T>::FutureBase(
typename std::enable_if<std::is_same<Unit, T2>::value>::type*) typename std::enable_if<std::is_same<Unit, T2>::value>::type*)
: core_(new futures::detail::Core<T>(Try<T>(T()))) {} : core_(new futures::detail::Core<T>(Try<T>(T()))) {}
...@@ -233,52 +156,52 @@ template < ...@@ -233,52 +156,52 @@ template <
class... Args, class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>:: typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type> type>
SemiFuture<T>::SemiFuture(in_place_t, Args&&... args) FutureBase<T>::FutureBase(in_place_t, Args&&... args)
: core_( : core_(
new futures::detail::Core<T>(in_place, std::forward<Args>(args)...)) { new futures::detail::Core<T>(in_place, std::forward<Args>(args)...)) {
} }
template <class T> template <class T>
SemiFuture<T>::~SemiFuture() { template <class FutureType>
detach(); void FutureBase<T>::assign(FutureType& other) noexcept {
std::swap(core_, other.core_);
} }
// This must be defined after the constructors to avoid a bug in MSVC template <class T>
// https://connect.microsoft.com/VisualStudio/feedback/details/3142777/out-of-line-constructor-definition-after-implicit-reference-causes-incorrect-c2244 FutureBase<T>::~FutureBase() {
inline SemiFuture<Unit> makeSemiFuture() { detach();
return makeSemiFuture(Unit{});
} }
template <class T> template <class T>
T& SemiFuture<T>::value() & { T& FutureBase<T>::value() & {
throwIfInvalid(); throwIfInvalid();
return core_->getTry().value(); return core_->getTry().value();
} }
template <class T> template <class T>
T const& SemiFuture<T>::value() const& { T const& FutureBase<T>::value() const& {
throwIfInvalid(); throwIfInvalid();
return core_->getTry().value(); return core_->getTry().value();
} }
template <class T> template <class T>
T&& SemiFuture<T>::value() && { T&& FutureBase<T>::value() && {
throwIfInvalid(); throwIfInvalid();
return std::move(core_->getTry().value()); return std::move(core_->getTry().value());
} }
template <class T> template <class T>
T const&& SemiFuture<T>::value() const&& { T const&& FutureBase<T>::value() const&& {
throwIfInvalid(); throwIfInvalid();
return std::move(core_->getTry().value()); return std::move(core_->getTry().value());
} }
template <class T> template <class T>
inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) && { inline Future<T> FutureBase<T>::via(Executor* executor, int8_t priority) && {
throwIfInvalid(); throwIfInvalid();
setExecutor(executor, priority); setExecutor(executor, priority);
...@@ -289,36 +212,23 @@ inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) && { ...@@ -289,36 +212,23 @@ inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) && {
} }
template <class T> template <class T>
inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) & { bool FutureBase<T>::isReady() const {
throwIfInvalid();
Promise<T> p;
auto f = p.getFuture();
auto func = [p = std::move(p)](Try<T>&& t) mutable {
p.setTry(std::move(t));
};
using R = futures::detail::callableResult<T, decltype(func)>;
thenImplementation<decltype(func), R>(std::move(func), typename R::Arg());
return std::move(f).via(executor, priority);
}
template <class T>
bool SemiFuture<T>::isReady() const {
throwIfInvalid(); throwIfInvalid();
return core_->ready(); return core_->ready();
} }
template <class T> template <class T>
bool SemiFuture<T>::hasValue() { bool FutureBase<T>::hasValue() {
return getTry().hasValue(); return getTry().hasValue();
} }
template <class T> template <class T>
bool SemiFuture<T>::hasException() { bool FutureBase<T>::hasException() {
return getTry().hasException(); return getTry().hasException();
} }
template <class T> template <class T>
void SemiFuture<T>::detach() { void FutureBase<T>::detach() {
if (core_) { if (core_) {
core_->detachFuture(); core_->detachFuture();
core_ = nullptr; core_ = nullptr;
...@@ -326,21 +236,21 @@ void SemiFuture<T>::detach() { ...@@ -326,21 +236,21 @@ void SemiFuture<T>::detach() {
} }
template <class T> template <class T>
Try<T>& SemiFuture<T>::getTry() { Try<T>& FutureBase<T>::getTry() {
throwIfInvalid(); throwIfInvalid();
return core_->getTry(); return core_->getTry();
} }
template <class T> template <class T>
void SemiFuture<T>::throwIfInvalid() const { void FutureBase<T>::throwIfInvalid() const {
if (!core_) { if (!core_) {
throwNoState(); throwNoState();
} }
} }
template <class T> template <class T>
Optional<Try<T>> SemiFuture<T>::poll() { Optional<Try<T>> FutureBase<T>::poll() {
Optional<Try<T>> o; Optional<Try<T>> o;
if (core_->ready()) { if (core_->ready()) {
o = std::move(core_->getTry()); o = std::move(core_->getTry());
...@@ -349,104 +259,21 @@ Optional<Try<T>> SemiFuture<T>::poll() { ...@@ -349,104 +259,21 @@ Optional<Try<T>> SemiFuture<T>::poll() {
} }
template <class T> template <class T>
void SemiFuture<T>::raise(exception_wrapper exception) { void FutureBase<T>::raise(exception_wrapper exception) {
core_->raise(std::move(exception)); core_->raise(std::move(exception));
} }
template <class T> template <class T>
template <class F> template <class F>
void SemiFuture<T>::setCallback_(F&& func) { void FutureBase<T>::setCallback_(F&& func) {
throwIfInvalid(); throwIfInvalid();
core_->setCallback(std::forward<F>(func)); core_->setCallback(std::forward<F>(func));
} }
template <class T> template <class T>
SemiFuture<T>::SemiFuture(futures::detail::EmptyConstruct) noexcept FutureBase<T>::FutureBase(futures::detail::EmptyConstruct) noexcept
: core_(nullptr) {} : core_(nullptr) {}
template <class T>
Future<T> Future<T>::makeEmpty() {
return Future<T>(futures::detail::EmptyConstruct{});
}
template <class T>
Future<T>::Future(Future<T>&& other) noexcept
: SemiFuture<T>(std::move(other)) {}
template <class T>
Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
SemiFuture<T>::operator=(SemiFuture<T>{std::move(other)});
return *this;
}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
std::is_convertible<T2&&, T>::value,
int>::type>
Future<T>::Future(Future<T2>&& other)
: Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
!std::is_convertible<T2&&, T>::value,
int>::type>
Future<T>::Future(Future<T2>&& other)
: Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value,
int>::type>
Future<T>& Future<T>::operator=(Future<T2>&& other) {
return operator=(
std::move(other).then([](T2&& v) { return T(std::move(v)); }));
}
// TODO: isSemiFuture
template <class T>
template <class T2, typename>
Future<T>::Future(T2&& val) : SemiFuture<T>(std::forward<T2>(val)) {}
template <class T>
template <typename T2>
Future<T>::Future(typename std::enable_if<std::is_same<Unit, T2>::value>::type*)
: SemiFuture<T>() {}
template <class T>
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type>
Future<T>::Future(in_place_t, Args&&... args)
: SemiFuture<T>(in_place, std::forward<Args>(args)...) {}
template <class T>
Future<T>::~Future() {
}
// unwrap
template <class T>
template <class F>
typename std::enable_if<isFuture<F>::value,
Future<typename isFuture<T>::Inner>>::type
Future<T>::unwrap() {
return then([](Future<typename isFuture<T>::Inner> internal_future) {
return internal_future;
});
}
// then // then
// Variant: returns a value // Variant: returns a value
...@@ -454,7 +281,7 @@ Future<T>::unwrap() { ...@@ -454,7 +281,7 @@ Future<T>::unwrap() {
template <class T> template <class T>
template <typename F, typename R, bool isTry, typename... Args> template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
SemiFuture<T>::thenImplementation( FutureBase<T>::thenImplementation(
F&& func, F&& func,
futures::detail::argResult<isTry, F, Args...>) { futures::detail::argResult<isTry, F, Args...>) {
static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
...@@ -517,7 +344,7 @@ SemiFuture<T>::thenImplementation( ...@@ -517,7 +344,7 @@ SemiFuture<T>::thenImplementation(
template <class T> template <class T>
template <typename F, typename R, bool isTry, typename... Args> template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
SemiFuture<T>::thenImplementation( FutureBase<T>::thenImplementation(
F&& func, F&& func,
futures::detail::argResult<isTry, F, Args...>) { futures::detail::argResult<isTry, F, Args...>) {
static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
...@@ -550,6 +377,181 @@ SemiFuture<T>::thenImplementation( ...@@ -550,6 +377,181 @@ SemiFuture<T>::thenImplementation(
return f; return f;
} }
} // namespace detail
} // namespace futures
template <class T>
SemiFuture<typename std::decay<T>::type> makeSemiFuture(T&& t) {
return makeSemiFuture(Try<typename std::decay<T>::type>(std::forward<T>(t)));
}
// makeSemiFutureWith(SemiFuture<T>()) -> SemiFuture<T>
template <class F>
typename std::enable_if<
isSemiFuture<typename std::result_of<F()>::type>::value,
typename std::result_of<F()>::type>::type
makeSemiFutureWith(F&& func) {
using InnerType =
typename isSemiFuture<typename std::result_of<F()>::type>::Inner;
try {
return std::forward<F>(func)();
} catch (std::exception& e) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception(), e));
} catch (...) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception()));
}
}
// makeSemiFutureWith(T()) -> SemiFuture<T>
// makeSemiFutureWith(void()) -> SemiFuture<Unit>
template <class F>
typename std::enable_if<
!(isSemiFuture<typename std::result_of<F()>::type>::value),
SemiFuture<Unit::LiftT<typename std::result_of<F()>::type>>>::type
makeSemiFutureWith(F&& func) {
using LiftedResult = Unit::LiftT<typename std::result_of<F()>::type>;
return makeSemiFuture<LiftedResult>(
makeTryWith([&func]() mutable { return std::forward<F>(func)(); }));
}
template <class T>
SemiFuture<T> makeSemiFuture(std::exception_ptr const& e) {
return makeSemiFuture(Try<T>(e));
}
template <class T>
SemiFuture<T> makeSemiFuture(exception_wrapper ew) {
return makeSemiFuture(Try<T>(std::move(ew)));
}
template <class T, class E>
typename std::
enable_if<std::is_base_of<std::exception, E>::value, SemiFuture<T>>::type
makeSemiFuture(E const& e) {
return makeSemiFuture(Try<T>(make_exception_wrapper<E>(e)));
}
template <class T>
SemiFuture<T> makeSemiFuture(Try<T>&& t) {
return SemiFuture<T>(new futures::detail::Core<T>(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<Unit> makeSemiFuture() {
return makeSemiFuture(Unit{});
}
template <class T>
SemiFuture<T> SemiFuture<T>::makeEmpty() {
return SemiFuture<T>(futures::detail::EmptyConstruct{});
}
template <class T>
SemiFuture<T>::SemiFuture(SemiFuture<T>&& other) noexcept
: futures::detail::FutureBase<T>(std::move(other)) {}
template <class T>
SemiFuture<T>::SemiFuture(Future<T>&& other) noexcept
: futures::detail::FutureBase<T>(std::move(other)) {
// SemiFuture should not have an executor on construction
if (this->core_) {
this->setExecutor(nullptr);
}
}
template <class T>
SemiFuture<T>& SemiFuture<T>::operator=(SemiFuture<T>&& other) noexcept {
this->assign(other);
return *this;
}
template <class T>
SemiFuture<T>& SemiFuture<T>::operator=(Future<T>&& other) noexcept {
this->assign(other);
// SemiFuture should not have an executor on construction
if (this->core_) {
this->setExecutor(nullptr);
}
return *this;
}
template <class T>
Future<T> Future<T>::makeEmpty() {
return Future<T>(futures::detail::EmptyConstruct{});
}
template <class T>
Future<T>::Future(Future<T>&& other) noexcept
: futures::detail::FutureBase<T>(std::move(other)) {}
template <class T>
Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
this->assign(other);
return *this;
}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
std::is_convertible<T2&&, T>::value,
int>::type>
Future<T>::Future(Future<T2>&& other)
: Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
!std::is_convertible<T2&&, T>::value,
int>::type>
Future<T>::Future(Future<T2>&& other)
: Future(std::move(other).then([](T2&& v) { return T(std::move(v)); })) {}
template <class T>
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value,
int>::type>
Future<T>& Future<T>::operator=(Future<T2>&& other) {
return operator=(
std::move(other).then([](T2&& v) { return T(std::move(v)); }));
}
// unwrap
template <class T>
template <class F>
typename std::
enable_if<isFuture<F>::value, Future<typename isFuture<T>::Inner>>::type
Future<T>::unwrap() {
return then([](Future<typename isFuture<T>::Inner> internal_future) {
return internal_future;
});
}
template <class T>
inline Future<T> Future<T>::via(Executor* executor, int8_t priority) & {
this->throwIfInvalid();
Promise<T> p;
auto f = p.getFuture();
auto func = [p = std::move(p)](Try<T>&& t) mutable {
p.setTry(std::move(t));
};
using R = futures::detail::callableResult<T, decltype(func)>;
this->template thenImplementation<decltype(func), R>(
std::move(func), typename R::Arg());
return std::move(f).via(executor, priority);
}
template <typename T> template <typename T>
template <typename R, typename Caller, typename... Args> template <typename R, typename Caller, typename... Args>
...@@ -733,10 +735,6 @@ auto via(Executor* x, Func&& func) ...@@ -733,10 +735,6 @@ auto via(Executor* x, Func&& func)
return via(x).then(std::forward<Func>(func)); return via(x).then(std::forward<Func>(func));
} }
template <class T>
Future<T>::Future(futures::detail::EmptyConstruct) noexcept
: SemiFuture<T>(futures::detail::EmptyConstruct{}) {}
// makeFuture // makeFuture
template <class T> template <class T>
...@@ -1294,7 +1292,7 @@ void waitImpl(FutureType& f, Duration dur) { ...@@ -1294,7 +1292,7 @@ void waitImpl(FutureType& f, Duration dur) {
Promise<T> promise; Promise<T> promise;
auto ret = promise.getFuture(); auto ret = promise.getFuture();
auto baton = std::make_shared<FutureBatonType>(); auto baton = std::make_shared<FutureBatonType>();
f.setCallback_([ baton, promise = std::move(promise) ](Try<T> && t) mutable { f.setCallback_([baton, promise = std::move(promise)](Try<T>&& t) mutable {
promise.setTry(std::move(t)); promise.setTry(std::move(t));
baton->post(); baton->post();
}); });
...@@ -1347,12 +1345,12 @@ SemiFuture<T>&& SemiFuture<T>::wait(Duration dur) && { ...@@ -1347,12 +1345,12 @@ SemiFuture<T>&& SemiFuture<T>::wait(Duration dur) && {
} }
template <class T> template <class T>
T SemiFuture<T>::get() { T SemiFuture<T>::get() && {
return std::move(wait().value()); return std::move(wait().value());
} }
template <class T> template <class T>
T SemiFuture<T>::get(Duration dur) { T SemiFuture<T>::get(Duration dur) && {
wait(dur); wait(dur);
if (this->isReady()) { if (this->isReady()) {
return std::move(this->value()); return std::move(this->value());
...@@ -1397,6 +1395,21 @@ Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && { ...@@ -1397,6 +1395,21 @@ Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
return std::move(*this); return std::move(*this);
} }
template <class T>
T Future<T>::get() {
return std::move(wait().value());
}
template <class T>
T Future<T>::get(Duration dur) {
wait(dur);
if (this->isReady()) {
return std::move(this->value());
} else {
throwTimedOut();
}
}
template <class T> template <class T>
T Future<T>::getVia(DrivableExecutor* e) { T Future<T>::getVia(DrivableExecutor* e) {
return std::move(waitVia(e).value()); return std::move(waitVia(e).value());
......
...@@ -45,42 +45,41 @@ template <class T> ...@@ -45,42 +45,41 @@ template <class T>
class Future; class Future;
template <class T> template <class T>
class SemiFuture { class SemiFuture;
namespace futures {
namespace detail {
template <class T>
class FutureBase {
public: public:
typedef T value_type; typedef T value_type;
static SemiFuture<T> 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<T>&&) noexcept;
SemiFuture& operator=(Future<T>&&) noexcept;
/// Construct a Future from a value (perfect forwarding) /// Construct a Future from a value (perfect forwarding)
template < template <
class T2 = T, class T2 = T,
typename = typename std::enable_if< typename = typename std::enable_if<
!isFuture<typename std::decay<T2>::type>::value>::type> !isFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ SemiFuture(T2&& val); /* implicit */ FutureBase(T2&& val);
template <class T2 = T> template <class T2 = T>
/* implicit */ SemiFuture( /* implicit */ FutureBase(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* = nullptr); typename std::enable_if<std::is_same<Unit, T2>::value>::type*);
template < template <
class... Args, class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>:: typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0> type = 0>
explicit SemiFuture(in_place_t, Args&&... args); explicit FutureBase(in_place_t, Args&&... args);
FutureBase(FutureBase<T> const&) = delete;
FutureBase(SemiFuture<T>&&) noexcept;
FutureBase(Future<T>&&) noexcept;
~SemiFuture(); // not copyable
FutureBase(Future<T> const&) = delete;
FutureBase(SemiFuture<T> const&) = delete;
~FutureBase();
/// Returns a reference to the result, with a reference category and const- /// Returns a reference to the result, with a reference category and const-
/// qualification equivalent to the reference category and const-qualification /// qualification equivalent to the reference category and const-qualification
...@@ -120,13 +119,6 @@ class SemiFuture { ...@@ -120,13 +119,6 @@ class SemiFuture {
Executor* executor, Executor* executor,
int8_t priority = Executor::MID_PRI) &&; 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<T> via(
Executor* executor,
int8_t priority = Executor::MID_PRI) &;
/** True when the result (or exception) is ready. */ /** True when the result (or exception) is ready. */
bool isReady() const; bool isReady() const;
...@@ -144,28 +136,6 @@ class SemiFuture { ...@@ -144,28 +136,6 @@ class SemiFuture {
/// Note that this moves the Try<T> out. /// Note that this moves the Try<T> out.
Optional<Try<T>> poll(); Optional<Try<T>> 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<T>& wait() &;
/// Overload of wait() for rvalue Futures
SemiFuture<T>&& wait() &&;
/// Block until this Future is complete or until the given Duration passes.
/// Returns a reference to this Future
SemiFuture<T>& wait(Duration) &;
/// Overload of wait(Duration) for rvalue Futures
SemiFuture<T>&& wait(Duration) &&;
/// This is not the method you're looking for. /// 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 /// This needs to be public because it's used by make* and when*, and it's
...@@ -199,25 +169,27 @@ class SemiFuture { ...@@ -199,25 +169,27 @@ class SemiFuture {
} }
protected: protected:
typedef futures::detail::Core<T>* corePtr; friend class Promise<T>;
template <class>
friend class SemiFuture;
template <class>
friend class Future;
using corePtr = futures::detail::Core<T>*;
// shared core state object // shared core state object
corePtr core_; 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 detach();
void throwIfInvalid() const; void throwIfInvalid() const;
friend class Promise<T>; template <class FutureType>
template <class> void assign(FutureType&) noexcept;
friend class SemiFuture;
template <class T2>
friend SemiFuture<T2> makeSemiFuture(Try<T2>&&);
Executor* getExecutor() { Executor* getExecutor() {
return core_->getExecutor(); return core_->getExecutor();
...@@ -239,21 +211,133 @@ class SemiFuture { ...@@ -239,21 +211,133 @@ class SemiFuture {
typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>); thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>);
}; };
} // namespace detail
} // namespace futures
template <class T> template <class T>
class Future : public SemiFuture<T> { class SemiFuture : private futures::detail::FutureBase<T> {
private:
using Base = futures::detail::FutureBase<T>;
public: public:
typedef T value_type; static SemiFuture<T> makeEmpty(); // equivalent to moved-from
static Future<T> 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 /// Construct a Future from a value (perfect forwarding)
Future(Future const&) = delete; template <
Future& operator=(Future const&) = delete; class T2 = T,
typename = typename std::enable_if<
!isFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ SemiFuture(T2&& val) : Base(std::forward<T2>(val)) {}
template <class T2 = T>
/* implicit */ SemiFuture(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
: Base(p) {}
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0>
explicit SemiFuture(in_place_t, Args&&... args)
: Base(in_place, std::forward<Args>(args)...) {}
SemiFuture(SemiFuture<T> const&) = delete;
// movable // movable
Future(Future&&) noexcept; SemiFuture(SemiFuture<T>&&) noexcept;
Future& operator=(Future&&) noexcept; // safe move-constructabilty from Future
/* implicit */ SemiFuture(Future<T>&&) 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<T>&&) 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<T>& wait() &;
/// Overload of wait() for rvalue Futures
SemiFuture<T>&& wait() &&;
/// Block until this Future is complete or until the given Duration passes.
/// Returns a reference to this Future
SemiFuture<T>& wait(Duration) &;
/// Overload of wait(Duration) for rvalue Futures
SemiFuture<T>&& wait(Duration) &&;
private:
template <class>
friend class futures::detail::FutureBase;
using typename Base::corePtr;
template <class T2>
friend SemiFuture<T2> makeSemiFuture(Try<T2>&&);
explicit SemiFuture(corePtr obj) : Base(obj) {}
explicit SemiFuture(futures::detail::EmptyConstruct) noexcept
: Base(futures::detail::EmptyConstruct{}) {}
};
template <class T>
class Future : private futures::detail::FutureBase<T> {
private:
using Base = futures::detail::FutureBase<T>;
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<typename std::decay<T2>::type>::value>::type>
/* implicit */ Future(T2&& val) : Base(std::forward<T2>(val)) {}
template <class T2 = T>
/* implicit */ Future(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
: Base(p) {}
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0>
explicit Future(in_place_t, Args&&... args)
: Base(in_place, std::forward<Args>(args)...) {}
Future(Future<T> const&) = delete;
// movable
Future(Future<T>&&) noexcept;
// converting move // converting move
template < template <
...@@ -280,25 +364,25 @@ class Future : public SemiFuture<T> { ...@@ -280,25 +364,25 @@ class Future : public SemiFuture<T> {
int>::type = 0> int>::type = 0>
Future& operator=(Future<T2>&&); Future& operator=(Future<T2>&&);
/// Construct a Future from a value (perfect forwarding) using Base::cancel;
template < using Base::getTry;
class T2 = T, using Base::hasException;
typename = typename std::enable_if< using Base::hasValue;
!isFuture<typename std::decay<T2>::type>::value && using Base::isActive;
!isSemiFuture<typename std::decay<T2>::type>::value>::type> using Base::isReady;
/* implicit */ Future(T2&& val); using Base::poll;
using Base::raise;
using Base::setCallback_;
using Base::value;
using Base::via;
template <class T2 = T> static Future<T> makeEmpty(); // equivalent to moved-from
/* implicit */ Future(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* = nullptr);
template < // not copyable
class... Args, Future& operator=(Future const&) = delete;
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0>
explicit Future(in_place_t, Args&&... args);
~Future(); // movable
Future& operator=(Future&&) noexcept;
/// Call e->drive() repeatedly until the future is fulfilled. Examples /// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns a /// of DrivableExecutor include EventBase and ManualExecutor. Returns a
...@@ -313,10 +397,17 @@ class Future : public SemiFuture<T> { ...@@ -313,10 +397,17 @@ class Future : public SemiFuture<T> {
/// Unwraps the case of a Future<Future<T>> instance, and returns a simple /// Unwraps the case of a Future<Future<T>> instance, and returns a simple
/// Future<T> instance. /// Future<T> instance.
template <class F = T> template <class F = T>
typename std::enable_if<isFuture<F>::value, typename std::
Future<typename isFuture<T>::Inner>>::type enable_if<isFuture<F>::value, Future<typename isFuture<T>::Inner>>::type
unwrap(); 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<T> via(
Executor* executor,
int8_t priority = Executor::MID_PRI) &;
/** When this Future has completed, execute func which is a function that /** When this Future has completed, execute func which is a function that
takes one of: takes one of:
(const) Try<T>&& (const) Try<T>&&
...@@ -354,8 +445,9 @@ class Future : public SemiFuture<T> { ...@@ -354,8 +445,9 @@ class Future : public SemiFuture<T> {
/// ///
/// f1.then(std::bind(&Worker::doWork, w)); /// f1.then(std::bind(&Worker::doWork, w));
template <typename R, typename Caller, typename... Args> template <typename R, typename Caller, typename... Args>
Future<typename isFuture<R>::Inner> Future<typename isFuture<R>::Inner> then(
then(R(Caller::*func)(Args...), Caller *instance); R (Caller::*func)(Args...),
Caller* instance);
/// Execute the callback via the given Executor. The executor doesn't stick. /// Execute the callback via the given Executor. The executor doesn't stick.
/// ///
...@@ -495,6 +587,15 @@ class Future : public SemiFuture<T> { ...@@ -495,6 +587,15 @@ class Future : public SemiFuture<T> {
/// now. The optional Timekeeper is as with futures::sleep(). /// now. The optional Timekeeper is as with futures::sleep().
Future<T> delayed(Duration, Timekeeper* = nullptr); Future<T> 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. /// Block until this Future is complete. Returns a reference to this Future.
Future<T>& wait() &; Future<T>& wait() &;
...@@ -587,15 +688,18 @@ class Future : public SemiFuture<T> { ...@@ -587,15 +688,18 @@ class Future : public SemiFuture<T> {
} }
protected: protected:
typedef futures::detail::Core<T>* corePtr; friend class Promise<T>;
template <class>
friend class futures::detail::FutureBase;
template <class>
friend class Future;
explicit Future(corePtr obj) : SemiFuture<T>(obj) {} using typename Base::corePtr;
explicit Future(futures::detail::EmptyConstruct) noexcept; explicit Future(corePtr obj) : Base(obj) {}
friend class Promise<T>; explicit Future(futures::detail::EmptyConstruct) noexcept
template <class> friend class Future; : Base(futures::detail::EmptyConstruct{}) {}
friend class SemiFuture<T>;
template <class T2> template <class T2>
friend Future<T2> makeFuture(Try<T2>&&); friend Future<T2> makeFuture(Try<T2>&&);
......
...@@ -29,6 +29,8 @@ template <class T> class Future; ...@@ -29,6 +29,8 @@ template <class T> class Future;
namespace futures { namespace futures {
namespace detail { namespace detail {
template <class T>
class FutureBase;
struct EmptyConstruct {}; struct EmptyConstruct {};
template <typename T, typename F> template <typename T, typename F>
class CoreCallbackState; class CoreCallbackState;
...@@ -110,8 +112,11 @@ class Promise { ...@@ -110,8 +112,11 @@ class Promise {
private: private:
typedef typename Future<T>::corePtr corePtr; typedef typename Future<T>::corePtr corePtr;
template <class> template <class>
friend class futures::detail::FutureBase;
template <class>
friend class SemiFuture; friend class SemiFuture;
template <class> friend class Future; template <class>
friend class Future;
template <class, class> template <class, class>
friend class futures::detail::CoreCallbackState; friend class futures::detail::CoreCallbackState;
......
...@@ -203,7 +203,7 @@ TEST(SemiFuture, MakeFutureFromSemiFutureLValue) { ...@@ -203,7 +203,7 @@ TEST(SemiFuture, MakeFutureFromSemiFutureLValue) {
Promise<int> p; Promise<int> p;
std::atomic<int> result{0}; std::atomic<int> result{0};
auto f = SemiFuture<int>{p.getFuture()}; auto f = SemiFuture<int>{p.getFuture()};
auto future = f.via(&e).then([&](int value) { auto future = std::move(f).via(&e).then([&](int value) {
result = value; result = value;
return value; return value;
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment