Introducing folly::FunctionRef
authorSven Over <over@fb.com>
Tue, 27 Sep 2016 14:44:32 +0000 (07:44 -0700)
committerFacebook Github Bot 8 <facebook-github-bot-8-bot@fb.com>
Tue, 27 Sep 2016 14:53:50 +0000 (07:53 -0700)
Summary:
This commit introduces a simple function reference type, similar to
std::reference_wrapper, but the template parameter is the function
signature type rather than the type of the referenced object.

A folly::FunctionRef is cheap to construct as it contains only a
pointer to the referenced callable and a pointer to a function which
invokes the callable.

The user of FunctionRef must be aware of the reference semantics:
storing a copy of a FunctionRef is potentially dangerous and should
be avoided unless the referenced object definitely outlives the
FunctionRef object. Thus any function that accepts a FunctionRef
parameter should only use it to invoke the referenced function and
not store a copy of it. Knowing that FunctionRef itself has reference
semantics, it is generally okay to use it to reference lambdas that
capture by reference.

Reviewed By: ericniebler

Differential Revision: D3277364

fbshipit-source-id: 0a7676919cd240da5b6e1f94cadba4289e0aca28

folly/Function.h
folly/test/FunctionRefTest.cpp [new file with mode: 0644]
folly/test/Makefile.am

index 91a8c6a15a6aca74728185d777bb65a491a7f779..a201c95c2cd44baddaba604bcce390d1efab60cc 100644 (file)
@@ -250,8 +250,7 @@ using IsSmall = std::integral_constant<
     bool,
     (sizeof(FunT) <= sizeof(Data::tiny) &&
      // Same as is_nothrow_move_constructible, but w/ no template instantiation.
-     noexcept(FunT(std::declval<FunT&&>()))
-     )>;
+     noexcept(FunT(std::declval<FunT&&>())))>;
 using SmallTag = std::true_type;
 using HeapTag = std::false_type;
 
@@ -390,6 +389,19 @@ bool execBig(Op o, Data* src, Data* dst) {
   return true;
 }
 
+// Invoke helper
+template <typename F, typename... Args>
+inline auto invoke(F&& f, Args&&... args)
+    -> decltype(std::forward<F>(f)(std::forward<Args>(args)...)) {
+  return std::forward<F>(f)(std::forward<Args>(args)...);
+}
+
+template <typename M, typename C, typename... Args>
+inline auto invoke(M(C::*d), Args&&... args)
+    -> decltype(std::mem_fn(d)(std::forward<Args>(args)...)) {
+  return std::mem_fn(d)(std::forward<Args>(args)...);
+}
+
 } // namespace function
 } // namespace detail
 
@@ -675,4 +687,80 @@ Function<ReturnType(Args...) const> constCastFunction(
     Function<ReturnType(Args...) const>&& that) noexcept {
   return std::move(that);
 }
