(Wangle) Then Magic
authorHannes Roth <hannesr@fb.com>
Mon, 26 Jan 2015 20:53:42 +0000 (12:53 -0800)
committerwoo <woo@fb.com>
Mon, 2 Feb 2015 21:12:41 +0000 (13:12 -0800)
Summary: All is good now.

Test Plan: Run all the tests.

Reviewed By: hans@fb.com

Subscribers: jsedgwick, trunkagent, fugalh, folly-diffs@

FB internal diff: D1758272

Signature: t1:1758272:1421889405:a015d30783715e106a1e6667e971f7cf47c8392d

folly/futures/Future-inl.h
folly/futures/Future.h
folly/futures/Try.h
folly/futures/test/FutureTest.cpp
folly/futures/test/ThenTest.cpp [new file with mode: 0644]
folly/futures/test/Thens.cpp
folly/futures/test/Thens.h
folly/futures/test/thens.rb

index e14754b792a999ebf604ef06cba88ee4fbb1c496..e95b4a1f7b2beb6528d0da9d1b9fffb9f046f033 100644 (file)
@@ -31,16 +31,6 @@ namespace detail {
   Timekeeper* getTimekeeperSingleton();
 }
 
   Timekeeper* getTimekeeperSingleton();
 }
 
-template <typename T>
-struct isFuture {
-  static const bool value = false;
-};
-
-template <typename T>
-struct isFuture<Future<T> > {
-  static const bool value = true;
-};
-
 template <class T>
 Future<T>::Future(Future<T>&& other) noexcept : core_(nullptr) {
   *this = std::move(other);
 template <class T>
 Future<T>::Future(Future<T>&& other) noexcept : core_(nullptr) {
   *this = std::move(other);
@@ -78,14 +68,14 @@ void Future<T>::setCallback_(F&& func) {
   core_->setCallback(std::move(func));
 }
 
   core_->setCallback(std::move(func));
 }
 
-// Variant: f.then([](Try<T>&& t){ return t.value(); });
+// Variant: returns a value
+// e.g. f.then([](Try<T>&& t){ return t.value(); });
 template <class T>
 template <class T>
-template <class F>
-typename std::enable_if<
-  !isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
-  Future<typename std::result_of<F(Try<T>&&)>::type> >::type
-Future<T>::then(F&& func) {
-  typedef typename std::result_of<F(Try<T>&&)>::type B;
+template <typename F, typename R, bool isTry, typename... Args>
+typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
+Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
+  static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
+  typedef typename R::ReturnsFuture::Inner B;
 
   throwIfInvalid();
 
 
   throwIfInvalid();
 
@@ -130,39 +120,11 @@ Future<T>::then(F&& func) {
      */
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
      */
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
-      p->fulfil([&]() {
-        return (*funcm)(std::move(t));
-      });
-    });
-
-  return 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()) {
+      if (!isTry && t.hasException()) {
         p->setException(std::move(t.exception()));
       } else {
         p->fulfil([&]() {
         p->setException(std::move(t.exception()));
       } else {
         p->fulfil([&]() {
-          return (*funcm)(std::move(t.value()));
+          return (*funcm)(t.template get<isTry, Args>()...);
         });
       }
     });
         });
       }
     });
@@ -170,44 +132,14 @@ Future<T>::then(F&& func) {
   return f;
 }
 
   return f;
 }
 
-// Variant: f.then([](){ return; });
+// Variant: returns a Future
+// e.g. f.then([](T&& t){ return makeFuture<T>(t); });
 template <class T>
 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(std::move(t.exception()));
