A macro for creating member-invoke traits
authorYedidya Feldblum <yfeldblum@fb.com>
Wed, 1 Nov 2017 05:12:53 +0000 (22:12 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 1 Nov 2017 05:20:17 +0000 (22:20 -0700)
Summary:
[Folly] A macro for creating member-invoke traits.

The macro creates a specialized traits container with member types and aliases mimicking `std::invoke_result` and the related traits types and aliases.

Reviewed By: aary

Differential Revision: D6195087

fbshipit-source-id: 07c2bbab6cccb04dc8ff12e20923351e8f38abfd

folly/functional/Invoke.h
folly/functional/test/InvokeTest.cpp

index 0924af7..2eab3ff 100644 (file)
@@ -160,3 +160,129 @@ struct is_nothrow_invocable_r
 } // namespace folly
 
 #endif
+
+/***
+ *  FOLLY_CREATE_MEMBER_INVOKE_TRAITS
+ *
+ *  Used to create traits container, bound to a specific member-invocable name,
+ *  with the following member traits types and aliases:
+ *
+ *  * invoke_result
+ *  * invoke_result_t
+ *  * is_invocable
+ *  * is_invocable_r
+ *  * is_nothrow_invocable
+ *  * is_nothrow_invocable_r
+ *
+ *  The container also has a static member function:
+ *
+ *  * invoke
+ *
+ *  These members have behavior matching the behavior of C++17's corresponding
+ *  invocation traits types, aliases, and functions, but substituting canonical
+ *  invocation with member invocation.
+ *
+ *  Example:
+ *
+ *    FOLLY_CREATE_MEMBER_INVOKE_TRAITS(foo_invoke_traits, foo);
+ *
+ *  The traits container type `foo_invoke_traits` is generated in the current
+ *  namespace and has the listed member types and aliases. They may be used as
+ *  follows:
+ *
+ *    struct CanFoo {
+ *      int foo(Bar const&) { return 1; }
+ *      int foo(Car&&) noexcept { return 2; }
+ *    };
+ *
+ *    using traits = foo_invoke_traits;
+ *
+ *    traits::invoke(CanFoo{}, Bar{}) // 1
+ *
+ *    traits::invoke_result<CanFoo, Bar&&> // has member
+ *    traits::invoke_result_t<CanFoo, Bar&&> // int
+ *    traits::invoke_result<CanFoo, Bar&> // empty
+ *    traits::invoke_result_t<CanFoo, Bar&> // error
+ *
+ *    traits::is_invocable<CanFoo, Bar&&>::value // true
+ *    traits::is_invocable<CanFoo, Bar&>::value // false
+ *
+ *    traits::is_invocable_r<int, CanFoo, Bar&&>::value // true
+ *    traits::is_invocable_r<char*, CanFoo, Bar&&>::value // false
+ *
+ *    traits::is_nothrow_invocable<CanFoo, Bar&&>::value // false
+ *    traits::is_nothrow_invocable<CanFoo, Car&&>::value // true
+ *
+ *    traits::is_nothrow_invocable<int, CanFoo, Bar&&>::value // false
+ *    traits::is_nothrow_invocable<char*, CanFoo, Bar&&>::value // false
+ *    traits::is_nothrow_invocable<int, CanFoo, Car&&>::value // true
+ *    traits::is_nothrow_invocable<char*, CanFoo, Car&&>::value // false
+ */
+#define FOLLY_CREATE_MEMBER_INVOKE_TRAITS(classname, membername)              \
+  struct classname {                                                          \
+   private:                                                                   \
+    template <typename T>                                                     \
+    using v_ = ::folly::void_t<T>;                                            \
+    template <typename F, typename... Args>                                   \
+    using result_ =                                                           \
+        decltype(::std::declval<F>().membername(::std::declval<Args>()...));  \
+    template <typename F, typename... Args>                                   \
+    using nothrow_ = ::std::integral_constant<                                \
+        bool,                                                                 \
+        noexcept(::std::declval<F>().membername(::std::declval<Args>()...))>; \
+                                                                              \
+    template <typename, typename F, typename... Args>                         \
+    struct invoke_result_ {};                                                 \
+    template <typename F, typename... Args>                                   \
+    struct invoke_result_<v_<result_<F, Args...>>, F, Args...> {              \
+      using type = result_<F, Args...>;                                       \
+    };                                                                        \
+                                                                              \
+    template <typename, typename F, typename... Args>                         \
+    struct is_invocable_ : ::std::false_type {};                              \
+    template <typename F, typename... Args>                                   \
+    struct is_invocable_<v_<result_<F, Args...>>, F, Args...>                 \
+        : ::std::true_type {};                                                \
+                                                                              \
+    template <typename, typename R, typename F, typename... Args>             \
+    struct is_invocable_r_ : ::std::false_type {};                            \
+    template <typename R, typename F, typename... Args>                       \
+    struct is_invocable_r_<v_<result_<F, Args...>>, R, F, Args...>            \
+        : ::std::is_convertible<result_<F, Args...>, R> {};                   \
+                                                                              \
+    template <typename, typename F, typename... Args>                         \
+    struct is_nothrow_invocable_ : ::std::false_type {};                      \
+    template <typename F, typename... Args>                                   \
+    struct is_nothrow_invocable_<v_<result_<F, Args...>>, F, Args...>         \
+        : nothrow_<F, Args...> {};                                            \
+                                                                              \
+    template <typename, typename R, typename F, typename... Args>             \
+    struct is_nothrow_invocable_r_ : ::std::false_type {};                    \
+    template <typename R, typename F, typename... Args>                       \
+    struct is_nothrow_invocable_r_<v_<result_<F, Args...>>, R, F, Args...>    \
+        : ::folly::StrictConjunction<                                         \
+              ::std::is_convertible<result_<F, Args...>, R>,                  \
+              nothrow_<F, Args...>> {};                                       \
+                                                                              \
+   public:                                                                    \
+    template <typename F, typename... Args>                                   \
+    struct invoke_result : invoke_result_<void, F, Args...> {};               \
+    template <typename F, typename... Args>                                   \
+    using invoke_result_t = typename invoke_result<F, Args...>::type;         \
+    template <typename F, typename... Args>                                   \
+    struct is_invocable : is_invocable_<void, F, Args...> {};                 \
+    template <typename R, typename F, typename... Args>                       \
+    struct is_invocable_r : is_invocable_r_<void, R, F, Args...> {};          \
+    template <typename F, typename... Args>                                   \
+    struct is_nothrow_invocable : is_nothrow_invocable_<void, F, Args...> {}; \
+    template <typename R, typename F, typename... Args>                       \
+    struct is_nothrow_invocable_r                                             \
+        : is_nothrow_invocable_r_<void, R, F, Args...> {};                    \
+                                                                              \
+    template <typename F, typename... Args>                                   \
+    static constexpr result_<F, Args...> invoke(                              \
+        F&& f,                                                                \
+        Args&&... args) noexcept(nothrow_<F, Args...>::value) {               \
+      return std::forward<F>(f).membername(std::forward<Args>(args)...);      \
+    }                                                                         \
+  }
index 721073d..d941b92 100644 (file)
@@ -33,6 +33,22 @@ struct Fn {
   }
   int volatile x_ = 17;
 };
