Commit 8cf67ca0 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

let FunctionRef detect empty-callable

Summary: Let `folly::FunctionRef` detect empty-callable ctor arguments like `folly::Function` does. Handles pointers which may be nullptr.

Reviewed By: luciang

Differential Revision: D25840538

fbshipit-source-id: 8cfccd57aa545ffc95807876d486ecf94aba8d62
parent d973a73e
......@@ -984,13 +984,22 @@ class FunctionRef<ReturnType(Args...)> final {
throw_exception<std::bad_function_call>();
}
template <typename Fun>
template <
typename Fun,
std::enable_if_t<!std::is_pointer<Fun>::value, int> = 0>
static ReturnType call(CallArg<Args>... args, void* object) {
using Pointer = std::add_pointer_t<Fun>;
return static_cast<ReturnType>(invoke(
static_cast<Fun&&>(*static_cast<Pointer>(object)),
static_cast<Args&&>(args)...));
}
template <
typename Fun,
std::enable_if_t<std::is_pointer<Fun>::value, int> = 0>
static ReturnType call(CallArg<Args>... args, void* object) {
return static_cast<ReturnType>(
invoke(reinterpret_cast<Fun>(object), static_cast<Args&&>(args)...));
}
void* object_{nullptr};
Call call_{&FunctionRef::uninitCall};
......@@ -1011,23 +1020,44 @@ class FunctionRef<ReturnType(Args...)> final {
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. If the
* callable is considered to be an empty callable, the FunctionRef will be
* empty.
*/
template <
typename Fun,
typename std::enable_if<
std::enable_if_t<
Conjunction<
Negation<std::is_same<FunctionRef, std::decay_t<Fun>>>,
is_invocable_r<ReturnType, Fun&&, Args&&...>>::value,
int>::type = 0>
constexpr /* implicit */ FunctionRef(Fun&& fun) noexcept
// `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::template call<Fun>) {}
int> = 0>
constexpr /* implicit */ FunctionRef(Fun&& fun) noexcept {
// `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`
auto& ref = fun; // work around forwarding lint advice
if (!detail::function::isEmptyFunction(ref)) {
auto ptr = std::addressof(ref);
object_ = const_cast<void*>(static_cast<void const*>(ptr));
call_ = &FunctionRef::template call<Fun>;
}
}
/**
* Constructs a FunctionRef from a pointer to a function. If the
* pointer is nullptr, the FunctionRef will be empty.
*/
template <
typename Fun,
std::enable_if_t<std::is_function<Fun>::value, int> = 0,
std::enable_if_t<is_invocable_r_v<ReturnType, Fun&, Args&&...>, int> = 0>
constexpr /* implicit */ FunctionRef(Fun* fun) noexcept {
if (fun) {
object_ = const_cast<void*>(reinterpret_cast<void const*>(fun));
call_ = &FunctionRef::template call<Fun*>;
}
}
ReturnType operator()(Args... args) const {
return call_(static_cast<Args&&>(args)..., object_);
......
......@@ -19,6 +19,12 @@
#include <folly/Function.h>
#include <folly/portability/GTest.h>
namespace {
int func_int_int_add_25(int x) {
return x + 25;
}
} // namespace
namespace folly {
TEST(FunctionRef, Traits) {
......@@ -210,4 +216,88 @@ TEST(FunctionRef, ForEach) {
EXPECT_EQ(55, sum);
}
TEST(FunctionRef, Emptiness) {
FunctionRef<int(int)> f;
EXPECT_EQ(f, nullptr);
EXPECT_EQ(nullptr, f);
EXPECT_FALSE(f);
EXPECT_THROW(f(98), std::bad_function_call);
auto g_ = [](int x) { return x + 1; };
FunctionRef<int(int)> g(g_);
EXPECT_NE(g, nullptr);
EXPECT_NE(nullptr, g);
// Explicitly convert to bool to work around
// https://github.com/google/googletest/issues/429
EXPECT_TRUE(bool(g));
EXPECT_EQ(100, g(99));
FunctionRef<int(int)> h(&func_int_int_add_25);
EXPECT_NE(h, nullptr);
EXPECT_NE(nullptr, h);
EXPECT_TRUE(bool(h));
EXPECT_EQ(125, h(100));
h = {};
EXPECT_EQ(h, nullptr);
EXPECT_EQ(nullptr, h);
EXPECT_FALSE(h);
EXPECT_THROW(h(101), std::bad_function_call);
auto i_ = Function<int(int)>{};
FunctionRef<int(int)> i{i_};
EXPECT_EQ(i, nullptr);
EXPECT_EQ(nullptr, i);
EXPECT_FALSE(i);
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;
FOLLY_MAYBE_UNUSED explicit NullptrTestableInSitu(std::nullptr_t);
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;
};
auto j_ = NullptrTestableInSitu(2);
FunctionRef<int(int)> j(j_);
EXPECT_EQ(j, nullptr);
EXPECT_EQ(nullptr, j);
EXPECT_FALSE(j);
EXPECT_THROW(j(107), std::bad_function_call);
auto k_ = NullptrTestableInSitu(4);
FunctionRef<int(int)> k(k_);
EXPECT_NE(k, nullptr);
EXPECT_NE(nullptr, k);
EXPECT_TRUE(k);
EXPECT_EQ(428, k(107));
auto l_ = NullptrTestableOnHeap(2);
FunctionRef<int(int)> l(l_);
EXPECT_EQ(l, nullptr);
EXPECT_EQ(nullptr, l);
EXPECT_FALSE(l);
EXPECT_THROW(l(107), std::bad_function_call);
auto m_ = NullptrTestableOnHeap(4);
FunctionRef<int(int)> m(m_);
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);
}
} // namespace folly
......@@ -23,7 +23,6 @@
#include <folly/portability/GTest.h>
using folly::Function;
using folly::FunctionRef;
namespace {
int func_int_int_add_25(int x) {
......@@ -269,7 +268,7 @@ TEST(Function, Emptiness_T) {
// models std::function
struct NullptrTestableInSitu {
int res;
explicit NullptrTestableInSitu(std::nullptr_t) : res(1) {}
FOLLY_MAYBE_UNUSED explicit NullptrTestableInSitu(std::nullptr_t);
explicit NullptrTestableInSitu(int i) : res(i) {}
CastableToBool operator==(std::nullptr_t) const { return res % 3 != 1; }
int operator()(int in) const { return res * in; }
......@@ -298,12 +297,6 @@ TEST(Function, Emptiness_T) {
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 =====================================================================
......
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