-      } else {
-        p->fulfil([&]() {
-          return (*funcm)();
-        });
-      }
-    });
-
-  return f;
-}
-
-// Variant: f.then([](Try<T>&& t){ return makeFuture<T>(t.value()); });
-template <class T>
-template <class F>
-typename std::enable_if<
-  isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
-  Future<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
-Future<T>::then(F&& func) {
-  typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
+template <typename F, typename R, bool isTry, typename... Args>
+typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
+Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
+  static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
+  typedef typename R::ReturnsFuture::Inner B;
 
   throwIfInvalid();
 
 
   throwIfInvalid();
 
@@ -220,53 +152,16 @@ Future<T>::then(F&& func) {
 
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
 
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
-      try {
-        auto f2 = (*funcm)(std::move(t));
-        // that didn't throw, now we can steal p
-        f2.setCallback_([p](Try<B>&& b) mutable {
-          p->fulfilTry(std::move(b));
-        });
-      } catch (const std::exception& e) {
-        p->setException(exception_wrapper(std::current_exception(), e));
-      } catch (...) {
-        p->setException(exception_wrapper(std::current_exception()));
-      }
-    });
-
-  return 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()) {
+      if (!isTry && t.hasException()) {
         p->setException(std::move(t.exception()));
       } else {
         try {
         p->setException(std::move(t.exception()));
       } else {
         try {
-          auto f2 = (*funcm)(std::move(t.value()));
+          auto f2 = (*funcm)(t.template get<isTry, Args>()...);
+          // that didn't throw, now we can steal p
           f2.setCallback_([p](Try<B>&& b) mutable {
             p->fulfilTry(std::move(b));
           });
         } catch (const std::exception& e) {
           f2.setCallback_([p](Try<B>&& b) mutable {
             p->fulfilTry(std::move(b));
           });
         } catch (const std::exception& e) {
-          p->setException(exception_wrapper(std::current_exception(), e));
-        } catch (...) {
           p->setException(exception_wrapper(std::current_exception()));
         }
       }
           p->setException(exception_wrapper(std::current_exception()));
         }
       }
@@ -275,42 +170,16 @@ Future<T>::then(F&& func) {
   return f;
 }
 
   return 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.exception());
-      } else {
-        try {
-          auto f2 = (*funcm)();
-          f2.setCallback_([p](Try<B>&& b) mutable {
-            p->fulfilTry(std::move(b));
-          });
-        } catch (const std::exception& e) {
-          p->setException(exception_wrapper(std::current_exception(), e));
-        } catch (...) {
-          p->setException(exception_wrapper(std::current_exception()));
-        }
-      }
-    });
-
-  return f;
+template <typename T>
+template <typename Caller, typename R, typename... Args>
+  Future<typename isFuture<R>::Inner>
+Future<T>::then(Caller *instance, R(Caller::*func)(Args...)) {
+  typedef typename std::remove_cv<
+    typename std::remove_reference<
+      typename detail::ArgType<Args...>::FirstArg>::type>::type FirstArg;
+  return then([instance, func](Try<T>&& t){
+    return (instance->*func)(t.template get<isTry<FirstArg>::value, Args>()...);
+  });
 }
 
 template <class T>
 }
 
 template <class T>
