template <class T>
template <typename Executor>
-inline Future<T> Future<T>::via(Executor* executor) {
+inline Future<T> Future<T>::via(Executor* executor) && {
throwIfInvalid();
this->deactivate();
return std::move(*this);
}
+template <class T>
+template <typename Executor>
+inline Future<T> Future<T>::via(Executor* executor) & {
+ throwIfInvalid();
+
+ MoveWrapper<Promise<T>> p;
+ auto f = p->getFuture();
+ then([p](Try<T>&& t) mutable { p->fulfilTry(std::move(t)); });
+ return std::move(f).via(executor);
+}
+
template <class T>
bool Future<T>::isReady() const {
throwIfInvalid();
///
/// f = f.via(e).then(a);
/// f.then(b);
+ // The ref-qualifier allows for `this` to be moved out so we
+ // don't get access-after-free situations in chaining.
+ // https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/
template <typename Executor>
- Future<T> via(Executor* executor);
+ Future<T> via(Executor* executor) &&;
+
+ /// 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.
+ template <typename Executor>
+ Future<T> via(Executor* executor) &;
/** True when the result (or exception) is ready. */
bool isReady() const;
core_->deactivate();
return std::move(*this);
}
+
bool isActive() {
return core_->isActive();
}
struct ViaFixture : public testing::Test {
ViaFixture() :
- future_(makeFuture().deactivate()),
westExecutor(new ManualExecutor),
eastExecutor(new ManualExecutor),
waiter(new ManualWaiter(westExecutor)),
});
}
- Future<void> future_;
std::shared_ptr<ManualExecutor> westExecutor;
std::shared_ptr<ManualExecutor> eastExecutor;
std::shared_ptr<ManualWaiter> waiter;
EXPECT_EQ(f.value(), "start;static;class-static;class");
}
+TEST_F(ViaFixture, deactivateChain) {
+ bool flag = false;
+ auto f = makeFuture().deactivate();
+ EXPECT_FALSE(f.isActive());
+ auto f2 = f.then([&](Try<void>){ flag = true; });
+ EXPECT_FALSE(flag);
+}
+
+TEST_F(ViaFixture, deactivateActivateChain) {
+ bool flag = false;
+ // you can do this all day long with temporaries.
+ auto f1 = makeFuture().deactivate().activate().deactivate();
+ // Chaining on activate/deactivate requires an rvalue, so you have to move
+ // one of these two ways (if you're not using a temporary).
+ auto f2 = std::move(f1).activate();
+ f2.deactivate();
+ auto f3 = std::move(f2.activate());
+ f3.then([&](Try<void>){ flag = true; });
+ EXPECT_TRUE(flag);
+}
+
TEST_F(ViaFixture, thread_hops) {
auto westThreadId = std::this_thread::get_id();
- auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
+ auto f = via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).via(westExecutor.get()
TEST_F(ViaFixture, chain_vias) {
auto westThreadId = std::this_thread::get_id();
- auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
+ auto f = via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).then([=](Try<int>&& t) {
EXPECT_EQ(f.value(), 1);
}
+TEST_F(ViaFixture, bareViaAssignment) {
+ auto f = via(eastExecutor.get());
+}
+TEST_F(ViaFixture, viaAssignment) {
+ // via()&&
+ auto f = makeFuture().via(eastExecutor.get());
+ // via()&
+ auto f2 = f.via(eastExecutor.get());
+}