diff --git a/webrtc/base/function_view.h b/webrtc/base/function_view.h index 6a7c9109ca..861bccff3e 100644 --- a/webrtc/base/function_view.h +++ b/webrtc/base/function_view.h @@ -14,16 +14,27 @@ #include #include +#include "webrtc/base/checks.h" + // Just like std::function, FunctionView will wrap any callable and hide its // actual type, exposing only its signature. But unlike std::function, // FunctionView doesn't own its callable---it just points to it. Thus, it's a // good choice mainly as a function argument when the callable argument will // not be called again once the function has returned. // -// TODO(kwiberg): FunctionView doesn't work with function pointers, just with -// lambdas. It's trivial to work around this by wrapping the function pointer -// in a stateless lambda, but it's tedious so it'd be nice to not have to do -// it. +// Its constructors are implicit, so that callers won't have to convert lambdas +// and other callables to FunctionView explicitly. This is +// safe because FunctionView is only a reference to the real callable. +// +// Example use: +// +// void SomeFunction(rtc::FunctionView index_transform); +// ... +// SomeFunction([](int i) { return 2 * i + 1; }); +// +// Note: FunctionView is tiny (essentially just two pointers) and trivially +// copyable, so it's probably cheaper to pass it by value than by const +// reference. namespace rtc { @@ -33,36 +44,85 @@ class FunctionView; // Undefined. template class FunctionView final { public: - // This constructor is implicit, so that callers won't have to convert - // lambdas and other callables to FunctionView explicitly. - // This is safe because FunctionView is only a reference to the real - // callable. - // - // We jump through some template metaprogramming hoops to ensure that this - // constructor does *not* accept FunctionView arguments. That way, copy - // construction, assignment, swap etc. will all do the obvious thing (because - // they use the implicitly-declared copy constructor and copy assignment), - // and we will never get a FunctionView object that points to another - // FunctionView. - template ::type>::type>::value>::type* = nullptr> + // Constructor for lambdas and other callables; it accepts every type of + // argument except those noted in its enable_if call. + template < + typename F, + typename std::enable_if< + // Not for function pointers; we have another constructor for that + // below. + !std::is_function::type>::type>::value && + + // Not for nullptr; we have another constructor for that below. + !std::is_same::type>::value && + + // Not for FunctionView objects; we have another constructor for that + // (the implicitly declared copy constructor). + !std::is_same::type>::type>::value>::type* = nullptr> FunctionView(F&& f) - : f_(&f), call_(Call::type>) {} + : call_(CallVoidPtr::type>) { + f_.void_ptr = &f; + } + + // Constructor that accepts function pointers. If the argument is null, the + // result is an empty FunctionView. + template < + typename F, + typename std::enable_if::type>::type>::value>::type* = + nullptr> + FunctionView(F&& f) + : call_(f ? CallFunPtr::type> : nullptr) { + f_.fun_ptr = reinterpret_cast(f); + } + + // Constructor that accepts nullptr. It creates an empty FunctionView. + template ::type>::value>::type* = nullptr> + FunctionView(F&& f) : call_(nullptr) {} + + // Default constructor. Creates an empty FunctionView. + FunctionView() : call_(nullptr) {} RetT operator()(ArgT... args) const { + RTC_DCHECK(call_); return call_(f_, std::forward(args)...); } + // Returns true if we have a function, false if we don't (i.e., we're null). + explicit operator bool() const { return !!call_; } + private: + union VoidUnion { + void* void_ptr; + void (*fun_ptr)(); + }; + template - static RetT Call(void* f, ArgT... args) { - return (*static_cast(f))(std::forward(args)...); + static RetT CallVoidPtr(VoidUnion vu, ArgT... args) { + return (*static_cast(vu.void_ptr))(std::forward(args)...); } - void* f_; - RetT (*call_)(void* f, ArgT... args); + template + static RetT CallFunPtr(VoidUnion vu, ArgT... args) { + return (reinterpret_cast::type>(vu.fun_ptr))( + std::forward(args)...); + } + + // A pointer to the callable thing, with type information erased. It's a + // union because we have to use separate types depending on if the callable + // thing is a function pointer or something else. + VoidUnion f_; + + // Pointer to a dispatch function that knows the type of the callable thing + // that's stored in f_, and how to call it. A FunctionView object is empty + // (null) iff call_ is null. + RetT (*call_)(VoidUnion, ArgT...); }; } // namespace rtc diff --git a/webrtc/base/function_view_unittest.cc b/webrtc/base/function_view_unittest.cc index 3ca2df456e..c769fe65b2 100644 --- a/webrtc/base/function_view_unittest.cc +++ b/webrtc/base/function_view_unittest.cc @@ -19,21 +19,28 @@ namespace rtc { namespace { int CallWith33(rtc::FunctionView fv) { - return fv(33); + return fv ? fv(33) : -1; +} + +int Add33(int x) { + return x + 33; } } // namespace -// Test the main use case of FunctionView: implicitly converting a lambda -// function argument. +// Test the main use case of FunctionView: implicitly converting a callable +// argument. TEST(FunctionViewTest, ImplicitConversion) { EXPECT_EQ(38, CallWith33([](int x) { return x + 5; })); + EXPECT_EQ(66, CallWith33(Add33)); + EXPECT_EQ(-1, CallWith33(nullptr)); } TEST(FunctionViewTest, IntIntLambdaWithoutState) { auto f = [](int x) { return x + 1; }; EXPECT_EQ(18, f(17)); rtc::FunctionView fv(f); + EXPECT_TRUE(fv); EXPECT_EQ(18, fv(17)); } @@ -41,12 +48,34 @@ TEST(FunctionViewTest, IntVoidLambdaWithState) { int x = 13; auto f = [x]() mutable { return ++x; }; rtc::FunctionView fv(f); + EXPECT_TRUE(fv); EXPECT_EQ(14, f()); EXPECT_EQ(15, fv()); EXPECT_EQ(16, f()); EXPECT_EQ(17, fv()); } +TEST(FunctionViewTest, IntIntFunction) { + rtc::FunctionView fv(Add33); + EXPECT_TRUE(fv); + EXPECT_EQ(50, fv(17)); +} + +TEST(FunctionViewTest, IntIntFunctionPointer) { + rtc::FunctionView fv(&Add33); + EXPECT_TRUE(fv); + EXPECT_EQ(50, fv(17)); +} + +TEST(FunctionViewTest, Null) { + // These two call constructors that statically construct null FunctionViews. + EXPECT_FALSE(rtc::FunctionView()); + EXPECT_FALSE(rtc::FunctionView(nullptr)); + + // This calls the constructor for function pointers. + EXPECT_FALSE(rtc::FunctionView(reinterpret_cast(0))); +} + // Ensure that FunctionView handles move-only arguments and return values. TEST(FunctionViewTest, UniquePtrPassthrough) { auto f = [](std::unique_ptr x) { return x; }; @@ -111,8 +140,8 @@ TEST(FunctionViewTest, Swap) { } // Ensure that when you copy-construct a FunctionView, the new object points to -// the same function as the old one, as opposed to the new object pointing to -// the old one. +// the same function as the old one (as opposed to the new object pointing to +// the old one). TEST(FunctionViewTest, CopyConstructorChaining) { auto f17 = [] { return 17; }; rtc::FunctionView fv1(f17); @@ -126,14 +155,14 @@ TEST(FunctionViewTest, CopyConstructorChaining) { } // Ensure that when you assign one FunctionView to another, we actually make a -// copy as opposed to making the second FunctionView point to the first one. +// copy (as opposed to making the second FunctionView point to the first one). TEST(FunctionViewTest, CopyAssignmentChaining) { auto f17 = [] { return 17; }; rtc::FunctionView fv1(f17); - auto f3 = [] { return 3; }; - rtc::FunctionView fv2(f3); + rtc::FunctionView fv2; + EXPECT_TRUE(fv1); EXPECT_EQ(17, fv1()); - EXPECT_EQ(3, fv2()); + EXPECT_FALSE(fv2); fv2 = fv1; EXPECT_EQ(17, fv1()); EXPECT_EQ(17, fv2());