Commit 18cfe815 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Generic detection of empty-callable in Function ctor

Summary:
[Folly] Generic detection of empty-callable in `Function` ctor.

Constructing a `folly::Function` from an empty `std::function` should result in an empty object. However, it results in a full object which, when invoked, throws `std::bad_function_call`. This may be a problem in some cases which need to use the emptiness/fullness property to tell whether `std::bad_function_call` would be thrown if the object were to be invoked.

This solution proposes a new protocol: to check arguments of all types, not just pointers, for constructibility-from and equality-comparability-with `nullptr`, and then if those two checks pass, to check equality-comparison-with `nullptr`. If the argument type is is constructible from `nullptr` and is equality-comparable with `nullptr` and compares equal to `nullptr, then treat the argument as empty, i.e., as if it were `nullptr`. This way, an empty `std::function` gets treated as if it were `nullptr` - as well as any other custom function object type out there - without having to enumerate every one of them.

The new protocol is somewhat strict. An alternative to the new protocol is to check if the object is castable to `bool` and, if it is, cast to `bool`, but such a protocol is broader than the one proposed in this diff.

Fixes #886.

Reviewed By: nbronson

Differential Revision: D9287898

fbshipit-source-id: bcb574387122aac92d154e81732e82ddbcdd4915
parent 8cf16b1e
...@@ -273,14 +273,27 @@ using EnableIfNotFunction = ...@@ -273,14 +273,27 @@ using EnableIfNotFunction =
struct CoerceTag {}; struct CoerceTag {};
template <typename, typename T>
struct IsFunctionNullptrTestable : std::false_type {};
template <typename T> template <typename T>
bool isNullPtrFn(T* p) { struct IsFunctionNullptrTestable<
return p == nullptr; void_t<decltype(
} static_cast<bool>(static_cast<T const&>(T(nullptr)) == nullptr))>,
T> : std::true_type {};
template <typename T> template <typename T>
std::false_type isNullPtrFn(T&&) { constexpr std::enable_if_t< //
!IsFunctionNullptrTestable<void, T>::value,
std::false_type>
isEmptyFunction(T const&) {
return {}; return {};
} }
template <typename T>
constexpr std::enable_if_t<IsFunctionNullptrTestable<void, T>::value, bool>
isEmptyFunction(T const& t) {
return static_cast<bool>(t == nullptr);
}
template <typename F, typename... Args> template <typename F, typename... Args>
using CallableResult = decltype(std::declval<F>()(std::declval<Args>()...)); using CallableResult = decltype(std::declval<F>()(std::declval<Args>()...));
...@@ -297,11 +310,34 @@ class FunctionTraitsSharedProxy { ...@@ -297,11 +310,34 @@ class FunctionTraitsSharedProxy {
std::shared_ptr<Function<F>> sp_; std::shared_ptr<Function<F>> sp_;
public: public:
explicit FunctionTraitsSharedProxy(std::nullptr_t) noexcept {}
explicit FunctionTraitsSharedProxy(Function<F>&& func) explicit FunctionTraitsSharedProxy(Function<F>&& func)
: sp_(std::make_shared<Function<F>>(std::move(func))) {} : sp_(std::make_shared<Function<F>>(std::move(func))) {}
R operator()(A&&... args) const { R operator()(A&&... args) const {
return (*sp_)(static_cast<A&&>(args)...); return (*sp_)(static_cast<A&&>(args)...);
} }
friend bool operator==(
FunctionTraitsSharedProxy<F, R, A...> const& proxy,
std::nullptr_t) noexcept {
return proxy.sp_ == nullptr;
}
friend bool operator!=(
FunctionTraitsSharedProxy<F, R, A...> const& proxy,
std::nullptr_t) noexcept {
return proxy.sp_ != nullptr;
}
friend bool operator==(
std::nullptr_t,
FunctionTraitsSharedProxy<F, R, A...> const& proxy) noexcept {
return proxy.sp_ == nullptr;
}
friend bool operator!=(
std::nullptr_t,
FunctionTraitsSharedProxy<F, R, A...> const& proxy) noexcept {
return proxy.sp_ != nullptr;
}
}; };
template <typename FunctionType> template <typename FunctionType>
...@@ -532,7 +568,7 @@ class Function final : private detail::function::FunctionTraits<FunctionType> { ...@@ -532,7 +568,7 @@ class Function final : private detail::function::FunctionTraits<FunctionType> {
template <typename Fun> template <typename Fun>
Function(Fun&& fun, SmallTag) noexcept { Function(Fun&& fun, SmallTag) noexcept {
using FunT = typename std::decay<Fun>::type; using FunT = typename std::decay<Fun>::type;
if (!detail::function::isNullPtrFn(fun)) { if (!detail::function::isEmptyFunction(fun)) {
::new (static_cast<void*>(&data_.tiny)) FunT(static_cast<Fun&&>(fun)); ::new (static_cast<void*>(&data_.tiny)) FunT(static_cast<Fun&&>(fun));
call_ = &Traits::template callSmall<FunT>; call_ = &Traits::template callSmall<FunT>;
exec_ = &detail::function::execSmall<FunT>; exec_ = &detail::function::execSmall<FunT>;
...@@ -542,10 +578,12 @@ class Function final : private detail::function::FunctionTraits<FunctionType> { ...@@ -542,10 +578,12 @@ class Function final : private detail::function::FunctionTraits<FunctionType> {
template <typename Fun> template <typename Fun>
Function(Fun&& fun, HeapTag) { Function(Fun&& fun, HeapTag) {
using FunT = typename std::decay<Fun>::type; using FunT = typename std::decay<Fun>::type;
if (!detail::function::isEmptyFunction(fun)) {
data_.big = new FunT(static_cast<Fun&&>(fun)); data_.big = new FunT(static_cast<Fun&&>(fun));
call_ = &Traits::template callBig<FunT>; call_ = &Traits::template callBig<FunT>;
exec_ = &detail::function::execBig<FunT>; exec_ = &detail::function::execBig<FunT>;
} }
}
template <typename Signature> template <typename Signature>
Function(Function<Signature>&& that, CoerceTag) Function(Function<Signature>&& that, CoerceTag)
...@@ -897,7 +935,14 @@ class FunctionRef<ReturnType(Args...)> final { ...@@ -897,7 +935,14 @@ class FunctionRef<ReturnType(Args...)> final {
* *
* Invoking it will throw std::bad_function_call. * Invoking it will throw std::bad_function_call.
*/ */
FunctionRef() = default; constexpr FunctionRef() = default;
/**
* Like default constructor. Constructs an empty FunctionRef.
*
* Invoking it will throw std::bad_function_call.
*/
constexpr explicit FunctionRef(std::nullptr_t) noexcept {}
/** /**
* Construct a FunctionRef from a reference to a callable object. * Construct a FunctionRef from a reference to a callable object.
...@@ -922,9 +967,31 @@ class FunctionRef<ReturnType(Args...)> final { ...@@ -922,9 +967,31 @@ class FunctionRef<ReturnType(Args...)> final {
return call_(object_, static_cast<Args&&>(args)...); return call_(object_, static_cast<Args&&>(args)...);
} }
constexpr explicit operator bool() const { constexpr explicit operator bool() const noexcept {
return object_; return object_;
} }
constexpr friend bool operator==(
FunctionRef<ReturnType(Args...)> ref,
std::nullptr_t) noexcept {
return ref.object_ == nullptr;
}
constexpr friend bool operator!=(
FunctionRef<ReturnType(Args...)> ref,
std::nullptr_t) noexcept {
return ref.object_ != nullptr;
}
constexpr friend bool operator==(
std::nullptr_t,
FunctionRef<ReturnType(Args...)> ref) noexcept {
return ref.object_ == nullptr;
}
constexpr friend bool operator!=(
std::nullptr_t,
FunctionRef<ReturnType(Args...)> ref) noexcept {
return ref.object_ != nullptr;
}
}; };
} // namespace folly } // namespace folly
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <folly/portability/GTest.h> #include <folly/portability/GTest.h>
using folly::Function; using folly::Function;
using folly::FunctionRef;
namespace { namespace {
int func_int_int_add_25(int x) { int func_int_int_add_25(int x) {
...@@ -261,6 +262,56 @@ TEST(Function, Emptiness_T) { ...@@ -261,6 +262,56 @@ TEST(Function, Emptiness_T) {
EXPECT_EQ(nullptr, i); EXPECT_EQ(nullptr, i);
EXPECT_FALSE(i); EXPECT_FALSE(i);
EXPECT_THROW(i(107), std::bad_function_call); EXPECT_THROW(i(107), std::bad_function_call);
struct CastableToBool {
bool val;
/* implicit */ CastableToBool(bool b) : val(b) {}
explicit operator bool() {
return val;
}
};
// models std::function
struct NullptrTestableInSitu {
int res;
explicit NullptrTestableInSitu(std::nullptr_t) : res(1) {}
explicit NullptrTestableInSitu(int i) : res(i) {}
CastableToBool operator==(std::nullptr_t) const {
return res % 3 != 1;
}
int operator()(int in) const {
return res * in;
}
};
struct NullptrTestableOnHeap : NullptrTestableInSitu {
unsigned char data[1024 - sizeof(NullptrTestableInSitu)];
using NullptrTestableInSitu::NullptrTestableInSitu;
};
Function<int(int)> j(NullptrTestableInSitu(2));
EXPECT_EQ(j, nullptr);
EXPECT_EQ(nullptr, j);
EXPECT_FALSE(j);
EXPECT_THROW(j(107), std::bad_function_call);
Function<int(int)> k(NullptrTestableInSitu(4));
EXPECT_NE(k, nullptr);
EXPECT_NE(nullptr, k);
EXPECT_TRUE(k);
EXPECT_EQ(428, k(107));
Function<int(int)> l(NullptrTestableOnHeap(2));
EXPECT_EQ(l, nullptr);
EXPECT_EQ(nullptr, l);
EXPECT_FALSE(l);
EXPECT_THROW(l(107), std::bad_function_call);
Function<int(int)> m(NullptrTestableOnHeap(4));
EXPECT_NE(m, nullptr);
EXPECT_NE(nullptr, m);
EXPECT_TRUE(m);
EXPECT_EQ(428, m(107));
auto noopfun = [] {};
EXPECT_EQ(nullptr, FunctionRef<void()>(nullptr));
EXPECT_NE(nullptr, FunctionRef<void()>(noopfun));
EXPECT_EQ(FunctionRef<void()>(nullptr), nullptr);
EXPECT_NE(FunctionRef<void()>(noopfun), nullptr);
} }
// TEST ===================================================================== // TEST =====================================================================
......
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