Commit ecf14e67 authored by Sven Over's avatar Sven Over Committed by Facebook Github Bot 7

folly::Function: improve conversion of return types

Summary:Treat any return type as convertible to void:

As of C++17, std::function<void(Args...)> 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<double()> from a
Function<int()> or a Function<Base*()> from a
Function<Derived*()>.

Reviewed By: yfeldblum

Differential Revision: D3095583

fb-gh-sync-id: 6d924dc6e97f759d8109db4200e1cb9333a98d31
fbshipit-source-id: 6d924dc6e97f759d8109db4200e1cb9333a98d31
parent b6ed6b39
...@@ -271,22 +271,17 @@ template < ...@@ -271,22 +271,17 @@ template <
typename OtherFunctionType, typename OtherFunctionType,
FunctionMoveCtor OtherNTM, FunctionMoveCtor OtherNTM,
size_t OtherEmbedFunctorSize> size_t OtherEmbedFunctorSize>
Function<FunctionType, NTM, EmbedFunctorSize>:: Function<FunctionType, NTM, EmbedFunctorSize>::Function(
Function( Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other,
Function<OtherFunctionType, typename std::enable_if<std::is_same<
OtherNTM, typename Traits::NonConstFunctionType,
OtherEmbedFunctorSize>&& other) noexcept( typename detail::function::FunctionTypeTraits<
OtherNTM == FunctionMoveCtor::NO_THROW && OtherFunctionType>::NonConstFunctionType>::value>::
type*) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW &&
EmbedFunctorSize >= OtherEmbedFunctorSize) { EmbedFunctorSize >= OtherEmbedFunctorSize) {
using OtherFunction = using OtherFunction =
Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>; Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>;
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( static_assert(
!Traits::IsConst::value || OtherFunction::Traits::IsConst::value, !Traits::IsConst::value || OtherFunction::Traits::IsConst::value,
"Function: cannot move Function<R(Args...)> into " "Function: cannot move Function<R(Args...)> into "
......
...@@ -34,6 +34,15 @@ struct SelectNonConstFunctionTag { ...@@ -34,6 +34,15 @@ struct SelectNonConstFunctionTag {
using QualifiedPointer = T*; 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 <typename CallableR, typename FollyFunctionR>
using ReturnTypeMatches = std::integral_constant<
bool,
std::is_convertible<CallableR, FollyFunctionR>::value ||
std::is_same<typename std::decay<FollyFunctionR>::type, void>::value>;
// Helper class to extract properties from a function type // Helper class to extract properties from a function type
template <typename T> template <typename T>
struct FunctionTypeTraits; struct FunctionTypeTraits;
...@@ -71,7 +80,7 @@ struct FunctionTypeTraits<R(Args...)> { ...@@ -71,7 +80,7 @@ struct FunctionTypeTraits<R(Args...)> {
using DefaultSelectFunctionTag = SelectNonConstFunctionTag; using DefaultSelectFunctionTag = SelectNonConstFunctionTag;
template <typename F> template <typename F>
using IsCallable = using IsCallable =
std::is_convertible<typename std::result_of<F&(Args...)>::type, R>; ReturnTypeMatches<typename std::result_of<F&(Args...)>::type, R>;
template <typename T> template <typename T>
using QualifiedPointer = T*; using QualifiedPointer = T*;
template <typename Obj> template <typename Obj>
...@@ -111,7 +120,7 @@ struct FunctionTypeTraits<R(Args...) const> { ...@@ -111,7 +120,7 @@ struct FunctionTypeTraits<R(Args...) const> {
using DefaultSelectFunctionTag = SelectConstFunctionTag; using DefaultSelectFunctionTag = SelectConstFunctionTag;
template <typename F> template <typename F>
using IsCallable = using IsCallable =
std::is_convertible<typename std::result_of<F const&(Args...)>::type, R>; ReturnTypeMatches<typename std::result_of<F const&(Args...)>::type, R>;
template <typename T> template <typename T>
using QualifiedPointer = T const*; using QualifiedPointer = T const*;
template <typename Obj> template <typename Obj>
...@@ -138,17 +147,27 @@ struct FunctionTypeTraits<R(Args...) const> { ...@@ -138,17 +147,27 @@ struct FunctionTypeTraits<R(Args...) const> {
class ExecutorMixin; class ExecutorMixin;
}; };
// Helper template for checking if a type is a Function // Helper template for checking if a type T is a Function with the same
template <typename T> // function type as OtherFunctionType (except for const-ness which may differ)
struct IsFunction : public std::false_type {}; template <typename T, typename OtherFunctionType>
struct IsFunction : std::false_type {};
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
struct IsFunction<::folly::Function<FunctionType, NTM, EmbedFunctorSize>> template <
: public std::true_type {}; typename FunctionType,
FunctionMoveCtor NTM,
size_t EmbedFunctorSize,
typename OtherFunctionType>
struct IsFunction<
::folly::Function<FunctionType, NTM, EmbedFunctorSize>,
OtherFunctionType>
: std::is_same<
typename FunctionTypeTraits<FunctionType>::NonConstFunctionType,
typename FunctionTypeTraits<
OtherFunctionType>::NonConstFunctionType> {};
// Helper template to check if a functor can be called with arguments of type // 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 // Args..., if it returns a type convertible to R (or R is void), and also is
// Function. // not a folly::Function.
// Function objects can constructed or assigned from types for which // Function objects can constructed or assigned from types for which
// IsCallableHelper is true_type. // IsCallableHelper is true_type.
template <typename FunctionType> template <typename FunctionType>
...@@ -163,11 +182,12 @@ struct IsCallableHelper { ...@@ -163,11 +182,12 @@ struct IsCallableHelper {
}; };
template <typename F, typename FunctionType> template <typename F, typename FunctionType>
struct IsCallable : public std::integral_constant< struct IsCallable
bool, : public std::integral_constant<
(!IsFunction<typename std::decay<F>::type>::value && bool,
decltype(IsCallableHelper<FunctionType>::template test< (!IsFunction<typename std::decay<F>::type, FunctionType>::value &&
typename std::decay<F>::type>(0))::value)> {}; decltype(IsCallableHelper<FunctionType>::template test<
typename std::decay<F>::type>(0))::value)> {};
// MaybeUnaryOrBinaryFunction: helper template class for deriving // MaybeUnaryOrBinaryFunction: helper template class for deriving
// Function from std::unary_function or std::binary_function // Function from std::unary_function or std::binary_function
...@@ -245,8 +265,8 @@ class FunctionTypeTraits<R(Args...)>::ExecutorMixin { ...@@ -245,8 +265,8 @@ class FunctionTypeTraits<R(Args...)>::ExecutorMixin {
template <typename Ex> template <typename Ex>
static R invokeFunctor(ExecutorIf* executor, Args&&... args) { static R invokeFunctor(ExecutorIf* executor, Args&&... args) {
return folly::detail::function::invoke( return static_cast<R>(folly::detail::function::invoke(
*Ex::getFunctor(executor), std::forward<Args>(args)...); *Ex::getFunctor(executor), std::forward<Args>(args)...));
} }
// invokePtr is of type // invokePtr is of type
...@@ -282,8 +302,8 @@ class FunctionTypeTraits<R(Args...) const>::ExecutorMixin { ...@@ -282,8 +302,8 @@ class FunctionTypeTraits<R(Args...) const>::ExecutorMixin {
template <typename Ex> template <typename Ex>
static R invokeFunctor(ExecutorIf const* executor, Args&&... args) { static R invokeFunctor(ExecutorIf const* executor, Args&&... args) {
return folly::detail::function::invoke( return static_cast<R>(folly::detail::function::invoke(
*Ex::getFunctor(executor), std::forward<Args>(args)...); *Ex::getFunctor(executor), std::forward<Args>(args)...));
} }
// invokePtr is of type // invokePtr is of type
......
...@@ -308,10 +308,14 @@ class Function final ...@@ -308,10 +308,14 @@ class Function final
typename OtherFunctionType, typename OtherFunctionType,
FunctionMoveCtor OtherNTM, FunctionMoveCtor OtherNTM,
size_t OtherEmbedFunctorSize> size_t OtherEmbedFunctorSize>
Function(Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other) Function(
noexcept( Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other,
OtherNTM == FunctionMoveCtor::NO_THROW && typename std::enable_if<std::is_same<
EmbedFunctorSize >= OtherEmbedFunctorSize); typename Traits::NonConstFunctionType,
typename detail::function::FunctionTypeTraits<
OtherFunctionType>::NonConstFunctionType>::value>::type* =
0) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW &&
EmbedFunctorSize >= OtherEmbedFunctorSize);
/** /**
* Moves a `Function` with different template parameters with regards * Moves a `Function` with different template parameters with regards
......
...@@ -1081,3 +1081,124 @@ TEST(Function, SafeCaptureByReference) { ...@@ -1081,3 +1081,124 @@ TEST(Function, SafeCaptureByReference) {
EXPECT_EQ(sum, 999); 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<void()> f = [&]() -> int { return ++x; };
EXPECT_EQ(x, 95);
f();
EXPECT_EQ(x, 96);
Function<int()> g = [&]() -> int { return ++x; };
Function<void()> 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<double()> f1 = []() -> int { return 5; };
EXPECT_EQ(f1(), 5.0);
Function<int()> f2 = []() -> double { return 5.2; };
EXPECT_EQ(f2(), 5);
CDerived derived;
derived.x = 55;
Function<CBase const&()> f3 = [&]() -> CDerived const& { return derived; };
EXPECT_EQ(f3().x, 55);
Function<CBase const&()> f4 = [&]() -> CDerived& { return derived; };
EXPECT_EQ(f4().x, 55);
Function<CBase&()> f5 = [&]() -> CDerived& { return derived; };
EXPECT_EQ(f5().x, 55);
Function<CBase const*()> f6 = [&]() -> CDerived const* { return &derived; };
EXPECT_EQ(f6()->x, 55);
Function<CBase const*()> f7 = [&]() -> CDerived* { return &derived; };
EXPECT_EQ(f7()->x, 55);
Function<CBase*()> f8 = [&]() -> CDerived* { return &derived; };
EXPECT_EQ(f8()->x, 55);
Function<CBase()> 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<int()> f1 = []() -> int { return 5; };
Function<double()> cf1 = std::move(f1);
EXPECT_EQ(cf1(), 5.0);
Function<int()> ccf1 = std::move(cf1);
EXPECT_EQ(ccf1(), 5);
Function<double()> f2 = []() -> double { return 5.2; };
Function<int()> cf2 = std::move(f2);
EXPECT_EQ(cf2(), 5);
Function<double()> ccf2 = std::move(cf2);
EXPECT_EQ(ccf2(), 5.0);
CDerived derived;
derived.x = 55;
Function<CDerived const&()> f3 = [&]() -> CDerived const& { return derived; };
Function<CBase const&()> cf3 = std::move(f3);
EXPECT_EQ(cf3().x, 55);
Function<CDerived&()> f4 = [&]() -> CDerived& { return derived; };
Function<CBase const&()> cf4 = std::move(f4);
EXPECT_EQ(cf4().x, 55);
Function<CDerived&()> f5 = [&]() -> CDerived& { return derived; };
Function<CBase&()> cf5 = std::move(f5);
EXPECT_EQ(cf5().x, 55);
Function<CDerived const*()> f6 = [&]() -> CDerived const* {
return &derived;
};
Function<CBase const*()> cf6 = std::move(f6);
EXPECT_EQ(cf6()->x, 55);
Function<CDerived const*()> f7 = [&]() -> CDerived* { return &derived; };
Function<CBase const*()> cf7 = std::move(f7);
EXPECT_EQ(cf7()->x, 55);
Function<CDerived*()> f8 = [&]() -> CDerived* { return &derived; };
Function<CBase*()> cf8 = std::move(f8);
EXPECT_EQ(cf8()->x, 55);
Function<CDerived()> f9 = [&]() -> CDerived {
auto d = derived;
d.x = 66;
return d;
};
Function<CBase()> cf9 = std::move(f9);
EXPECT_EQ(cf9().x, 66);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment