Commit 449e2596 authored by James Sedgwick's avatar James Sedgwick Committed by Pavlo Kushnir

Future<T>::then([](T&&)), aka next()

Summary:
variants of then() that bypass Try and forward any exceptions up to the next future. like next() from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3865.pdf

this could go a long way towards reducing spurious rethrows wherever people don't ever actually catch exceptions in their continuations, which is probably often enough.

anyone know if there's something in folly or standard library that does what my silly VoidWrapper struct does?

there's a lot of copypasta here but i'm not sure consolidating into helpers would actually be useful considering the amount of template crap, i don't feel strongly about it though.

Test Plan: added unit

Reviewed By: hans@fb.com

Subscribers: trunkagent, sammerat, fugalh, njormrod, folly-diffs@, bmatheny

FB internal diff: D1641776

Signature: t1:1641776:1414610434:c742563b8061a748fca9292fc2765081edcf9d52
parent 32a9723a
......@@ -71,6 +71,7 @@ void Future<T>::setCallback_(F&& func) {
core_->setCallback(std::move(func));
}
// Variant: f.then([](Try<T>&& t){ return t.value(); });
template <class T>
template <class F>
typename std::enable_if<
......@@ -130,6 +131,69 @@ Future<T>::then(F&& func) {
return std::move(f);
}
// Variant: f.then([](T&& t){ return t; });
template <class T>
template <class F>
typename std::enable_if<
!std::is_same<T, void>::value &&
!isFuture<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type>::value,
Future<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type> >::type
Future<T>::then(F&& func) {
typedef typename std::result_of<F(T&&)>::type B;
throwIfInvalid();
folly::MoveWrapper<Promise<B>> p;
folly::MoveWrapper<F> funcm(std::forward<F>(func));
auto f = p->getFuture();
setCallback_(
[p, funcm](Try<T>&& t) mutable {
if (t.hasException()) {
p->setException(t.getException());
} else {
p->fulfil([&]() {
return (*funcm)(std::move(t.value()));
});
}
});
return std::move(f);
}
// Variant: f.then([](){ return; });
template <class T>
template <class F>
typename std::enable_if<
std::is_same<T, void>::value &&
!isFuture<typename std::result_of<F()>::type>::value,
Future<typename std::result_of<F()>::type> >::type
Future<T>::then(F&& func) {
typedef typename std::result_of<F()>::type B;
throwIfInvalid();
folly::MoveWrapper<Promise<B>> p;
folly::MoveWrapper<F> funcm(std::forward<F>(func));
auto f = p->getFuture();
setCallback_(
[p, funcm](Try<T>&& t) mutable {
if (t.hasException()) {
p->setException(t.getException());
} else {
p->fulfil([&]() {
return (*funcm)();
});
}
});
return std::move(f);
}
// Variant: f.then([](Try<T>&& t){ return makeFuture<T>(t.value()); });
template <class T>
template <class F>
typename std::enable_if<
......@@ -163,6 +227,79 @@ Future<T>::then(F&& func) {
return std::move(f);
}
// Variant: f.then([](T&& t){ return makeFuture<T>(t); });
template <class T>
template <class F>
typename std::enable_if<
!std::is_same<T, void>::value &&
isFuture<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type>::value,
Future<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type::value_type> >::type
Future<T>::then(F&& func) {
typedef typename std::result_of<F(T&&)>::type::value_type B;
throwIfInvalid();
folly::MoveWrapper<Promise<B>> p;
folly::MoveWrapper<F> funcm(std::forward<F>(func));
auto f = p->getFuture();
setCallback_(
[p, funcm](Try<T>&& t) mutable {
if (t.hasException()) {
p->setException(t.getException());
} else {
try {
auto f2 = (*funcm)(std::move(t.value()));
f2.setCallback_([p](Try<B>&& b) mutable {
p->fulfilTry(std::move(b));
});
} catch (...) {
p->setException(std::current_exception());
}
}
});
return std::move(f);
}
// Variant: f.then([](){ return makeFuture(); });
template <class T>
template <class F>
typename std::enable_if<
std::is_same<T, void>::value &&
isFuture<typename std::result_of<F()>::type>::value,
Future<typename std::result_of<F()>::type::value_type> >::type
Future<T>::then(F&& func) {
typedef typename std::result_of<F()>::type::value_type B;
throwIfInvalid();
folly::MoveWrapper<Promise<B>> p;
folly::MoveWrapper<F> funcm(std::forward<F>(func));
auto f = p->getFuture();
setCallback_(
[p, funcm](Try<T>&& t) mutable {
if (t.hasException()) {
p->setException(t.getException());
} else {
try {
auto f2 = (*funcm)();
f2.setCallback_([p](Try<B>&& b) mutable {
p->fulfilTry(std::move(b));
});
} catch (...) {
p->setException(std::current_exception());
}
}
});
return std::move(f);
}
template <class T>
Future<void> Future<T>::then() {
return then([] (Try<T>&& t) {});
......
......@@ -32,7 +32,16 @@ namespace folly { namespace wangle {
namespace detail {
template <class> struct Core;
template <class...> struct VariadicContext;
template <class T>
struct AliasIfVoid {
typedef typename std::conditional<
std::is_same<T, void>::value,
int,
T>::type type;
};
}
template <class> struct Promise;
template <typename T> struct isFuture;
......@@ -113,6 +122,30 @@ class Future {
Future<typename std::result_of<F(Try<T>&&)>::type> >::type
then(F&& func);
/// Variant where func takes a T directly, bypassing a try. Any exceptions
/// will be implicitly passed on to the resultant Future.
///
/// Future<int> f = makeFuture<int>(42).then([](int i) { return i+1; });
template <class F>
typename std::enable_if<
!std::is_same<T, void>::value &&
!isFuture<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type>::value,
Future<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type> >::type
then(F&& func);
/// Like the above variant, but for void futures. That is, func takes no
/// argument.
///
/// Future<int> f = makeFuture().then([] { return 42; });
template <class F>
typename std::enable_if<
std::is_same<T, void>::value &&
!isFuture<typename std::result_of<F()>::type>::value,
Future<typename std::result_of<F()>::type> >::type
then(F&& func);
/// Variant where func returns a Future<T> instead of a T. e.g.
///
/// Future<string> f2 = f1.then(
......@@ -123,6 +156,33 @@ class Future {
Future<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
then(F&& func);
/// Variant where func returns a Future<T2> and takes a T directly, bypassing
/// a Try. Any exceptions will be implicitly passed on to the resultant
/// Future. For example,
///
/// Future<int> f = makeFuture<int>(42).then(
/// [](int i) { return makeFuture<int>(i+1); });
template <class F>
typename std::enable_if<
!std::is_same<T, void>::value &&
isFuture<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type>::value,
Future<typename std::result_of<
F(typename detail::AliasIfVoid<T>::type&&)>::type::value_type> >::type
then(F&& func);
/// Like the above variant, but for void futures. That is, func takes no
/// argument and returns a future.
///
/// Future<int> f = makeFuture().then(
/// [] { return makeFuture<int>(42); });
template <class F>
typename std::enable_if<
std::is_same<T, void>::value &&
isFuture<typename std::result_of<F()>::type>::value,
Future<typename std::result_of<F()>::type::value_type> >::type
then(F&& func);
/// Variant where func is an ordinary function (static method, method)
///
/// R doWork(Try<T>&&);
......@@ -172,6 +232,28 @@ class Future {
});
}
// Same as above, but func takes void instead of Try<void>&&
template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
typename std::enable_if<
std::is_same<T, void>::value && !isFuture<R>::value, Future<R>>::type
inline then(Caller *instance, R(Caller::*func)()) {
return then([instance, func]() {
return (instance->*func)();
});
}
// Same as above, but func takes T&& instead of Try<T>&&
template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
typename std::enable_if<
!std::is_same<T, void>::value && !isFuture<R>::value, Future<R>>::type
inline then(
Caller *instance,
R(Caller::*func)(typename detail::AliasIfVoid<T>::type&&)) {
return then([instance, func](T&& t) {
return (instance->*func)(std::move(t));
});
}
/// Variant where func returns a Future<R> instead of a R. e.g.
///
/// struct Worker {
......@@ -187,6 +269,28 @@ class Future {
});
}
// Same as above, but func takes void instead of Try<void>&&
template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
typename std::enable_if<
std::is_same<T, void>::value && isFuture<R>::value, R>::type
inline then(Caller *instance, R(Caller::*func)()) {
return then([instance, func]() {
return (instance->*func)();
});
}
// Same as above, but func takes T&& instead of Try<T>&&
template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
typename std::enable_if<
!std::is_same<T, void>::value && isFuture<R>::value, R>::type
inline then(
Caller *instance,
R(Caller::*func)(typename detail::AliasIfVoid<T>::type&&)) {
return then([instance, func](T&& t) {
return (instance->*func)(std::move(t));
});
}
/// Convenience method for ignoring the value and creating a Future<void>.
/// Exceptions still propagate.
Future<void> then();
......
......@@ -19,6 +19,8 @@
#include <type_traits>
#include <exception>
#include <algorithm>
#include <folly/Likely.h>
#include <folly/wangle/WangleException.h>
namespace folly { namespace wangle {
......@@ -65,6 +67,14 @@ class Try {
bool hasValue() const { return contains_ == Contains::VALUE; }
bool hasException() const { return contains_ == Contains::EXCEPTION; }
std::exception_ptr getException() const {
if (UNLIKELY(!hasException())) {
throw WangleException(
"getException(): Try does not contain an exception");
}
return e_;
}
private:
Contains contains_;
union {
......@@ -87,6 +97,14 @@ class Try<void> {
bool hasValue() const { return hasValue_; }
bool hasException() const { return !hasValue_; }
std::exception_ptr getException() const {
if (UNLIKELY(!hasException())) {
throw WangleException(
"getException(): Try does not contain an exception");
}
return e_;
}
private:
bool hasValue_;
std::exception_ptr e_;
......
......@@ -69,7 +69,7 @@ TEST(Future, special) {
EXPECT_TRUE(std::is_move_assignable<Future<int>>::value);
}
TEST(Future, then) {
TEST(Future, thenTry) {
bool flag = false;
makeFuture<int>(42).then([&](Try<int>&& t) {
......@@ -95,6 +95,44 @@ TEST(Future, then) {
EXPECT_TRUE(f.isReady());
}
TEST(Future, thenValue) {
bool flag = false;
makeFuture<int>(42).then([&](int i){
EXPECT_EQ(42, i);
flag = true;
});
EXPECT_TRUE(flag); flag = false;
makeFuture<int>(42)
.then([](int i){ return i; })
.then([&](int i) { flag = true; EXPECT_EQ(42, i); });
EXPECT_TRUE(flag); flag = false;
makeFuture().then([&]{
flag = true;
});
EXPECT_TRUE(flag); flag = false;
auto f = makeFuture<int>(eggs).then([&](int i){});
EXPECT_THROW(f.value(), eggs_t);
f = makeFuture<void>(eggs).then([&]{});
EXPECT_THROW(f.value(), eggs_t);
}
TEST(Future, thenValueFuture) {
bool flag = false;
makeFuture<int>(42)
.then([](int i){ return makeFuture<int>(std::move(i)); })
.then([&](Try<int>&& t) { flag = true; EXPECT_EQ(42, t.value()); });
EXPECT_TRUE(flag); flag = false;
makeFuture()
.then([]{ return makeFuture(); })
.then([&](Try<void>&& t) { flag = true; });
EXPECT_TRUE(flag); flag = false;
}
static string doWorkStatic(Try<string>&& t) {
return t.value() + ";static";
}
......
// This file is @generated by thens.rb
// This file is @generated by thens.rb. Do not edit directly.
// TODO: fails to compile with clang:dev. See task #4412111
#ifndef __clang__
#include <folly/wangle/test/Thens.h>
#ifndef __clang__
// TODO: fails to compile with clang:dev. See task #4412111
TEST(Future, thenVariants) {
SomeClass anObject;
Executor* anExecutor;
{Future<B> f = someFuture<A>().then(aFunction<Future<B>, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&aFunction<Future<B>, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<Future<B>, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<Future<B>, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(aStdFunction<Future<B>, Try<A>&&>());}
{Future<B> f = someFuture<A>().then([&](Try<A>&&){return someFuture<B>();});}
{Future<B> f = someFuture<A>().then(aFunction<B, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&aFunction<Future<B>, A&&>);}
{Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<Future<B>, A&&>);}
{Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<Future<B>, A&&>);}
{Future<B> f = someFuture<A>().then(aStdFunction<Future<B>, A&&>());}
{Future<B> f = someFuture<A>().then([&](A&&){return someFuture<B>();});}
{Future<B> f = someFuture<A>().then(&aFunction<B, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<B, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<B, Try<A>&&>);}
{Future<B> f = someFuture<A>().then(aStdFunction<B, Try<A>&&>());}
{Future<B> f = someFuture<A>().then([&](Try<A>&&){return B();});}
{Future<B> f = someFuture<A>().then(&aFunction<B, A&&>);}
{Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<B, A&&>);}
{Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<B, A&&>);}
{Future<B> f = someFuture<A>().then(aStdFunction<B, A&&>());}
{Future<B> f = someFuture<A>().then([&](A&&){return B();});}
}
#endif
......@@ -35,7 +35,7 @@ param_types = [
#"Try<A> const&",
#"Try<A>",
#"Try<A>&",
#"A&&",
"A&&",
#"A const&",
#"A",
#"A&",
......@@ -47,7 +47,7 @@ tests = (
param_types.map { |param|
both = "#{ret}, #{param}"
[
["aFunction<#{both}>"],
["&aFunction<#{both}>"],
["&SomeClass::aStaticMethod<#{both}>"],
# TODO switch these around (std::bind-style)
["&anObject", "&SomeClass::aMethod<#{both}>"],
......@@ -68,6 +68,9 @@ print <<EOF
#include <folly/wangle/test/Thens.h>
#ifndef __clang__
// TODO: fails to compile with clang:dev. See task #4412111
TEST(Future, thenVariants) {
SomeClass anObject;
Executor* anExecutor;
......
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