From 4b39d461103b3be68903e8e882d59e2f93c136ea Mon Sep 17 00:00:00 2001 From: Sven Over Date: Tue, 27 Sep 2016 07:44:32 -0700 Subject: [PATCH] Introducing folly::FunctionRef 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 | 92 ++++++++++++++++- folly/test/FunctionRefTest.cpp | 177 +++++++++++++++++++++++++++++++++ folly/test/Makefile.am | 4 +- 3 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 folly/test/FunctionRefTest.cpp diff --git a/folly/Function.h b/folly/Function.h index 91a8c6a1..a201c95c 100644 --- a/folly/Function.h +++ b/folly/Function.h @@ -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())) - )>; + noexcept(FunT(std::declval())))>; 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 +inline auto invoke(F&& f, Args&&... args) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +template +inline auto invoke(M(C::*d), Args&&... args) + -> decltype(std::mem_fn(d)(std::forward(args)...)) { + return std::mem_fn(d)(std::forward(args)...); +} + } // namespace function } // namespace detail @@ -675,4 +687,80 @@ Function constCastFunction( Function&& 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 +class FunctionRef; + +template +class FunctionRef final { + using Call = ReturnType (*)(void*, Args&&...); + + void* object_{nullptr}; + Call call_{&FunctionRef::uninitCall}; + + static ReturnType uninitCall(void*, Args&&...) { + throw std::bad_function_call(); + } + + template + static ReturnType call(void* object, Args&&... args) { + return static_cast(detail::function::invoke( + *static_cast(object), static_cast(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 + /* implicit */ FunctionRef(Fun&& fun) noexcept { + using ReferencedType = typename std::remove_reference::type; + + static_assert( + std::is_convertible< + typename std::result_of::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(static_cast(std::addressof(fun))); + call_ = &FunctionRef::call; + } + + ReturnType operator()(Args... args) const { + return call_(object_, static_cast(args)...); + } +}; + } // namespace folly diff --git a/folly/test/FunctionRefTest.cpp b/folly/test/FunctionRefTest.cpp new file mode 100644 index 00000000..621b0ad8 --- /dev/null +++ b/folly/test/FunctionRefTest.cpp @@ -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 + +#include +#include + +using folly::Function; +using folly::FunctionRef; + +TEST(FunctionRef, Simple) { + int x = 1000; + auto lambda = [&x](int v) { return x += v; }; + + FunctionRef fref = lambda; + EXPECT_EQ(1005, fref(5)); + EXPECT_EQ(1011, fref(6)); + EXPECT_EQ(1018, fref(7)); + + FunctionRef const cfref = lambda; + EXPECT_EQ(1023, cfref(5)); + EXPECT_EQ(1029, cfref(6)); + EXPECT_EQ(1036, cfref(7)); + + auto const& clambda = lambda; + + FunctionRef fcref = clambda; + EXPECT_EQ(1041, fcref(5)); + EXPECT_EQ(1047, fcref(6)); + EXPECT_EQ(1054, fcref(7)); + + FunctionRef 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 fref = funcptr; + EXPECT_EQ(100, fref(10)); + EXPECT_EQ(121, fref(11)); + + FunctionRef 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 const&) const { + return 100 + 6 * x; + } + }; + OverloadedFunctor of; + auto const& cof = of; + + FunctionRef variant1 = of; + EXPECT_EQ(100 + 1 * 15, variant1(15)); + FunctionRef const cvariant1 = of; + EXPECT_EQ(100 + 1 * 15, cvariant1(15)); + + FunctionRef variant2 = cof; + EXPECT_EQ(100 + 2 * 16, variant2(16)); + FunctionRef const cvariant2 = cof; + EXPECT_EQ(100 + 2 * 16, cvariant2(16)); + + FunctionRef variant3 = of; + EXPECT_EQ(100 + 3 * 17, variant3(17, 0)); + FunctionRef const cvariant3 = of; + EXPECT_EQ(100 + 3 * 17, cvariant3(17, 0)); + + FunctionRef variant4 = cof; + EXPECT_EQ(100 + 4 * 18, variant4(18, 0)); + FunctionRef const cvariant4 = cof; + EXPECT_EQ(100 + 4 * 18, cvariant4(18, 0)); + + FunctionRef variant5 = of; + EXPECT_EQ(100 + 5 * 19, variant5(19, "foo")); + FunctionRef const cvariant5 = of; + EXPECT_EQ(100 + 5 * 19, cvariant5(19, "foo")); + + FunctionRef const&)> variant6 = of; + EXPECT_EQ(100 + 6 * 20, variant6(20, {})); + EXPECT_EQ(100 + 6 * 20, variant6(20, {1, 2, 3})); + FunctionRef const&)> const cvariant6 = of; + EXPECT_EQ(100 + 6 * 20, cvariant6(20, {})); + EXPECT_EQ(100 + 6 * 20, cvariant6(20, {1, 2, 3})); + + FunctionRef const&)> variant6const = cof; + EXPECT_EQ(100 + 6 * 21, variant6const(21, {})); + FunctionRef const&)> const cvariant6const = cof; + EXPECT_EQ(100 + 6 * 21, cvariant6const(21, {})); +} + +TEST(FunctionRef, DefaultConstructAndAssign) { + FunctionRef 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 +class ForEach { + public: + template + ForEach(InputIterator begin, InputIterator end) + : func_([begin, end](FunctionRef f) { + for (auto it = begin; it != end; ++it) { + f(*it); + } + }) {} + + void operator()(FunctionRef f) const { + func_(f); + } + + private: + Function) const> const func_; +}; + +TEST(FunctionRef, ForEach) { + std::list s{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + int sum = 0; + + ForEach fe{s.begin(), s.end()}; + + fe([&](int x) { sum += x; }); + + EXPECT_EQ(55, sum); +} diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index a227287a..a35455e1 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -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 -- 2.34.1