Future<T>::then([](T&&)), aka next()
authorJames Sedgwick <jsedgwick@fb.com>
Wed, 29 Oct 2014 23:33:14 +0000 (16:33 -0700)
committerPavlo Kushnir <pavlo@fb.com>
Sat, 8 Nov 2014 02:07:58 +0000 (18:07 -0800)
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

folly/wangle/Future-inl.h
folly/wangle/Future.h
folly/wangle/Try.h
folly/wangle/test/FutureTest.cpp
folly/wangle/test/Thens.cpp
folly/wangle/test/thens.rb

index f462189cca9a6b0a78a2515571d17bbd1419388c..6dd2125c446d66c520619d004be45723e9b54a08 100644 (file)
@@ -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) {});
index f3d50ce020aedbf5bcd9b5cc5bc378d6ad718704..c01aff5c11edc9d6d80d4f46fd439dd33766636d 100644 (file)
@@ -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();
index 68d3ef92184f1813a71d5808b63593900493232c..bc7800d8ca193d995627a62b332f44b733dd46a6 100644 (file)
@@ -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_;
index 0feaa50fb38fe1ab245e9e1b56009bb82a2eaa7d..8d1f95d3ce203fbda13641c188875f42ce4f656a 100644 (file)
@@ -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";
 }
index a0e1be3250f7bccfdf983036c3f09eeeb65f84ab..2a9cdca19bad79797002a328d22c12dced346625 100644 (file)
@@ -1,24 +1,37 @@
-// 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
index e45b2c31dc2e63b90a3ef1f979bd804a9a7e3cfd..7de0e4ccee6e97e7441aef6d6fa53b1be4bc80bd 100755 (executable)
@@ -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;