+
+/**
+ * @class FunctionRef
+ *
+ * @brief A reference wrapper for callable objects
+ *
+ * FunctionRef is similar to std::reference_wrapper, but the template parameter
+ * is the function signature type rather than the type of the referenced object.
+ * A folly::FunctionRef is cheap to construct as it contains only a pointer to
+ * the referenced callable and a pointer to a function which invokes the
+ * callable.
+ *
+ * The user of FunctionRef must be aware of the reference semantics: storing a
+ * copy of a FunctionRef is potentially dangerous and should be avoided unless
+ * the referenced object definitely outlives the FunctionRef object. Thus any
+ * function that accepts a FunctionRef parameter should only use it to invoke
+ * the referenced function and not store a copy of it. Knowing that FunctionRef
+ * itself has reference semantics, it is generally okay to use it to reference
+ * lambdas that capture by reference.
+ */
+
+template <typename FunctionType>
+class FunctionRef;
+
+template <typename ReturnType, typename... Args>
+class FunctionRef<ReturnType(Args...)> final {
+  using Call = ReturnType (*)(void*, Args&&...);
+
+  void* object_{nullptr};
+  Call call_{&FunctionRef::uninitCall};
+
+  static ReturnType uninitCall(void*, Args&&...) {
+    throw std::bad_function_call();
+  }
+
+  template <typename Fun>
+  static ReturnType call(void* object, Args&&... args) {
+    return static_cast<ReturnType>(detail::function::invoke(
+        *static_cast<Fun*>(object), static_cast<Args&&>(args)...));
+  }
+
+ public:
+  /**
+   * Default constructor. Constructs an empty FunctionRef.
+   *
+   * Invoking it will throw std::bad_function_call.
+   */
+  FunctionRef() = default;
+
+  /**
+   * Construct a FunctionRef from a reference to a callable object.
+   */
+  template <typename Fun>
+  /* implicit */ FunctionRef(Fun&& fun) noexcept {
+    using ReferencedType = typename std::remove_reference<Fun>::type;
+
+    static_assert(
+        std::is_convertible<
+            typename std::result_of<ReferencedType&(Args && ...)>::type,
+            ReturnType>::value,
+        "FunctionRef cannot be constructed from object with "
+        "incompatible function signature");
+
+    // `Fun` may be a const type, in which case we have to do a const_cast
+    // to store the address in a `void*`. This is safe because the `void*`
+    // will be cast back to `Fun*` (which is a const pointer whenever `Fun`
+    // is a const type) inside `FunctionRef::call`
+    object_ = const_cast<void*>(static_cast<void const*>(std::addressof(fun)));
+    call_ = &FunctionRef::call<ReferencedType>;
+  }
+
+  ReturnType operator()(Args... args) const {
+    return call_(object_, static_cast<Args&&>(args)...);
+  }
+};
+
 } // namespace folly