+
+FOLLY_CREATE_MEMBER_INVOKE_TRAITS(test_invoke_traits, test);
+
+struct Obj {
+  char test(int, int) noexcept {
+    return 'a';
+  }
+  int volatile&& test(int, char const*) {
+    return std::move(x_);
+  }
+  float test(float, float) {
+    return 3.14;
+  }
+  int volatile x_ = 17;
+};
+
 } // namespace
 
 TEST_F(InvokeTest, invoke) {
@@ -81,3 +97,57 @@ TEST_F(InvokeTest, is_nothrow_invocable_r) {
   EXPECT_FALSE((folly::is_nothrow_invocable_r<int, Fn, int, char*>::value));
   EXPECT_FALSE((folly::is_nothrow_invocable_r<int, Fn, int>::value));
 }
+
+TEST_F(InvokeTest, member_invoke) {
+  using traits = test_invoke_traits;
+
+  Obj fn;
+
+  EXPECT_TRUE(noexcept(traits::invoke(fn, 1, 2)));
+  EXPECT_FALSE(noexcept(traits::invoke(fn, 1, "2")));
+
+  EXPECT_EQ('a', traits::invoke(fn, 1, 2));
+  EXPECT_EQ(17, traits::invoke(fn, 1, "2"));
+}
+
+TEST_F(InvokeTest, member_invoke_result) {
+  using traits = test_invoke_traits;
+
+  EXPECT_TRUE(
+      (std::is_same<char, traits::invoke_result_t<Obj, int, char>>::value));
+  EXPECT_TRUE(
+      (std::is_same<int volatile&&, traits::invoke_result_t<Obj, int, char*>>::
+           value));
+}
+
+TEST_F(InvokeTest, member_is_invocable) {
+  using traits = test_invoke_traits;
+
+  EXPECT_TRUE((traits::is_invocable<Obj, int, char>::value));
+  EXPECT_TRUE((traits::is_invocable<Obj, int, char*>::value));
+  EXPECT_FALSE((traits::is_invocable<Obj, int>::value));
+}
+
+TEST_F(InvokeTest, member_is_invocable_r) {
+  using traits = test_invoke_traits;
+
+  EXPECT_TRUE((traits::is_invocable_r<int, Obj, int, char>::value));
+  EXPECT_TRUE((traits::is_invocable_r<int, Obj, int, char*>::value));
+  EXPECT_FALSE((traits::is_invocable_r<int, Obj, int>::value));
+}
+
+TEST_F(InvokeTest, member_is_nothrow_invocable) {
+  using traits = test_invoke_traits;
+
+  EXPECT_TRUE((traits::is_nothrow_invocable<Obj, int, char>::value));
+  EXPECT_FALSE((traits::is_nothrow_invocable<Obj, int, char*>::value));
+  EXPECT_FALSE((traits::is_nothrow_invocable<Obj, int>::value));
+}
+
+TEST_F(InvokeTest, member_is_nothrow_invocable_r) {
+  using traits = test_invoke_traits;
+
+  EXPECT_TRUE((traits::is_nothrow_invocable_r<int, Obj, int, char>::value));
+  EXPECT_FALSE((traits::is_nothrow_invocable_r<int, Obj, int, char*>::value));
+  EXPECT_FALSE((traits::is_nothrow_invocable_r<int, Obj, int>::value));
+}