index 948e263a22b3bc6e4dca80afc5289bbed37d9077..0f406d7653095bf3df0f7727aa515bf894f09a68 100644 (file)
@@ -35,29 +35,29 @@ namespace folly {
 
 template <class> struct Promise;
 
 
 template <class> struct Promise;
 
-namespace detail {
-
-template <class> struct Core;
-template <class...> struct VariadicContext;
+template <typename T>
+struct isFuture : std::false_type {
+  typedef T Inner;
+};
 
 
-template <class T>
-struct AliasIfVoid {
-  typedef typename std::conditional<
-    std::is_same<T, void>::value,
-    int,
-    T>::type type;
+template <typename T>
+struct isFuture<Future<T>> : std::true_type {
+  typedef T Inner;
 };
 
 };
 
+template <typename T>
+struct isTry : std::false_type {};
 
 template <typename T>
 
 template <typename T>
-struct IsFuture : std::integral_constant<bool, false> {
-    typedef T Inner;
-};
+struct isTry<Try<T>> : std::true_type {};
 
 
-template <template <typename T> class Future, typename T>
-struct IsFuture<Future<T>> : std::integral_constant<bool, true> {
-    typedef T Inner;
-};
+namespace detail {
+
+template <class> struct Core;
+template <class...> struct VariadicContext;
+
+template<typename F, typename... Args>
+using resultOf = decltype(std::declval<F>()(std::declval<Args>()...));
 
 template <typename...>
 struct ArgType;
 
 template <typename...>
 struct ArgType;
@@ -72,12 +72,64 @@ struct ArgType<> {
   typedef void FirstArg;
 };
 
   typedef void FirstArg;
 };
 
+template <bool isTry, typename F, typename... Args>
+struct argResult {
+  typedef resultOf<F, Args...> Result;
+};
+
+template<typename F, typename... Args>
+struct callableWith {
+    template<typename T,
+             typename = detail::resultOf<T, Args...>>
+    static constexpr std::true_type
+    check(std::nullptr_t) { return std::true_type{}; };
+
+    template<typename>
+    static constexpr std::false_type
+    check(...) { return std::false_type{}; };
+
+    typedef decltype(check<F>(nullptr)) type;
+    static constexpr bool value = type::value;
+};
+
+template<typename T, typename F>
+struct callableResult {
+  typedef typename std::conditional<
+    callableWith<F>::value,
+    detail::argResult<false, F>,
+    typename std::conditional<
+      callableWith<F, Try<T>&&>::value,
+      detail::argResult<true, F, Try<T>&&>,
+      typename std::conditional<
+        callableWith<F, Try<T>&>::value,
+        detail::argResult<true, F, Try<T>&>,
+        typename std::conditional<
+          callableWith<F, T&&>::value,
+          detail::argResult<false, F, T&&>,
+          detail::argResult<false, F, T&>>::type>::type>::type>::type Arg;
+  typedef isFuture<typename Arg::Result> ReturnsFuture;
+  typedef Future<typename ReturnsFuture::Inner> Return;
+};
+
+template<typename F>
+struct callableResult<void, F> {
+  typedef typename std::conditional<
+    callableWith<F>::value,
+    detail::argResult<false, F>,
+    typename std::conditional<
+      callableWith<F, Try<void>&&>::value,
+      detail::argResult<true, F, Try<void>&&>,
+      detail::argResult<true, F, Try<void>&>>::type>::type Arg;
+  typedef isFuture<typename Arg::Result> ReturnsFuture;
+  typedef Future<typename ReturnsFuture::Inner> Return;
+};
+
 template <typename L>
 struct Extract : Extract<decltype(&L::operator())> { };
 
 template <typename Class, typename R, typename... Args>
 struct Extract<R(Class::*)(Args...) const> {
 template <typename L>
 struct Extract : Extract<decltype(&L::operator())> { };
 
 template <typename Class, typename R, typename... Args>
 struct Extract<R(Class::*)(Args...) const> {
-  typedef IsFuture<R> ReturnsFuture;
+  typedef isFuture<R> ReturnsFuture;
   typedef Future<typename ReturnsFuture::Inner> Return;
   typedef typename ReturnsFuture::Inner RawReturn;
   typedef typename ArgType<Args...>::FirstArg FirstArg;
   typedef Future<typename ReturnsFuture::Inner> Return;
   typedef typename ReturnsFuture::Inner RawReturn;
   typedef typename ArgType<Args...>::FirstArg FirstArg;
@@ -85,7 +137,7 @@ struct Extract<R(Class::*)(Args...) const> {
 
 template <typename Class, typename R, typename... Args>
 struct Extract<R(Class::*)(Args...)> {
 
 template <typename Class, typename R, typename... Args>
 struct Extract<R(Class::*)(Args...)> {
-  typedef IsFuture<R> ReturnsFuture;
+  typedef isFuture<R> ReturnsFuture;
   typedef Future<typename ReturnsFuture::Inner> Return;
   typedef typename ReturnsFuture::Inner RawReturn;
   typedef typename ArgType<Args...>::FirstArg FirstArg;
   typedef Future<typename ReturnsFuture::Inner> Return;
   typedef typename ReturnsFuture::Inner RawReturn;
   typedef typename ArgType<Args...>::FirstArg FirstArg;
@@ -95,8 +147,6 @@ struct Extract<R(Class::*)(Args...)> {
 
 struct Timekeeper;
 
 
 struct Timekeeper;
 
-template <typename T> struct isFuture;
-
 /// This namespace is for utility functions that would usually be static
 /// members of Future, except they don't make sense there because they don't
 /// depend on the template type (rather, on the type of their arguments in
 /// This namespace is for utility functions that would usually be static
 /// members of Future, except they don't make sense there because they don't
 /// depend on the template type (rather, on the type of their arguments in
@@ -196,8 +246,18 @@ class Future {
   T getVia(DrivableExecutor* e);
 
   /** When this Future has completed, execute func which is a function that
   T getVia(DrivableExecutor* e);
 
   /** When this Future has completed, execute func which is a function that
-    takes a Try<T>&&. A Future for the return type of func is
-    returned. e.g.
+    takes one of:
+      (const) Try<T>&&
+      (const) Try<T>&
+      (const) Try<T>
+      (const) T&&
+      (const) T&
+      (const) T
+      (void)
+
+    Func shall return either another Future or a value.
+
+    A Future for the return type of func is returned.
 
     Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
 
 
     Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
 
@@ -209,105 +269,10 @@ class Future {
      Future), we might want to support a similar API which could be
      implemented a little more efficiently than
      f.via(executor).then(callback) */
      Future), we might want to support a similar API which could be
      implemented a little more efficiently than
      f.via(executor).then(callback) */
-  template <class F>
-  typename std::enable_if<
-    !isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
-    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(
-  ///     [](Try<T>&&) { return makeFuture<string>("foo"); });
-  template <class F>
-  typename std::enable_if<
-    isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
-    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>&&);
-  ///
-  ///   Future<R> f2 = f1.then(doWork);
-  ///
-  /// or
-  ///
-  ///   struct Worker {
-  ///     static R doWork(Try<T>&&); }
-  ///
-  ///   Future<R> f2 = f1.then(&Worker::doWork);
-  template <class = T, class R = std::nullptr_t>
-  typename std::enable_if<!isFuture<R>::value, Future<R>>::type
-  inline then(R(*func)(Try<T>&&)) {
-    return then([func](Try<T>&& t) {
-      return (*func)(std::move(t));
-    });
-  }
-
-  /// Variant where func returns a Future<R> instead of a R. e.g.
-  ///
-  ///   struct Worker {
-  ///     Future<R> doWork(Try<T>&&); }
-  ///
-  ///   Future<R> f2 = f1.then(&Worker::doWork);
-  template <class = T, class R = std::nullptr_t>
-  typename std::enable_if<isFuture<R>::value, R>::type
-  inline then(R(*func)(Try<T>&&)) {
-    return then([func](Try<T>&& t) {
-      return (*func)(std::move(t));
-    });
+  template <typename F, typename R = detail::callableResult<T, F>>
+  typename R::Return then(F func) {
+    typedef typename R::Arg Arguments;
+    return thenImplementation<F, R>(std::move(func), Arguments());
   }
 
   /// Variant where func is an member function
   }
 
   /// Variant where func is an member function
@@ -317,72 +282,9 @@ class Future {
   ///
   ///   Worker *w;
   ///   Future<R> f2 = f1.then(w, &Worker::doWork);
   ///
   ///   Worker *w;
   ///   Future<R> f2 = f1.then(w, &Worker::doWork);
-  template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
-  typename std::enable_if<!isFuture<R>::value, Future<R>>::type
-  inline then(Caller *instance, R(Caller::*func)(Try<T>&&)) {
-    return then([instance, func](Try<T>&& t) {
-      return (instance->*func)(std::move(t));
-    });
-  }
-
-  // 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 {
-  ///     Future<R> doWork(Try<T>&&); }
-  ///
-  ///   Worker *w;
-  ///   Future<R> f2 = f1.then(w, &Worker::doWork);
-  template <class = T, class R = std::nullptr_t, class Caller = std::nullptr_t>
-  typename std::enable_if<isFuture<R>::value, R>::type
-  inline then(Caller *instance, R(Caller::*func)(Try<T>&&)) {
-    return then([instance, func](Try<T>&& t) {
-      return (instance->*func)(std::move(t));
-    });
-  }
-
-  // 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));
-    });
-  }
+  template <typename Caller, typename R, typename... Args>
+    Future<typename isFuture<R>::Inner>
+  then(Caller *instance, R(Caller::*func)(Args...));
 
   /// Convenience method for ignoring the value and creating a Future<void>.
   /// Exceptions still propagate.
 
   /// Convenience method for ignoring the value and creating a Future<void>.
   /// Exceptions still propagate.
@@ -515,6 +417,18 @@ class Future {
   void throwIfInvalid() const;
 
   friend class Promise<T>;
   void throwIfInvalid() const;
 
   friend class Promise<T>;
+
+  // Variant: returns a value
+  // e.g. f.then([](Try<T> t){ return t.value(); });
+  template <typename F, typename R, bool isTry, typename... Args>
+  typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
+  thenImplementation(F func, detail::argResult<isTry, F, Args...>);
+
+  // Variant: returns a Future
+  // e.g. f.then([](Try<T> t){ return makeFuture<T>(t); });
+  template <typename F, typename R, bool isTry, typename... Args>
+  typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
+  thenImplementation(F func, detail::argResult<isTry, F, Args...>);
 };
 
 /**
 };
 
 /**
index 7234a96eec605cb13e902ea7c853a2f8cdacb47e..00008046699a5a72c4853a47ebb4545d4cf6cb54 100644 (file)
@@ -198,6 +198,16 @@ class Try {
     return e_->with_exception<Ex>(std::move(func));
   }
 
     return e_->with_exception<Ex>(std::move(func));
   }
 
+  template <bool isTry, typename R>
+  typename std::enable_if<isTry, R>::type get() {
+    return std::forward<R>(*this);
+  }
+
+  template <bool isTry, typename R>
+  typename std::enable_if<!isTry, R>::type get() {
+    return std::forward<R>(value());
+  }
+
  private:
   Contains contains_;
   union {
  private:
   Contains contains_;
   union {
@@ -300,6 +310,11 @@ class Try<void> {
     return e_->with_exception<Ex>(std::move(func));
   }
 
     return e_->with_exception<Ex>(std::move(func));
   }
 
+  template <bool, typename R>
+  R get() {
+    return std::forward<R>(*this);
+  }
+
  private:
   bool hasValue_;
   std::unique_ptr<exception_wrapper> e_{nullptr};
  private:
   bool hasValue_;
   std::unique_ptr<exception_wrapper> e_{nullptr};
index a69c8b9671c4975ccbbf98a5d71c8c882160eb9c..f4cd52867bec157b540c13fe454f91cf4d7ee889 100644 (file)
@@ -276,6 +276,25 @@ TEST(Future, special) {
   EXPECT_TRUE(std::is_move_assignable<Future<int>>::value);
 }
 
   EXPECT_TRUE(std::is_move_assignable<Future<int>>::value);
 }
 
+TEST(Future, then) {
+  auto f = makeFuture<string>("0")
+    .then([](){ return makeFuture<string>("1"); })
+    .then([](Try<string>&& t) { return makeFuture(t.value() + ";2"); })
+    .then([](const Try<string>&& t) { return makeFuture(t.value() + ";3"); })
+    .then([](Try<string>& t) { return makeFuture(t.value() + ";4"); })
+    .then([](const Try<string>& t) { return makeFuture(t.value() + ";5"); })
+    .then([](Try<string> t) { return makeFuture(t.value() + ";6"); })
+    .then([](const Try<string> t) { return makeFuture(t.value() + ";7"); })
+    .then([](string&& s) { return makeFuture(s + ";8"); })
+    .then([](const string&& s) { return makeFuture(s + ";9"); })
+    .then([](string& s) { return makeFuture(s + ";10"); })
+    .then([](const string& s) { return makeFuture(s + ";11"); })
+    .then([](string s) { return makeFuture(s + ";12"); })
+    .then([](const string s) { return makeFuture(s + ";13"); })
+  ;
+  EXPECT_EQ(f.value(), "1;2;3;4;5;6;7;8;9;10;11;12;13");
+}
+
 TEST(Future, thenTry) {
   bool flag = false;
 
 TEST(Future, thenTry) {
   bool flag = false;
 
@@ -384,6 +403,25 @@ TEST(Future, thenFunctionFuture) {
   EXPECT_EQ(f.value(), "start;static;class-static;class");
 }
 
   EXPECT_EQ(f.value(), "start;static;class-static;class");
 }
 
+TEST(Future, thenBind) {
+  auto l = []() {
+    return makeFuture("bind");
+  };
+  auto b = std::bind(l);
+  auto f = makeFuture().then(std::move(b));
+  EXPECT_EQ(f.value(), "bind");
+}
+
+TEST(Future, thenBindTry) {
+  auto l = [](Try<string>&& t) {
+    return makeFuture(t.value() + ";bind");
+  };
+  auto b = std::bind(l, std::placeholders::_1);
+  auto f = makeFuture<string>("start").then(std::move(b));
+
+  EXPECT_EQ(f.value(), "start;bind");
+}
+
 TEST(Future, value) {
   auto f = makeFuture(unique_ptr<int>(new int(42)));
   auto up = std::move(f.value());
 TEST(Future, value) {
   auto f = makeFuture(unique_ptr<int>(new int(42)));
   auto up = std::move(f.value());
diff --git a/folly/futures/test/ThenTest.cpp b/folly/futures/test/ThenTest.cpp
new file mode 100644 (file)
index 0000000..fdc65a6
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <thread>
+
+#include <folly/futures/Future.h>
+
+using namespace folly;
+
+struct Widget {
+  int v_, copied_, moved_;
+  /* implicit */ Widget(int v) : v_(v), copied_(0), moved_(0) {}
+  Widget(const Widget& other)
+    : v_(other.v_), copied_(other.copied_ + 1), moved_(other.moved_) {}
+  Widget(Widget&& other) noexcept
+    : v_(other.v_), copied_(other.copied_), moved_(other.moved_ + 1) {}
+  Widget& operator=(const Widget& other)
+    { throw std::logic_error("unexpected copy assignment"); }
+  Widget& operator=(Widget&& other)
+    { throw std::logic_error("unexpected move assignment"); }
+};
+
+TEST(Then, tryConstructor) {
+  auto t = Try<Widget>(23);
+  EXPECT_EQ(t.value().v_, 23);
+  EXPECT_EQ(t.value().copied_, 0);
+  EXPECT_EQ(t.value().moved_, 1);
+}
+
+TEST(Then, makeFuture) {
+  auto future = makeFuture<Widget>(23);
+  EXPECT_EQ(future.value().v_, 23);
+  EXPECT_EQ(future.value().copied_, 0);
+  EXPECT_EQ(future.value().moved_, 2);
+}
+
+TEST(Then, TryConstRValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Try<Widget>&& t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 2);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, TryRValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Try<Widget>&& t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 2);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, TryLValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Try<Widget>& t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 2);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, TryConstLValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Try<Widget>& t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 2);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, TryValue) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Try<Widget> t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 3);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, TryConstValue) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Try<Widget> t) {
+      EXPECT_EQ(t.value().copied_, 0);
+      EXPECT_EQ(t.value().moved_, 3);
+      return t.value().v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, ConstRValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Widget&& w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 2);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, RValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Widget&& w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 2);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, LValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Widget& w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 2);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, ConstLValueReference) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Widget& w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 2);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, Value) {
+  auto future = makeFuture<Widget>(23).then(
+    [](Widget w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 3);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
+
+TEST(Then, ConstValue) {
+  auto future = makeFuture<Widget>(23).then(
+    [](const Widget w) {
+      EXPECT_EQ(w.copied_, 0);
+      EXPECT_EQ(w.moved_, 3);
+      return w.v_;
+    });
+  EXPECT_EQ(future.value(), 23);
+}
index 1fb33dc1cec34d740b7c360ad9fcb8d3f7e3b191..716c27ad66262879ab6a650f8105841546470c52 100644 (file)
@@ -1,8 +1,5 @@
 // This file is @generated by thens.rb. Do not edit directly.
 
 // 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/futures/test/Thens.h>
 
 TEST(Future, thenVariants) {
 #include <folly/futures/test/Thens.h>
 
 TEST(Future, thenVariants) {
@@ -14,21 +11,82 @@ TEST(Future, thenVariants) {
   {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(&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<Future<B>, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<Future<B>, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<Future<B>, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(aStdFunction<Future<B>, Try<A> const&>());}
+  {Future<B> f = someFuture<A>().then([&](Try<A> const&){return someFuture<B>();});}
+  {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<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<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<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<Future<B>, A const&>);}
+  {Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<Future<B>, A const&>);}
+  {Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<Future<B>, A const&>);}
+  {Future<B> f = someFuture<A>().then(aStdFunction<Future<B>, A const&>());}
+  {Future<B> f = someFuture<A>().then([&](A const&){return someFuture<B>();});}
+  {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<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([&](){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, 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, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<B, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<B, Try<A> const&>);}
+  {Future<B> f = someFuture<A>().then(aStdFunction<B, Try<A> const&>());}
+  {Future<B> f = someFuture<A>().then([&](Try<A> const&){return 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, 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();});}
   {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();});}
+  {Future<B> f = someFuture<A>().then(&aFunction<B, A const&>);}
+  {Future<B> f = someFuture<A>().then(&SomeClass::aStaticMethod<B, A const&>);}
+  {Future<B> f = someFuture<A>().then(&anObject, &SomeClass::aMethod<B, A const&>);}
+  {Future<B> f = someFuture<A>().then(aStdFunction<B, A const&>());}
+  {Future<B> f = someFuture<A>().then([&](A const&){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();});}
+  {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();});}
+  {Future<B> f = someFuture<A>().then([&](){return B();});}
 }
 
 }
 