diff --git a/folly/test/FunctionRefTest.cpp b/folly/test/FunctionRefTest.cpp
new file mode 100644 (file)
index 0000000..621b0ad
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2016 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 <list>
+
+#include <folly/Function.h>
+#include <folly/portability/GTest.h>
+
+using folly::Function;
+using folly::FunctionRef;
+
+TEST(FunctionRef, Simple) {
+  int x = 1000;
+  auto lambda = [&x](int v) { return x += v; };
+
+  FunctionRef<int(int)> fref = lambda;
+  EXPECT_EQ(1005, fref(5));
+  EXPECT_EQ(1011, fref(6));
+  EXPECT_EQ(1018, fref(7));
+
+  FunctionRef<int(int)> const cfref = lambda;
+  EXPECT_EQ(1023, cfref(5));
+  EXPECT_EQ(1029, cfref(6));
+  EXPECT_EQ(1036, cfref(7));
+
+  auto const& clambda = lambda;
+
+  FunctionRef<int(int)> fcref = clambda;
+  EXPECT_EQ(1041, fcref(5));
+  EXPECT_EQ(1047, fcref(6));
+  EXPECT_EQ(1054, fcref(7));
+
+  FunctionRef<int(int)> const cfcref = clambda;
+  EXPECT_EQ(1059, cfcref(5));
+  EXPECT_EQ(1065, cfcref(6));
+  EXPECT_EQ(1072, cfcref(7));
+}
+
+TEST(FunctionRef, FunctionPtr) {
+  int (*funcptr)(int) = [](int v) { return v * v; };
+
+  FunctionRef<int(int)> fref = funcptr;
+  EXPECT_EQ(100, fref(10));
+  EXPECT_EQ(121, fref(11));
+
+  FunctionRef<int(int)> const cfref = funcptr;
+  EXPECT_EQ(100, cfref(10));
+  EXPECT_EQ(121, cfref(11));
+}
+
+TEST(FunctionRef, OverloadedFunctor) {
+  struct OverloadedFunctor {
+    // variant 1
+    int operator()(int x) {
+      return 100 + 1 * x;
+    }
+
+    // variant 2 (const-overload of v1)
+    int operator()(int x) const {
+      return 100 + 2 * x;
+    }
+
+    // variant 3
+    int operator()(int x, int) {
+      return 100 + 3 * x;
+    }
+
+    // variant 4 (const-overload of v3)
+    int operator()(int x, int) const {
+      return 100 + 4 * x;
+    }
+
+    // variant 5 (non-const, has no const-overload)
+    int operator()(int x, char const*) {
+      return 100 + 5 * x;
+    }
+
+    // variant 6 (const only)
+    int operator()(int x, std::vector<int> const&) const {
+      return 100 + 6 * x;
+    }
+  };
+  OverloadedFunctor of;
+  auto const& cof = of;
+
+  FunctionRef<int(int)> variant1 = of;
+  EXPECT_EQ(100 + 1 * 15, variant1(15));
+  FunctionRef<int(int)> const cvariant1 = of;
+  EXPECT_EQ(100 + 1 * 15, cvariant1(15));
+
+  FunctionRef<int(int)> variant2 = cof;
+  EXPECT_EQ(100 + 2 * 16, variant2(16));
+  FunctionRef<int(int)> const cvariant2 = cof;
+  EXPECT_EQ(100 + 2 * 16, cvariant2(16));
+
+  FunctionRef<int(int, int)> variant3 = of;
+  EXPECT_EQ(100 + 3 * 17, variant3(17, 0));
+  FunctionRef<int(int, int)> const cvariant3 = of;
+  EXPECT_EQ(100 + 3 * 17, cvariant3(17, 0));
+
+  FunctionRef<int(int, int)> variant4 = cof;
+  EXPECT_EQ(100 + 4 * 18, variant4(18, 0));
+  FunctionRef<int(int, int)> const cvariant4 = cof;
+  EXPECT_EQ(100 + 4 * 18, cvariant4(18, 0));
+
+  FunctionRef<int(int, char const*)> variant5 = of;
+  EXPECT_EQ(100 + 5 * 19, variant5(19, "foo"));
+  FunctionRef<int(int, char const*)> const cvariant5 = of;
+  EXPECT_EQ(100 + 5 * 19, cvariant5(19, "foo"));
+
+  FunctionRef<int(int, std::vector<int> const&)> variant6 = of;
+  EXPECT_EQ(100 + 6 * 20, variant6(20, {}));
+  EXPECT_EQ(100 + 6 * 20, variant6(20, {1, 2, 3}));
+  FunctionRef<int(int, std::vector<int> const&)> const cvariant6 = of;
+  EXPECT_EQ(100 + 6 * 20, cvariant6(20, {}));
+  EXPECT_EQ(100 + 6 * 20, cvariant6(20, {1, 2, 3}));
+
+  FunctionRef<int(int, std::vector<int> const&)> variant6const = cof;
+  EXPECT_EQ(100 + 6 * 21, variant6const(21, {}));
+  FunctionRef<int(int, std::vector<int> const&)> const cvariant6const = cof;
+  EXPECT_EQ(100 + 6 * 21, cvariant6const(21, {}));
+}
+
+TEST(FunctionRef, DefaultConstructAndAssign) {
+  FunctionRef<int(int, int)> fref;
+
+  EXPECT_THROW(fref(1, 2), std::bad_function_call);
+
+  int (*func)(int, int) = [](int x, int y) { return 10 * x + y; };
+  fref = func;
+
+  EXPECT_EQ(42, fref(4, 2));
+}
+
+template <typename ValueType>
+class ForEach {
+ public:
+  template <typename InputIterator>
+  ForEach(InputIterator begin, InputIterator end)
+      : func_([begin, end](FunctionRef<void(ValueType)> f) {
+          for (auto it = begin; it != end; ++it) {
+            f(*it);
+          }
+        }) {}
+
+  void operator()(FunctionRef<void(ValueType)> f) const {
+    func_(f);
+  }
+
+ private:
+  Function<void(FunctionRef<void(ValueType)>) const> const func_;
+};
+
+TEST(FunctionRef, ForEach) {
+  std::list<int> s{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+  int sum = 0;
+
+  ForEach<int> fe{s.begin(), s.end()};
+
+  fe([&](int x) { sum += x; });
+
+  EXPECT_EQ(55, sum);
+}
index a227287a0d524b9636343894d79f1795ef8d10ba..a35455e1ccce1e1fd929745989ba95e44c264daf 100644 (file)
@@ -288,7 +288,9 @@ futures_test_SOURCES = \
 futures_test_LDADD = libfollytestmain.la
 TESTS += futures_test
 
-function_test_SOURCES = FunctionTest.cpp
+function_test_SOURCES = \
+               FunctionRefTest.cpp \
+               FunctionTest.cpp
 function_test_LDADD = libfollytestmain.la
 TESTS += function_test