From ecf14e67a9f98b0548ce119f400b66e9a2c58a69 Mon Sep 17 00:00:00 2001 From: Sven Over Date: Sat, 26 Mar 2016 02:10:46 -0700 Subject: [PATCH] folly::Function: improve conversion of return types Summary:Treat any return type as convertible to void: As of C++17, std::function can be set to callables returning non-void types when called with parameters Args.... This diff adds that capability to folly::Function. It also adds unit tests, not only for ignoring return types, but also for correctly converting between the return type of the embedded callabled and the return type of the encapsulating folly::Function. Allow conversion of one folly::Function type to another one which declares a return type the original one can be converted to: E.g. allow to construct a Function from a Function or a Function from a Function. Reviewed By: yfeldblum Differential Revision: D3095583 fb-gh-sync-id: 6d924dc6e97f759d8109db4200e1cb9333a98d31 fbshipit-source-id: 6d924dc6e97f759d8109db4200e1cb9333a98d31 --- folly/Function-inl.h | 19 +++--- folly/Function-pre.h | 60 ++++++++++++------ folly/Function.h | 12 ++-- folly/test/FunctionTest.cpp | 121 ++++++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 36 deletions(-) diff --git a/folly/Function-inl.h b/folly/Function-inl.h index 05f79083..7c34f92c 100644 --- a/folly/Function-inl.h +++ b/folly/Function-inl.h @@ -271,22 +271,17 @@ template < typename OtherFunctionType, FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize> -Function:: - Function( - Function&& other) noexcept( - OtherNTM == FunctionMoveCtor::NO_THROW && +Function::Function( + Function&& other, + typename std::enable_if::NonConstFunctionType>::value>:: + type*) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && EmbedFunctorSize >= OtherEmbedFunctorSize) { using OtherFunction = Function; - static_assert( - std::is_same< - typename Traits::NonConstFunctionType, - typename OtherFunction::Traits::NonConstFunctionType>::value, - "Function: cannot move into a Function with different " - "parameter signature"); static_assert( !Traits::IsConst::value || OtherFunction::Traits::IsConst::value, "Function: cannot move Function into " diff --git a/folly/Function-pre.h b/folly/Function-pre.h index 1196f571..293efbcb 100644 --- a/folly/Function-pre.h +++ b/folly/Function-pre.h @@ -34,6 +34,15 @@ struct SelectNonConstFunctionTag { using QualifiedPointer = T*; }; +// Helper to check whether the return type of a callable matches that of +// a folly::Function object. Either because the former is convertible to +// the latter, or the latter is void (possibly cv-qualified) +template +using ReturnTypeMatches = std::integral_constant< + bool, + std::is_convertible::value || + std::is_same::type, void>::value>; + // Helper class to extract properties from a function type template struct FunctionTypeTraits; @@ -71,7 +80,7 @@ struct FunctionTypeTraits { using DefaultSelectFunctionTag = SelectNonConstFunctionTag; template using IsCallable = - std::is_convertible::type, R>; + ReturnTypeMatches::type, R>; template using QualifiedPointer = T*; template @@ -111,7 +120,7 @@ struct FunctionTypeTraits { using DefaultSelectFunctionTag = SelectConstFunctionTag; template using IsCallable = - std::is_convertible::type, R>; + ReturnTypeMatches::type, R>; template using QualifiedPointer = T const*; template @@ -138,17 +147,27 @@ struct FunctionTypeTraits { class ExecutorMixin; }; -// Helper template for checking if a type is a Function -template -struct IsFunction : public std::false_type {}; - -template -struct IsFunction<::folly::Function> - : public std::true_type {}; +// Helper template for checking if a type T is a Function with the same +// function type as OtherFunctionType (except for const-ness which may differ) +template +struct IsFunction : std::false_type {}; + +template < + typename FunctionType, + FunctionMoveCtor NTM, + size_t EmbedFunctorSize, + typename OtherFunctionType> +struct IsFunction< + ::folly::Function, + OtherFunctionType> + : std::is_same< + typename FunctionTypeTraits::NonConstFunctionType, + typename FunctionTypeTraits< + OtherFunctionType>::NonConstFunctionType> {}; // Helper template to check if a functor can be called with arguments of type -// Args..., if it returns a type convertible to R, and also is not a -// Function. +// Args..., if it returns a type convertible to R (or R is void), and also is +// not a folly::Function. // Function objects can constructed or assigned from types for which // IsCallableHelper is true_type. template @@ -163,11 +182,12 @@ struct IsCallableHelper { }; template -struct IsCallable : public std::integral_constant< - bool, - (!IsFunction::type>::value && - decltype(IsCallableHelper::template test< - typename std::decay::type>(0))::value)> {}; +struct IsCallable + : public std::integral_constant< + bool, + (!IsFunction::type, FunctionType>::value && + decltype(IsCallableHelper::template test< + typename std::decay::type>(0))::value)> {}; // MaybeUnaryOrBinaryFunction: helper template class for deriving // Function from std::unary_function or std::binary_function @@ -245,8 +265,8 @@ class FunctionTypeTraits::ExecutorMixin { template static R invokeFunctor(ExecutorIf* executor, Args&&... args) { - return folly::detail::function::invoke( - *Ex::getFunctor(executor), std::forward(args)...); + return static_cast(folly::detail::function::invoke( + *Ex::getFunctor(executor), std::forward(args)...)); } // invokePtr is of type @@ -282,8 +302,8 @@ class FunctionTypeTraits::ExecutorMixin { template static R invokeFunctor(ExecutorIf const* executor, Args&&... args) { - return folly::detail::function::invoke( - *Ex::getFunctor(executor), std::forward(args)...); + return static_cast(folly::detail::function::invoke( + *Ex::getFunctor(executor), std::forward(args)...)); } // invokePtr is of type diff --git a/folly/Function.h b/folly/Function.h index d33ec9eb..66cb96ba 100644 --- a/folly/Function.h +++ b/folly/Function.h @@ -308,10 +308,14 @@ class Function final typename OtherFunctionType, FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize> - Function(Function&& other) - noexcept( - OtherNTM == FunctionMoveCtor::NO_THROW && - EmbedFunctorSize >= OtherEmbedFunctorSize); + Function( + Function&& other, + typename std::enable_if::NonConstFunctionType>::value>::type* = + 0) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && + EmbedFunctorSize >= OtherEmbedFunctorSize); /** * Moves a `Function` with different template parameters with regards diff --git a/folly/test/FunctionTest.cpp b/folly/test/FunctionTest.cpp index 8019b59b..718c00fc 100644 --- a/folly/test/FunctionTest.cpp +++ b/folly/test/FunctionTest.cpp @@ -1081,3 +1081,124 @@ TEST(Function, SafeCaptureByReference) { EXPECT_EQ(sum, 999); } + +// TEST ===================================================================== +// IgnoreReturnValue + +TEST(Function, IgnoreReturnValue) { + int x = 95; + + // Assign a lambda that return int to a folly::Function that returns void. + Function f = [&]() -> int { return ++x; }; + + EXPECT_EQ(x, 95); + f(); + EXPECT_EQ(x, 96); + + Function g = [&]() -> int { return ++x; }; + Function cg = std::move(g); + + EXPECT_EQ(x, 96); + cg(); + EXPECT_EQ(x, 97); +} + +// TEST ===================================================================== +// ReturnConvertible, ConvertReturnType + +TEST(Function, ReturnConvertible) { + struct CBase { + int x; + }; + struct CDerived : CBase {}; + + Function f1 = []() -> int { return 5; }; + EXPECT_EQ(f1(), 5.0); + + Function f2 = []() -> double { return 5.2; }; + EXPECT_EQ(f2(), 5); + + CDerived derived; + derived.x = 55; + + Function f3 = [&]() -> CDerived const& { return derived; }; + EXPECT_EQ(f3().x, 55); + + Function f4 = [&]() -> CDerived& { return derived; }; + EXPECT_EQ(f4().x, 55); + + Function f5 = [&]() -> CDerived& { return derived; }; + EXPECT_EQ(f5().x, 55); + + Function f6 = [&]() -> CDerived const* { return &derived; }; + EXPECT_EQ(f6()->x, 55); + + Function f7 = [&]() -> CDerived* { return &derived; }; + EXPECT_EQ(f7()->x, 55); + + Function f8 = [&]() -> CDerived* { return &derived; }; + EXPECT_EQ(f8()->x, 55); + + Function f9 = [&]() -> CDerived { + auto d = derived; + d.x = 66; + return d; + }; + EXPECT_EQ(f9().x, 66); +} + +TEST(Function, ConvertReturnType) { + struct CBase { + int x; + }; + struct CDerived : CBase {}; + + Function f1 = []() -> int { return 5; }; + Function cf1 = std::move(f1); + EXPECT_EQ(cf1(), 5.0); + Function ccf1 = std::move(cf1); + EXPECT_EQ(ccf1(), 5); + + Function f2 = []() -> double { return 5.2; }; + Function cf2 = std::move(f2); + EXPECT_EQ(cf2(), 5); + Function ccf2 = std::move(cf2); + EXPECT_EQ(ccf2(), 5.0); + + CDerived derived; + derived.x = 55; + + Function f3 = [&]() -> CDerived const& { return derived; }; + Function cf3 = std::move(f3); + EXPECT_EQ(cf3().x, 55); + + Function f4 = [&]() -> CDerived& { return derived; }; + Function cf4 = std::move(f4); + EXPECT_EQ(cf4().x, 55); + + Function f5 = [&]() -> CDerived& { return derived; }; + Function cf5 = std::move(f5); + EXPECT_EQ(cf5().x, 55); + + Function f6 = [&]() -> CDerived const* { + return &derived; + }; + Function cf6 = std::move(f6); + EXPECT_EQ(cf6()->x, 55); + + Function f7 = [&]() -> CDerived* { return &derived; }; + Function cf7 = std::move(f7); + EXPECT_EQ(cf7()->x, 55); + + Function f8 = [&]() -> CDerived* { return &derived; }; + Function cf8 = std::move(f8); + EXPECT_EQ(cf8()->x, 55); + + Function f9 = [&]() -> CDerived { + auto d = derived; + d.x = 66; + return d; + }; + Function cf9 = std::move(f9); + EXPECT_EQ(cf9().x, 66); +} -- 2.34.1