-#endif
index e8e8a8e53c69b832aa4c760d5774c716e8d0bf2b..141be78856a228ff3ae3ef1046af19c607279bdb 100644 (file)
@@ -38,9 +38,6 @@ Future<T> someFuture() {
   return makeFuture(T());
 }
 
   return makeFuture(T());
 }
 
-template <class Ret, class... Params, typename = void>
-Ret aFunction(Params...);
-
 template <class Ret, class... Params>
 typename std::enable_if<isFuture<Ret>::value, Ret>::type
 aFunction(Params...) {
 template <class Ret, class... Params>
 typename std::enable_if<isFuture<Ret>::value, Ret>::type
 aFunction(Params...) {
@@ -69,11 +66,7 @@ aStdFunction(typename std::enable_if<isFuture<Ret>::value, bool>::type = true) {
 }
 
 class SomeClass {
 }
 
 class SomeClass {
-  B b;
 public:
 public:
-  template <class Ret, class... Params>
-  static Ret aStaticMethod(Params...);
-
   template <class Ret, class... Params>
   static
   typename std::enable_if<!isFuture<Ret>::value, Ret>::type
   template <class Ret, class... Params>
   static
   typename std::enable_if<!isFuture<Ret>::value, Ret>::type
@@ -89,9 +82,6 @@ public:
     return makeFuture(T());
   }
 
     return makeFuture(T());
   }
 
-  template <class Ret, class... Params>
-  Ret aMethod(Params...);
-
   template <class Ret, class... Params>
   typename std::enable_if<!isFuture<Ret>::value, Ret>::type
   aMethod(Params...) {
   template <class Ret, class... Params>
   typename std::enable_if<!isFuture<Ret>::value, Ret>::type
   aMethod(Params...) {
index 7b2fa6982f080625b8d6223e0c24303872f5e32f..fa73a8e723aadcfc1434c7338ea3876fecf8fd0b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 
 #!/usr/bin/env ruby
 
-# ruby thens.rb > Thens.cpp
+# ruby folly/futures/test/thens.rb > folly/futures/test/Thens.cpp
 
 # An exercise in combinatorics.
 # (ordinary/static function, member function, std::function, lambda)
 
 # An exercise in combinatorics.
 # (ordinary/static function, member function, std::function, lambda)
@@ -32,40 +32,39 @@ return_types = [
 ]
 param_types = [
     "Try<A>&&",
 ]
 param_types = [
     "Try<A>&&",
-    #"Try<A> const&",
-    #"Try<A>",
-    #"Try<A>&",
+    "Try<A> const&",
+    "Try<A>",
+    "Try<A>&",
     "A&&",
     "A&&",
-    #"A const&",
-    #"A",
-    #"A&",
-    #"",
+    "A const&",
+    "A",
+    "A&",
+    "",
   ]
 
 tests = (
   return_types.map { |ret|
     param_types.map { |param|
   ]
 
 tests = (
   return_types.map { |ret|
     param_types.map { |param|
-      both = "#{ret}, #{param}"
-      [
-        ["&aFunction<#{both}>"],
-        ["&SomeClass::aStaticMethod<#{both}>"],
-        # TODO switch these around (std::bind-style)
-        ["&anObject", "&SomeClass::aMethod<#{both}>"],
-        ["aStdFunction<#{both}>()"],
-        ["[&](#{param}){return #{retval(ret)};}"],
-      ]
+      if param != "" then
+        both = "#{ret}, #{param}"
+        [
+          ["&aFunction<#{both}>"],
+          ["&SomeClass::aStaticMethod<#{both}>"],
+          # TODO switch these around (std::bind-style)
+          ["&anObject", "&SomeClass::aMethod<#{both}>"],
+          ["aStdFunction<#{both}>()"],
+          ["[&](#{param}){return #{retval(ret)};}"],
+        ]
+      else
+        [["[&](){return #{retval(ret)};}"]]
+      end
     }
     }
-  }.flatten(2) + [
-    #[""],
-  ]
+  }.flatten(2)
 ).map {|a| test(a)}.flatten
 
 print <<EOF
 // This file is #{"@"}generated by thens.rb. Do not edit directly.
 
 ).map {|a| test(a)}.flatten
 
 print <<EOF
 // 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/futures/test/Thens.h>
 
 TEST(Future, thenVariants) {
 #include <folly/futures/test/Thens.h>
 
 TEST(Future, thenVariants) {
@@ -75,5 +74,4 @@ TEST(Future, thenVariants) {
   #{tests.join("\n  ")}
 }
 
   #{tests.join("\n  ")}
 }
 
-#endif
 EOF
 EOF