From 08a67734a964c129f3675c159b40af8a9b26fc12 Mon Sep 17 00:00:00 2001 From: Sven Over Date: Tue, 22 Mar 2016 06:10:19 -0700 Subject: [PATCH] Introducing folly::Function Summary:std::function is copy-constructible and requires that the callable that it wraps is copy-constructible as well, which is a constraint that is often inconvenient. In most cases when using a std::function we don't make use of its copy-constructibility. This diff introduces a templated type called folly::Function that is very similar to a std::function, except it is not copy-constructible and doesn't require the callable to be either. Like std::function, Function is a templated type with template parameters for return type and argument types of the callable, but not the callable's specific type. It can store function pointers, static member function pointers, std::function objects, std::reference_wrapper objects and arbitrary callable types (functors) with matching return and argument types. Much like std::function, Function will store small callables in-place, so that no additional memory allocation is necessary. For larger callables, Function will allocate memory on the heap. Function has two more template parameters: firstly, an enum parameter of type folly::FunctionMoveCtor, which defaults to NO_THROW and determines whether no-except-movability should be guaranteed. If set to NO_THROW, callables that are not no-except-movable will be stored on the heap, even if they would fit into the storage area within Function. Secondly, a size_t parameter (EmbedFunctorSize), which determines the size of the internal callable storage. If you know the specific type of the callable you want to store, you can set EmbedFunctorSize to sizeof(CallableType). The original motivation of this diff was to allow to pass lambdas to folly::Future::then that are not copy-constructible because they capture non-copyable types, such as a promise or a unique pointer. Another diff will shortly follow that changes folly::Future to use folly::Function instead of std::function for callbacks, thus allowing to pass non-copyable lambdas to folly::Future::then. Reviewed By: fugalh Differential Revision: D2844587 fb-gh-sync-id: 3bee2af75ef8a4eca4409aaa679cc13762cae0d0 shipit-source-id: 3bee2af75ef8a4eca4409aaa679cc13762cae0d0 --- folly/Function-inl.h | 491 ++++++++ folly/Function-pre.h | 298 +++++ folly/Function.h | 538 ++++++++ folly/Makefile.am | 3 + folly/test/FunctionTest.cpp | 1083 +++++++++++++++++ folly/test/Makefile.am | 4 + .../function_benchmark/benchmark_impl.cpp | 7 + .../test/function_benchmark/benchmark_impl.h | 4 + folly/test/function_benchmark/main.cpp | 22 + 9 files changed, 2450 insertions(+) create mode 100644 folly/Function-inl.h create mode 100644 folly/Function-pre.h create mode 100644 folly/Function.h create mode 100644 folly/test/FunctionTest.cpp diff --git a/folly/Function-inl.h b/folly/Function-inl.h new file mode 100644 index 00000000..05f79083 --- /dev/null +++ b/folly/Function-inl.h @@ -0,0 +1,491 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace folly { +namespace detail { +namespace function { + +// --------------------------------------------------------------------------- +// HELPER TYPES + +enum class AllocationStatus { EMPTY, EMBEDDED, ALLOCATED }; + +// --------------------------------------------------------------------------- +// EXECUTOR CLASSES + +// function::ExecutorIf +template +class Executors::ExecutorIf + : public Executors::Traits::ExecutorMixin { + protected: + ExecutorIf(InvokeFunctionPtr invoke_ptr) + : Traits::ExecutorMixin(invoke_ptr){}; + + public: + // executors are neither copyable nor movable + ExecutorIf(ExecutorIf const&) = delete; + ExecutorIf& operator=(ExecutorIf const&) = delete; + ExecutorIf(ExecutorIf&&) = delete; + ExecutorIf& operator=(ExecutorIf&&) = delete; + + virtual ~ExecutorIf() {} + virtual detail::function::AllocationStatus getAllocationStatus() const + noexcept = 0; + virtual std::pair target() const noexcept = 0; + + // moveTo: move this executor to a different place + // preconditions: + // * *this is a valid executor object (derived from ExecutorIf) + // * the memory at [dest; dest+size) may be overwritten + // postconditions: + // * *this is an EmptyExecutor + // * *dest is a valid executor object (derived from ExecutorIf) + // You can move this executor into one for a non-const or const + // function. + virtual void moveTo( + typename NonConstFunctionExecutors::ExecutorIf* dest, + size_t size, + FunctionMoveCtor throws) = 0; + virtual void moveTo( + typename ConstFunctionExecutors::ExecutorIf* dest, + size_t size, + FunctionMoveCtor throws) = 0; +}; + +// function::EmptyExecutor +template +class Executors::EmptyExecutor final + : public Executors::ExecutorIf { + public: + EmptyExecutor() noexcept : ExecutorIf(&EmptyExecutor::invokeEmpty) {} + ~EmptyExecutor() {} + detail::function::AllocationStatus getAllocationStatus() const noexcept { + return detail::function::AllocationStatus::EMPTY; + } + + std::pair target() const noexcept { + return {typeid(void), nullptr}; + } + + template + void moveToImpl(typename DestinationExecutors::ExecutorIf* dest) noexcept { + new (dest) typename DestinationExecutors::EmptyExecutor(); + } + + void moveTo( + typename NonConstFunctionExecutors::ExecutorIf* dest, + size_t /*size*/, + FunctionMoveCtor /*throws*/) noexcept { + moveToImpl>(dest); + } + void moveTo( + typename ConstFunctionExecutors::ExecutorIf* dest, + size_t /*size*/, + FunctionMoveCtor /*throws*/) noexcept { + moveToImpl>(dest); + } +}; + +// function::FunctorPtrExecutor +template +template +class Executors::FunctorPtrExecutor final + : public Executors::ExecutorIf { + public: + FunctorPtrExecutor(F&& f) + : ExecutorIf( + &FunctorPtrExecutor::template invokeFunctor), + functorPtr_(new F(std::move(f))) {} + FunctorPtrExecutor(F const& f) + : ExecutorIf( + &FunctorPtrExecutor::template invokeFunctor), + functorPtr_(new F(f)) {} + FunctorPtrExecutor(std::unique_ptr f) + : ExecutorIf( + &FunctorPtrExecutor::template invokeFunctor), + functorPtr_(std::move(f)) {} + ~FunctorPtrExecutor() {} + detail::function::AllocationStatus getAllocationStatus() const noexcept { + return detail::function::AllocationStatus::ALLOCATED; + } + + static auto getFunctor( + typename Traits::template QualifiedPointer self) -> + typename SelectFunctionTag::template QualifiedPointer { + return FunctorPtrExecutor::selectFunctionHelper( + static_cast< + typename Traits::template QualifiedPointer>( + self) + ->functorPtr_.get(), + SelectFunctionTag()); + } + + std::pair target() const noexcept { + return {typeid(F), const_cast(functorPtr_.get())}; + } + + template + void moveToImpl(typename DestinationExecutors::ExecutorIf* dest) noexcept { + new (dest) typename DestinationExecutors:: + template FunctorPtrExecutor( + std::move(functorPtr_)); + this->~FunctorPtrExecutor(); + new (this) EmptyExecutor(); + } + + void moveTo( + typename NonConstFunctionExecutors::ExecutorIf* dest, + size_t /*size*/, + FunctionMoveCtor /*throws*/) noexcept { + moveToImpl>(dest); + } + void moveTo( + typename ConstFunctionExecutors::ExecutorIf* dest, + size_t /*size*/, + FunctionMoveCtor /*throws*/) noexcept { + moveToImpl>(dest); + } + + private: + std::unique_ptr functorPtr_; +}; + +// function::FunctorExecutor +template +template +class Executors::FunctorExecutor final + : public Executors::ExecutorIf { + public: + static constexpr bool kFunctorIsNTM = + std::is_nothrow_move_constructible::value; + + FunctorExecutor(F&& f) + : ExecutorIf(&FunctorExecutor::template invokeFunctor), + functor_(std::move(f)) {} + FunctorExecutor(F const& f) + : ExecutorIf(&FunctorExecutor::template invokeFunctor), + functor_(f) {} + ~FunctorExecutor() {} + detail::function::AllocationStatus getAllocationStatus() const noexcept { + return detail::function::AllocationStatus::EMBEDDED; + } + + static auto getFunctor( + typename Traits::template QualifiedPointer self) -> + typename SelectFunctionTag::template QualifiedPointer { + return FunctorExecutor::selectFunctionHelper( + &static_cast< + typename Traits::template QualifiedPointer>(self) + ->functor_, + SelectFunctionTag()); + } + + std::pair target() const noexcept { + return {typeid(F), const_cast(&functor_)}; + } + + template + void moveToImpl( + typename DestinationExecutors::ExecutorIf* dest, + size_t size, + FunctionMoveCtor throws) noexcept(kFunctorIsNTM) { + if ((kFunctorIsNTM || throws == FunctionMoveCtor::MAY_THROW) && + size >= sizeof(*this)) { + // Either functor_ is no-except-movable or no-except-movability is + // not requested *and* functor_ fits into destination + // => functor_ will be moved into a FunctorExecutor at dest + new (dest) typename DestinationExecutors:: + template FunctorExecutor(std::move(functor_)); + } else { + // Either functor_ may throw when moved and no-except-movabilty is + // requested *or* the functor is too big to fit into destination + // => functor_ will be moved into a FunctorPtrExecutor. This will + // move functor_ onto the heap. The FunctorPtrExecutor object + // contains a unique_ptr. + new (dest) typename DestinationExecutors:: + template FunctorPtrExecutor( + std::move(functor_)); + } + this->~FunctorExecutor(); + new (this) EmptyExecutor(); + } + void moveTo( + typename NonConstFunctionExecutors::ExecutorIf* dest, + size_t size, + FunctionMoveCtor throws) noexcept(kFunctorIsNTM) { + moveToImpl>( + dest, size, throws); + } + void moveTo( + typename ConstFunctionExecutors::ExecutorIf* dest, + size_t size, + FunctionMoveCtor throws) noexcept(kFunctorIsNTM) { + moveToImpl>( + dest, size, throws); + } + + private: + F functor_; +}; +} // namespace function +} // namespace detail + +// --------------------------------------------------------------------------- +// MOVE CONSTRUCTORS & MOVE ASSIGNMENT OPERATORS + +template +Function::Function( + Function&& other) noexcept(hasNoExceptMoveCtor()) { + other.access()->moveTo(access(), kStorageSize, NTM); +} + +template +Function& +Function::operator=( + Function&& rhs) noexcept(hasNoExceptMoveCtor()) { + destroyExecutor(); + SCOPE_FAIL { + initializeEmptyExecutor(); + }; + rhs.access()->moveTo(access(), kStorageSize, NTM); + return *this; +} + +template +template < + typename OtherFunctionType, + FunctionMoveCtor OtherNTM, + size_t OtherEmbedFunctorSize> +Function:: + Function( + Function&& other) 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 " + "Function; " + "use folly::constCastFunction!"); + + other.template access()->moveTo( + access(), kStorageSize, NTM); +} + +template +template < + typename OtherFunctionType, + FunctionMoveCtor OtherNTM, + size_t OtherEmbedFunctorSize> +Function& +Function::operator=( + Function&& + rhs) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW) { + 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 " + "Function; " + "use folly::constCastFunction!"); + + destroyExecutor(); + SCOPE_FAIL { + initializeEmptyExecutor(); + }; + rhs.template access()->moveTo( + access(), kStorageSize, NTM); + return *this; +} + +// --------------------------------------------------------------------------- +// PUBLIC METHODS + +template +template +inline void Function:: + swap(Function& o) noexcept( + hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW) { + Function tmp(std::move(*this)); + *this = std::move(o); + o = std::move(tmp); +} + +template +Function::operator bool() const noexcept { + return access()->getAllocationStatus() != + detail::function::AllocationStatus::EMPTY; +} + +template +inline bool Function::hasAllocatedMemory() + const noexcept { + return access()->getAllocationStatus() == + detail::function::AllocationStatus::ALLOCATED; +} + +template +inline std::type_info const& +Function::target_type() const noexcept { + return access()->target().first; +} + +template +template +T* Function::target() noexcept { + auto type_target_pair = access()->target(); + if (type_target_pair.first == typeid(T)) { + return static_cast(type_target_pair.second); + } + return nullptr; +} + +template +template +T const* Function::target() const + noexcept { + auto type_target_pair = access()->target(); + if (type_target_pair.first == typeid(T)) { + return static_cast(type_target_pair.second); + } + return nullptr; +} + +template + Function< + typename detail::function::FunctionTypeTraits< + FunctionType>::ConstFunctionType, + NTM, + EmbedFunctorSize> + Function::castToConstFunction() && + noexcept(hasNoExceptMoveCtor()) { + using ReturnType = + Function; + + ReturnType result; + result.destroyExecutor(); + SCOPE_FAIL { + result.initializeEmptyExecutor(); + }; + access()->moveTo( + result.template access(), + kStorageSize, + NTM); + return result; +} + +// --------------------------------------------------------------------------- +// PRIVATE METHODS + +template +template +T* Function::access() { + static_assert( + std::is_base_of::value, + "Function::access: ExecutorIf must be base class of T " + "(this is a bug in the Function implementation)"); + static_assert( + sizeof(T) <= kStorageSize, + "Requested access to object not fitting into ExecutorStore " + "(this is a bug in the Function implementation)"); + + return reinterpret_cast(&data_); +} + +template +template +T const* Function::access() const { + static_assert( + std::is_base_of::value, + "Function::access: ExecutorIf must be base class of T " + "(this is a bug in the Function implementation)"); + static_assert( + sizeof(T) <= kStorageSize, + "Requested access to object not fitting into ExecutorStore " + "(this is a bug in the Function implementation)"); + + return reinterpret_cast(&data_); +} + +template +void Function:: + initializeEmptyExecutor() noexcept { + new (access()) EmptyExecutor; +} + +template +template +void Function:: + createExecutor(F&& f) noexcept( + noexcept(typename std::decay::type(std::forward(f)))) { + using ValueType = typename std::decay::type; + static constexpr bool kFunctorIsNTM = + std::is_nothrow_move_constructible::value; + using ExecutorType = typename std::conditional< + (sizeof(FunctorExecutor< + ValueType, + typename Traits::DefaultSelectFunctionTag>) > kStorageSize || + (hasNoExceptMoveCtor() && !kFunctorIsNTM)), + FunctorPtrExecutor, + FunctorExecutor>:: + type; + new (access()) ExecutorType(std::forward(f)); +} + +template +void Function::destroyExecutor() noexcept { + access()->~ExecutorIf(); +} + +template +struct Function::MinStorageSize { + using NotEmbeddedFunctor = + FunctorPtrExecutor; + + using EmbeddedFunctor = FunctorExecutor< + typename std::aligned_storage< + constexpr_max(EmbedFunctorSize, sizeof(void (*)(void)))>::type, + detail::function::SelectConstFunctionTag>; + + static constexpr size_t value = + constexpr_max(sizeof(NotEmbeddedFunctor), sizeof(EmbeddedFunctor)); + + static_assert( + sizeof(EmptyExecutor) <= value, + "Internal error in Function: EmptyExecutor does not fit " + "in storage"); +}; + +} // namespace folly diff --git a/folly/Function-pre.h b/folly/Function-pre.h new file mode 100644 index 00000000..1196f571 --- /dev/null +++ b/folly/Function-pre.h @@ -0,0 +1,298 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// included by Function.h, do not include directly. + +#include + +namespace folly { + +namespace detail { +namespace function { + +struct SelectConstFunctionTag { + template + using QualifiedPointer = T const*; +}; +struct SelectNonConstFunctionTag { + template + using QualifiedPointer = T*; +}; + +// Helper class to extract properties from a function type +template +struct FunctionTypeTraits; + +// FunctionTypeTraits default implementation - this only exists to suppress +// very long compiler errors when Function is tried to be instantiated +// with an unsuitable type +template +struct FunctionTypeTraits { + using SuitableForFunction = std::false_type; + + // The following definitions are here only to suppress long and misleading + // compiler errors. + using ResultType = void; + using ArgsTuple = int; + using ArgsRefTuple = int; + using NonConstFunctionType = void; + using ConstFunctionType = int; + + template + class InvokeOperator {}; + class ExecutorMixin {}; +}; + +// FunctionTypeTraits for non-const function types +template +struct FunctionTypeTraits { + using SuitableForFunction = std::true_type; + using ResultType = R; + using ArgsTuple = std::tuple; + using ArgsRefTuple = std::tuple; + using NonConstFunctionType = R(Args...); + using ConstFunctionType = R(Args...) const; + using IsConst = std::false_type; + using DefaultSelectFunctionTag = SelectNonConstFunctionTag; + template + using IsCallable = + std::is_convertible::type, R>; + template + using QualifiedPointer = T*; + template + using InvokeFunctionPtr = R (*)(Obj*, Args&&...); + + // Function inherits from InvokeOperator. This is + // where Function's operator() is defined. + template + class InvokeOperator { + public: + /** + * Invokes the stored callable via the invokePtr stored in the Executor. + * + * Throws std::bad_function_call if @c *this is empty. + */ + ResultType operator()(Args... args) { + auto executor = + static_cast(this) + ->template access(); + return executor->invokePtr(executor, std::forward(args)...); + } + }; + + class ExecutorMixin; +}; + +// FunctionTypeTraits for const function types +template +struct FunctionTypeTraits { + using SuitableForFunction = std::true_type; + using ResultType = R; + using ArgsTuple = std::tuple; + using ArgsRefTuple = std::tuple; + using NonConstFunctionType = R(Args...); + using ConstFunctionType = R(Args...) const; + using IsConst = std::true_type; + using DefaultSelectFunctionTag = SelectConstFunctionTag; + template + using IsCallable = + std::is_convertible::type, R>; + template + using QualifiedPointer = T const*; + template + using InvokeFunctionPtr = R (*)(Obj const*, Args&&...); + + // Function inherits from InvokeOperator. This is + // where Function's operator() is defined. + template + class InvokeOperator { + public: + /** + * Invokes the stored callable via the invokePtr stored in the Executor. + * + * Throws std::bad_function_call if @c *this is empty. + */ + ResultType operator()(Args... args) const { + auto executor = + static_cast(this) + ->template access(); + return executor->invokePtr(executor, std::forward(args)...); + } + }; + + 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 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. +// Function objects can constructed or assigned from types for which +// IsCallableHelper is true_type. +template +struct IsCallableHelper { + using Traits = FunctionTypeTraits; + + template + static std::integral_constant::value> + test(int); + template + static std::false_type test(...); +}; + +template +struct IsCallable : public std::integral_constant< + bool, + (!IsFunction::type>::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 +template +struct MaybeUnaryOrBinaryFunctionImpl { + using result_type = R; +}; + +template +struct MaybeUnaryOrBinaryFunctionImpl> + : public std::unary_function {}; + +template +struct MaybeUnaryOrBinaryFunctionImpl> + : public std::binary_function {}; + +template +using MaybeUnaryOrBinaryFunction = MaybeUnaryOrBinaryFunctionImpl< + typename FunctionTypeTraits::ResultType, + typename FunctionTypeTraits::ArgsTuple>; + +// Invoke helper +template +inline auto invoke(F&& f, Args&&... args) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +template +inline auto invoke(M(C::*d), Args&&... args) + -> decltype(std::mem_fn(d)(std::forward(args)...)) { + return std::mem_fn(d)(std::forward(args)...); +} + +// Executors helper class +template +struct Executors { + class ExecutorIf; + class EmptyExecutor; + template + class FunctorPtrExecutor; + template + class FunctorExecutor; + + using Traits = FunctionTypeTraits; + using NonConstFunctionExecutors = + Executors; + using ConstFunctionExecutors = Executors; + using InvokeFunctionPtr = typename Traits::template InvokeFunctionPtr< + Executors::ExecutorIf>; +}; + +template +class FunctionTypeTraits::ExecutorMixin { + public: + using ExecutorIf = typename Executors::ExecutorIf; + using InvokeFunctionPtr = typename Executors::InvokeFunctionPtr; + + ExecutorMixin(InvokeFunctionPtr invoke_ptr) : invokePtr(invoke_ptr) {} + virtual ~ExecutorMixin() {} + + template + static F* selectFunctionHelper(F* f, SelectNonConstFunctionTag) { + return f; + } + + template + static F const* selectFunctionHelper(F* f, SelectConstFunctionTag) { + return f; + } + + static R invokeEmpty(ExecutorIf*, Args&&...) { + throw std::bad_function_call(); + } + + template + static R invokeFunctor(ExecutorIf* executor, Args&&... args) { + return folly::detail::function::invoke( + *Ex::getFunctor(executor), std::forward(args)...); + } + + // invokePtr is of type + // ReturnType (*)(ExecutorIf*, Args&&...) + // and it will be set to the address of one of the static functions above + // (invokeEmpty or invokeFunctor), which will invoke the stored callable + InvokeFunctionPtr const invokePtr; +}; + +template +class FunctionTypeTraits::ExecutorMixin { + public: + using ExecutorIf = typename Executors::ExecutorIf; + using InvokeFunctionPtr = + typename Executors::InvokeFunctionPtr; + + ExecutorMixin(InvokeFunctionPtr invoke_ptr) : invokePtr(invoke_ptr) {} + virtual ~ExecutorMixin() {} + + template + static F* selectFunctionHelper(F const* f, SelectNonConstFunctionTag) { + return const_cast(f); + } + + template + static F const* selectFunctionHelper(F const* f, SelectConstFunctionTag) { + return f; + } + + static R invokeEmpty(ExecutorIf const*, Args&&...) { + throw std::bad_function_call(); + } + + template + static R invokeFunctor(ExecutorIf const* executor, Args&&... args) { + return folly::detail::function::invoke( + *Ex::getFunctor(executor), std::forward(args)...); + } + + // invokePtr is of type + // ReturnType (*)(ExecutorIf*, Args&&...) + // and it will be set to the address of one of the static functions above + // (invokeEmpty or invokeFunctor), which will invoke the stored callable + InvokeFunctionPtr const invokePtr; +}; + +} // namespace function +} // namespace detail +} // namespace folly diff --git a/folly/Function.h b/folly/Function.h new file mode 100644 index 00000000..d33ec9eb --- /dev/null +++ b/folly/Function.h @@ -0,0 +1,538 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @class Function + * + * @brief A polymorphic function wrapper that is not copyable and does not + * require the wrapped function to be copy constructible. + * + * `folly::Function` is a polymorphic function wrapper, similar to + * `std::function`. The template parameters of the `folly::Function` define + * the parameter signature of the wrapped callable, but not the specific + * type of the embedded callable. E.g. a `folly::Function` + * can wrap callables that return an `int` when passed an `int`. This can be a + * function pointer or any class object implementing one or both of + * int operator(int); + * int operator(int) const; + * If both are defined, the non-const one takes precedence. + * + * Unlike `std::function`, a `folly::Function` can wrap objects that are not + * copy constructible. As a consequence of this, `folly::Function` itself + * is not copyable, either. + * + * Another difference is that, unlike `std::function`, `folly::Function` treats + * const-ness of methods correctly. While a `std::function` allows to wrap + * an object that only implements a non-const `operator()` and invoke + * a const-reference of the `std::function`, `folly::Function` requires you to + * declare a function type as const in order to be able to execute it on a + * const-reference. + * + * For example: + * class Foo { + * public: + * void operator()() { + * // mutates the Foo object + * } + * }; + * + * class Bar { + * std::function foo_; // wraps a Foo object + * public: + * void mutateFoo() const + * { + * foo_(); + * } + * }; + * Even though `mutateFoo` is a const-method, so it can only reference `foo_` + * as const, it is able to call the non-const `operator()` of the Foo + * object that is embedded in the foo_ function. + * + * `folly::Function` will not allow you to do that. You will have to decide + * whether you need to invoke your wrapped callable from a const reference + * (like in the example above), in which case it will only wrap a + * `operator() const`. If your functor does not implement that, + * compilation will fail. If you do not require to be able to invoke the + * wrapped function in a const context, you can wrap any functor that + * implements either or both of const and non-const `operator()`. + * + * The first (and usually only specified) template parameter of + * `folly::Function`, the `FunctionType`, can be const-qualified. Be aware + * that the const is part of the function signature. It does not mean that + * the function type is a const type. + * + * using FunctionType = R(Args...); + * using ConstFunctionType = R(Args...) const; + * + * In this example, `FunctionType` and `ConstFunctionType` are different + * types. `ConstFunctionType` is not the same as `const FunctionType`. + * As a matter of fact, trying to use the latter should emit a compiler + * warning or error, because it has no defined meaning. + * + * // This will not compile: + * folly::Function func = Foo(); + * // because Foo does not have a member function of the form: + * // void operator()() const; + * + * // This will compile just fine: + * folly::Function func = Foo(); + * // and it will wrap the existing member function: + * // void operator()(); + * + * When should a const function type be used? As a matter of fact, you will + * probably not need to use const function types very often. See the following + * example: + * + * class Bar { + * folly::Function func_; + * folly::Function constFunc_; + * + * void someMethod() { + * // Can call func_. + * func_(); + * // Can call constFunc_. + * constFunc_(); + * } + * + * void someConstMethod() const { + * // Can call constFunc_. + * constFunc_(); + * // However, cannot call func_ because a non-const method cannot + * // be called from a const one. + * } + * }; + * + * As you can see, whether the `folly::Function`'s function type should + * be declared const or not is identical to whether a corresponding method + * would be declared const or not. + * + * You only require a `folly::Function` to hold a const function type, if you + * intend to invoke it from within a const context. This is to ensure that + * you cannot mutate its inner state when calling in a const context. + * + * This is how the const/non-const choice relates to lambda functions: + * + * // Non-mutable lambdas: can be stored in a non-const... + * folly::Function print_number = + * [] (int number) { std::cout << number << std::endl; }; + * + * // ...as well as in a const folly::Function + * folly::Function print_number_const = + * [] (int number) { std::cout << number << std::endl; }; + * + * // Mutable lambda: can only be stored in a non-const folly::Function: + * int number = 0; + * folly::Function print_number = + * [number] () mutable { std::cout << ++number << std::endl; }; + * // Trying to store the above mutable lambda in a + * // `folly::Function` would lead to a compiler error: + * // error: no viable conversion from '(lambda at ...)' to + * // 'folly::Function' + * + * Casting between const and non-const `folly::Function`s: + * conversion from const to non-const signatures happens implicitly. Any + * function that takes a `folly::Function` can be passed + * a `folly::Function` without explicit conversion. + * This is safe, because casting from const to non-const only entails giving + * up the ability to invoke the function from a const context. + * Casting from a non-const to a const signature is potentially dangerous, + * as it means that a function that may change its inner state when invoked + * is made possible to call from a const context. Therefore this cast does + * not happen implicitly. The function `folly::constCastfolly::Function` can + * be used to perform the cast. + * + * // Mutable lambda: can only be stored in a non-const folly::Function: + * int number = 0; + * folly::Function print_number = + * [number] () mutable { std::cout << ++number << std::endl; }; + * + * // const-cast to a const folly::Function: + * folly::Function print_number_const = + * constCastfolly::Function(std::move(print_number)); + * + * When to use const function types? + * Generally, only when you need them. When you use a `folly::Function` as a + * member of a struct or class, only use a const function signature when you + * need to invoke the function from const context. + * When passing a `folly::Function` to a function, the function should accept + * a non-const `folly::Function` whenever possible, i.e. when it does not + * need to pass on or store a const `folly::Function`. This is the least + * possible constraint: you can always pass a const `folly::Function` when + * the function accepts a non-const one. + * + * How does the const behaviour compare to `std::function`? + * `std::function` can wrap object with non-const invokation behaviour but + * exposes them as const. The equivalent behaviour can be achieved with + * `folly::Function` like so: + * + * std::function stdfunc = someCallable; + * + * folly::Function uniqfunc = constCastfolly::Function( + * folly::Function(someCallable) + * ); + * + * You need to wrap the callable first in a non-const `folly::Function` to + * select a non-const invoke operator (or the const one if no non-const one is + * present), and then move it into a const `folly::Function` using + * `constCastfolly::Function`. + * The name of `constCastfolly::Function` should warn you that something + * potentially dangerous is happening. As a matter of fact, using + * `std::function` always involves this potentially dangerous aspect, which + * is why it is not considered fully const-safe or even const-correct. + * However, in most of the cases you will not need the dangerous aspect at all. + * Either you do not require invokation of the function from a const context, + * in which case you do not need to use `constCastfolly::Function` and just + * use the inner `folly::Function` in the example above, i.e. just use a + * non-const `folly::Function`. Or, you may need invokation from const, but + * the callable you are wrapping does not mutate its state (e.g. it is a class + * object and implements `operator() const`, or it is a normal, + * non-mutable lambda), in which case you can wrap the callable in a const + * `folly::Function` directly, without using `constCastfolly::Function`. + * Only if you require invokation from a const context of a callable that + * may mutate itself when invoked you have to go through the above procedure. + * However, in that case what you do is potentially dangerous and requires + * the equivalent of a `const_cast`, hence you need to call + * `constCastfolly::Function`. + * + * `folly::Function` also has two additional template paremeters: + * * `NTM`: if set to `folly::FunctionMoveCtor::NO_THROW`, the + * `folly::Function` object is guaranteed to be nothrow move constructible. + * The downside is that any function object that itself is + * not nothrow move constructible cannot be stored in-place in the + * `folly::Function` object and will be stored on the heap instead. + * * `EmbedFunctorSize`: a number of bytes that will be reserved in the + * `folly::Function` object to store callable objects in-place. If you + * wrap a callable object bigger than this in a `folly::Function` object, + * it will be stored on the heap and the `folly::Function` object will keep + * a `std::unique_ptr` to it. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace folly { + +enum class FunctionMoveCtor { NO_THROW, MAY_THROW }; + +template < + typename FunctionType, + FunctionMoveCtor NTM = FunctionMoveCtor::NO_THROW, + size_t EmbedFunctorSize = (NTM == FunctionMoveCtor::NO_THROW) + ? sizeof(void (*)(void)) + : sizeof(std::function)> +class Function; + +} // folly + +// boring predeclarations and details +#include "Function-pre.h" + +namespace folly { + +template +class Function final + : public detail::function::FunctionTypeTraits:: + template InvokeOperator< + Function>, + public detail::function::MaybeUnaryOrBinaryFunction { + private: + using Traits = detail::function::FunctionTypeTraits; + static_assert( + Traits::SuitableForFunction::value, + "Function: FunctionType must be of the " + "form 'R(Args...)' or 'R(Args...) const'"); + + using ThisType = Function; + using InvokeOperator = typename Traits::template InvokeOperator; + + static constexpr bool hasNoExceptMoveCtor() noexcept { + return NTM == FunctionMoveCtor::NO_THROW; + }; + + public: + // not copyable + Function(Function const&) = delete; + Function& operator=(Function const&) = delete; + + /** + * Default constructor. Constructs an empty Function. + */ + Function() noexcept { + initializeEmptyExecutor(); + } + + ~Function() { + destroyExecutor(); + + static_assert( + kStorageSize == sizeof(*this), + "There is something wrong with the size of Function"); + } + + // construct/assign from Function + /** + * Move constructor + */ + Function(Function&& other) noexcept(hasNoExceptMoveCtor()); + /** + * Move assignment operator + */ + Function& operator=(Function&& rhs) noexcept(hasNoExceptMoveCtor()); + + /** + * Constructs a `Function` by moving from one with different template + * parameters with regards to const-ness, no-except-movability and internal + * storage size. + */ + template < + typename OtherFunctionType, + FunctionMoveCtor OtherNTM, + size_t OtherEmbedFunctorSize> + Function(Function&& other) + noexcept( + OtherNTM == FunctionMoveCtor::NO_THROW && + EmbedFunctorSize >= OtherEmbedFunctorSize); + + /** + * Moves a `Function` with different template parameters with regards + * to const-ness, no-except-movability and internal storage size into this + * one. + */ + template < + typename RhsFunctionType, + FunctionMoveCtor RhsNTM, + size_t RhsEmbedFunctorSize> + Function& operator=(Function&& + rhs) noexcept(RhsNTM == FunctionMoveCtor::NO_THROW); + + /** + * Constructs an empty `Function`. + */ + /* implicit */ Function(std::nullptr_t) noexcept : Function() {} + + /** + * Clears this `Function`. + */ + Function& operator=(std::nullptr_t) noexcept { + destroyExecutor(); + initializeEmptyExecutor(); + return *this; + } + + /** + * Constructs a new `Function` from any callable object. This + * handles function pointers, pointers to static member functions, + * `std::reference_wrapper` objects, `std::function` objects, and arbitrary + * objects that implement `operator()` if the parameter signature + * matches (i.e. it returns R when called with Args...). + * For a `Function` with a const function type, the object must be + * callable from a const-reference, i.e. implement `operator() const`. + * For a `Function` with a non-const function type, the object will + * be called from a non-const reference, which means that it will execute + * a non-const `operator()` if it is defined, and falls back to + * `operator() const` otherwise + */ + template + /* implicit */ Function( + F&& f, + typename std::enable_if< + detail::function::IsCallable::value>::type* = + 0) noexcept(noexcept(typename std::decay:: + type(std::forward(f)))) { + createExecutor(std::forward(f)); + } + + /** + * Assigns a callable object to this `Function`. + */ + template + typename std::enable_if< + detail::function::IsCallable::value, + Function&>::type + operator=(F&& f) noexcept( + noexcept(typename std::decay::type(std::forward(f)))) { + destroyExecutor(); + SCOPE_FAIL { + initializeEmptyExecutor(); + }; + createExecutor(std::forward(f)); + return *this; + } + + /** + * Exchanges the callable objects of `*this` and `other`. `other` can be + * a Function with different settings with regard to + * no-except-movability and internal storage size, but must match + * `*this` with regards to return type and argument types. + */ + template + void + swap(Function& o) noexcept( + hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW); + + /** + * Returns `true` if this `Function` contains a callable, i.e. is + * non-empty. + */ + explicit operator bool() const noexcept; + + /** + * Returns `true` if this `Function` stores the callable on the + * heap. If `false` is returned, there has been no additional memory + * allocation and the callable is stored inside the `Function` + * object itself. + */ + bool hasAllocatedMemory() const noexcept; + + /** + * Returns the `type_info` (as returned by `typeid`) of the callable stored + * in this `Function`. Returns `typeid(void)` if empty. + */ + std::type_info const& target_type() const noexcept; + + /** + * Returns a pointer to the stored callable if its type matches `T`, and + * `nullptr` otherwise. + */ + template + T* target() noexcept; + + /** + * Returns a const-pointer to the stored callable if its type matches `T`, + * and `nullptr` otherwise. + */ + template + const T* target() const noexcept; + + /** + * Move out this `Function` into one with a const function type. + * + * This is a potentially dangerous operation, equivalent to a `const_cast`. + * This converts a `Function` with a non-const function type, i.e. + * one that can only be called when in the form of a non-const reference, + * into one that can be called in a const context. Use at your own risk! + */ + Function + castToConstFunction() && noexcept(hasNoExceptMoveCtor()); + + using SignatureType = FunctionType; + using ResultType = typename Traits::ResultType; + using ArgsTuple = typename Traits::ArgsTuple; + + private: + template + friend class Function; + + friend struct detail::function::FunctionTypeTraits; + + using ExecutorIf = + typename detail::function::Executors::ExecutorIf; + using EmptyExecutor = + typename detail::function::Executors::EmptyExecutor; + template + using FunctorPtrExecutor = typename detail::function::Executors< + FunctionType>::template FunctorPtrExecutor; + template + using FunctorExecutor = typename detail::function::Executors< + FunctionType>::template FunctorExecutor; + + template + T const* access() const; + + template + T* access(); + + void initializeEmptyExecutor() noexcept; + + template + void createExecutor(F&& f) noexcept( + noexcept(typename std::decay::type(std::forward(f)))); + + void destroyExecutor() noexcept; + + struct MinStorageSize; + + typename std::aligned_storage::type data_; + static constexpr size_t kStorageSize = sizeof(data_); +}; + +// operator== +template +inline bool operator==( + Function const& f, + std::nullptr_t) noexcept { + return !f; +} + +template +inline bool operator==( + std::nullptr_t, + Function const& f) noexcept { + return !f; +} + +template +inline bool operator!=( + Function const& f, + std::nullptr_t) noexcept { + return !!f; +} + +template +inline bool operator!=( + std::nullptr_t, + Function const& f) noexcept { + return !!f; +} + +/** + * Cast a `Function` into one with a const function type. + * + * This is a potentially dangerous operation, equivalent to a `const_cast`. + * This converts a `Function` with a non-const function type, i.e. + * one that can only be called when in the form of a non-const reference, + * into one that can be called in a const context. Use at your own risk! + */ +template +Function< + typename detail::function::FunctionTypeTraits< + FunctionType>::ConstFunctionType, + NTM, + EmbedFunctorSize> +constCastFunction(Function&& + from) noexcept(NTM == FunctionMoveCtor::NO_THROW) { + return std::move(from).castToConstFunction(); +} + +} // folly + +namespace std { +template +void swap( + ::folly::Function& lhs, + ::folly::Function& rhs) { + lhs.swap(rhs); +} +} // std + +#include "Function-inl.h" diff --git a/folly/Makefile.am b/folly/Makefile.am index 54b738d7..f9906987 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -329,6 +329,9 @@ nobase_follyinclude_HEADERS = \ TimeoutQueue.h \ Traits.h \ Unicode.h \ + Function.h \ + Function-inl.h \ + Function-pre.h \ Uri.h \ Uri-inl.h \ Varint.h \ diff --git a/folly/test/FunctionTest.cpp b/folly/test/FunctionTest.cpp new file mode 100644 index 00000000..8019b59b --- /dev/null +++ b/folly/test/FunctionTest.cpp @@ -0,0 +1,1083 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +using folly::FunctionMoveCtor; +using folly::Function; + +namespace { +int func_int_int_add_25(int x) { + return x + 25; +} +int func_int_int_add_111(int x) { + return x + 111; +} +int func_int_return_987() { + return 987; +} +float floatMult(float a, float b) { + return a * b; +} + +template +struct Functor { + std::array data = {{0}}; + + // Two operator() with different argument types. + // The InvokeReference tests use both + T const& operator()(size_t index) const { + return data[index]; + } + T operator()(size_t index, T const& value) { + T oldvalue = data[index]; + data[index] = value; + return oldvalue; + } +}; + +// TEST ===================================================================== +// NoExceptMovable + +struct MoveMayThrow { + bool doThrow{false}; + + MoveMayThrow() = default; + MoveMayThrow(MoveMayThrow const&) = default; + MoveMayThrow& operator=(MoveMayThrow const&) = default; + MoveMayThrow(MoveMayThrow&&) noexcept(false) { + if (doThrow) { + throw std::runtime_error("MoveMayThrow(MoveMayThrow&&)"); + } + } + MoveMayThrow& operator=(MoveMayThrow&&) noexcept(false) { + if (doThrow) { + throw std::runtime_error("MoveMayThrow::operator=(MoveMayThrow&&)"); + } + return *this; + } +}; +} + +TEST(Function, NoExceptMovable) { + // callable_noexcept is noexcept-movable + auto callable_noexcept = [](int x) { return x + 1; }; + EXPECT_TRUE( + std::is_nothrow_move_constructible::value); + + // callable_throw may throw when moved + MoveMayThrow mmt; + auto callable_throw = [mmt](int x) { return x + 10; }; + EXPECT_FALSE( + std::is_nothrow_move_constructible::value); + + // callable_noexcept can be stored in the Function object + Function func(callable_noexcept); + EXPECT_EQ(func(42), 43); + EXPECT_FALSE(func.hasAllocatedMemory()); + EXPECT_TRUE(std::is_nothrow_move_constructible::value); + + // callable_throw cannot be stored in the Function object, + // because Function guarantees noexcept-movability, but + // callable_throw may throw when moved + Function func_safe_move(callable_throw); + EXPECT_EQ(func_safe_move(42), 52); + EXPECT_TRUE(func_safe_move.hasAllocatedMemory()); + EXPECT_TRUE( + std::is_nothrow_move_constructible::value); + + // callable_throw can be stored in the Function object when + // the NoExceptMovable template parameter is set to NO + Function func_movethrows( + callable_throw); + EXPECT_EQ(func_movethrows(42), 52); + EXPECT_FALSE(func_movethrows.hasAllocatedMemory()); + EXPECT_FALSE( + std::is_nothrow_move_constructible::value); +} + +// TEST ===================================================================== +// InvokeFunctor & InvokeReference + +template +void invoke_functor_test() { + Functor func; + func(5, 123); + + // Try Functions with differently sized storage areas + // S=0: request storage for functors of size 0. The storage size + // will be actually larger, because there is a lower limit which + // still allows to store at least pointers to functors on the heap. + // S=1: request minimum storage size of 0.5x the sizeof(func) + // S=2: request sizeof(func) + // S=3: request 1.5*sizeof(func) + Function getter = + std::move(func); + + // Function will allocate memory on the heap to store + // the functor object if the internal storage area is smaller than + // sizeof(func). + EXPECT_EQ(getter.hasAllocatedMemory(), S < 2); + + EXPECT_EQ(getter(5), 123); +} +TEST(Function, InvokeFunctor_T0) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_N0) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_T1) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_N1) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_T2) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_N2) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_T3) { + invoke_functor_test(); +} +TEST(Function, InvokeFunctor_N3) { + invoke_functor_test(); +} + +template +void invoke_reference_test() { + Functor func; + func(5, 123); + + // Have Functions for getter and setter, both referencing the + // same funtor + Function getter = std::ref(func); + Function setter = std::ref(func); + + EXPECT_EQ(getter(5), 123); + EXPECT_EQ(setter(5, 456), 123); + EXPECT_EQ(setter(5, 567), 456); + EXPECT_EQ(getter(5), 567); +} +TEST(Function, InvokeReference_T) { + invoke_reference_test(); +} +TEST(Function, InvokeReference_N) { + invoke_reference_test(); +} + +// TEST ===================================================================== +// Emptiness + +template +void emptiness_test() { + Function f; + EXPECT_EQ(f, nullptr); + EXPECT_EQ(nullptr, f); + EXPECT_FALSE(f); + EXPECT_THROW(f(98), std::bad_function_call); + + Function g([](int x) { return x + 1; }); + EXPECT_NE(g, nullptr); + EXPECT_NE(nullptr, g); + EXPECT_TRUE(g); + EXPECT_EQ(g(99), 100); + + Function h(&func_int_int_add_25); + EXPECT_NE(h, nullptr); + EXPECT_NE(nullptr, h); + EXPECT_TRUE(h); + EXPECT_EQ(h(100), 125); + + h = {}; + EXPECT_EQ(h, nullptr); + EXPECT_EQ(nullptr, h); + EXPECT_FALSE(h); + EXPECT_THROW(h(101), std::bad_function_call); +} + +TEST(Function, Emptiness_T) { + emptiness_test(); +} +TEST(Function, Emptiness_N) { + emptiness_test(); +} + +// TEST ===================================================================== +// Types + +TEST(Function, Types) { + EXPECT_TRUE(( + !std::is_base_of, Function>::value)); + EXPECT_TRUE( + (!std::is_base_of, Function>:: + value)); + EXPECT_TRUE((std::is_same::ResultType, int>::value)); + + EXPECT_TRUE(( + std::is_base_of, Function>:: + value)); + EXPECT_TRUE((!std::is_base_of< + std::binary_function, + Function>::value)); + EXPECT_TRUE((std::is_same::ResultType, double>::value)); + EXPECT_TRUE( + (std::is_same::result_type, double>::value)); + EXPECT_TRUE((std::is_same::argument_type, int>::value)); + + EXPECT_TRUE((!std::is_base_of< + std::unary_function, + Function>::value)); + EXPECT_TRUE((std::is_base_of< + std::binary_function, + Function>::value)); + EXPECT_TRUE( + (std::is_same::ResultType, double>::value)); + EXPECT_TRUE( + (std::is_same::result_type, double>::value)); + EXPECT_TRUE( + (std::is_same::first_argument_type, int>:: + value)); + EXPECT_TRUE( + (std::is_same::second_argument_type, char>:: + value)); +} + +// TEST ===================================================================== +// Swap + +template +void swap_test() { + Function mf1(func_int_int_add_25); + Function mf2(func_int_int_add_111); + + EXPECT_EQ(mf1(100), 125); + EXPECT_EQ(mf2(100), 211); + + mf1.swap(mf2); + + EXPECT_EQ(mf2(100), 125); + EXPECT_EQ(mf1(100), 211); + + Function mf3(nullptr); + EXPECT_EQ(mf3, nullptr); + + mf1.swap(mf3); + + EXPECT_EQ(mf3(100), 211); + EXPECT_EQ(mf1, nullptr); + + Function mf4([](int x) { return x + 222; }); + EXPECT_EQ(mf4(100), 322); + + mf4.swap(mf3); + EXPECT_EQ(mf4(100), 211); + EXPECT_EQ(mf3(100), 322); + + mf3.swap(mf1); + EXPECT_EQ(mf3, nullptr); + EXPECT_EQ(mf1(100), 322); +} +TEST(Function, Swap_TT) { + swap_test(); +} +TEST(Function, Swap_TN) { + swap_test(); +} +TEST(Function, Swap_NT) { + swap_test(); +} +TEST(Function, Swap_NN) { + swap_test(); +} + +// TEST ===================================================================== +// Bind + +template +void bind_test() { + Function fnc = floatMult; + auto task = std::bind(std::move(fnc), 2.f, 4.f); + EXPECT_THROW(fnc(0, 0), std::bad_function_call); + EXPECT_EQ(task(), 8); + auto task2(std::move(task)); + EXPECT_THROW(task(), std::bad_function_call); + EXPECT_EQ(task2(), 8); +} +TEST(Function, Bind_T) { + bind_test(); +} +TEST(Function, Bind_N) { + bind_test(); +} + +// TEST ===================================================================== +// NonCopyableLambda + +template +void non_copyable_lambda_test() { + auto unique_ptr_int = folly::make_unique(900); + EXPECT_EQ(*unique_ptr_int, 900); + + char fooData[64] = {0}; + EXPECT_EQ(fooData[0], 0); // suppress gcc warning about fooData not being used + + auto functor = std::bind( + [fooData](std::unique_ptr& up) mutable { return ++*up; }, + std::move(unique_ptr_int)); + + EXPECT_EQ(functor(), 901); + + Function func = std::move(functor); + EXPECT_EQ( + func.hasAllocatedMemory(), + S < 2 || (NEM == FunctionMoveCtor::NO_THROW && + !std::is_nothrow_move_constructible::value)); + + EXPECT_EQ(func(), 902); +} +TEST(Function, NonCopyableLambda_T0) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_N0) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_T1) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_N1) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_T2) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_N2) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_T3) { + non_copyable_lambda_test(); +} +TEST(Function, NonCopyableLambda_N3) { + non_copyable_lambda_test(); +} + +// TEST ===================================================================== +// Downsize + +template +void downsize_test() { + Functor functor; + + // set element 3 + functor(3, 123); + EXPECT_EQ(functor(3), 123); + + // Function with large callable storage area (twice the size of + // the functor) + Function func2x = + std::move(functor); + EXPECT_FALSE(func2x.hasAllocatedMemory()); + EXPECT_EQ(func2x(3, 200), 123); + EXPECT_EQ(func2x(3, 201), 200); + + // Function with sufficient callable storage area (equal to + // size of the functor) + Function func1x = std::move(func2x); + EXPECT_THROW(func2x(0, 0), std::bad_function_call); + EXPECT_FALSE(func2x); + EXPECT_FALSE(func1x.hasAllocatedMemory()); + EXPECT_EQ(func1x(3, 202), 201); + EXPECT_EQ(func1x(3, 203), 202); + + // Function with minimal callable storage area (functor does + // not fit and will be moved to memory on the heap) + Function func0x = std::move(func1x); + EXPECT_THROW(func1x(0, 0), std::bad_function_call); + EXPECT_FALSE(func1x); + EXPECT_TRUE(func0x.hasAllocatedMemory()); + EXPECT_EQ(func0x(3, 204), 203); + EXPECT_EQ(func0x(3, 205), 204); + + // bonus test: move to Function with opposite NoExceptMovable + // setting + Function< + int(size_t, int), + NEM == FunctionMoveCtor::NO_THROW ? FunctionMoveCtor::MAY_THROW + : FunctionMoveCtor::NO_THROW, + 0> + funcnot = std::move(func0x); + EXPECT_THROW(func0x(0, 0), std::bad_function_call); + EXPECT_FALSE(func0x); + EXPECT_TRUE(funcnot.hasAllocatedMemory()); + EXPECT_EQ(funcnot(3, 206), 205); + EXPECT_EQ(funcnot(3, 207), 206); +} +TEST(Function, Downsize_T) { + downsize_test(); +} +TEST(Function, Downsize_N) { + downsize_test(); +} + +// TEST ===================================================================== +// Refcount + +template +void refcount_test() { + Functor functor; + functor(3, 999); + auto shared_int = std::make_shared(100); + + EXPECT_EQ(*shared_int, 100); + EXPECT_EQ(shared_int.use_count(), 1); + + Function func1 = [shared_int]() { return ++*shared_int; }; + EXPECT_EQ(shared_int.use_count(), 2); + EXPECT_EQ(func1(), 101); + EXPECT_EQ(*shared_int, 101); + + // func2: made to not fit functor. + Function func2 = std::move(func1); + EXPECT_THROW(func1(), std::bad_function_call); + EXPECT_EQ(shared_int.use_count(), 2); + EXPECT_FALSE(func1); + EXPECT_EQ(func2(), 102); + EXPECT_EQ(*shared_int, 102); + + func2 = [shared_int]() { return ++*shared_int; }; + EXPECT_EQ(shared_int.use_count(), 2); + EXPECT_EQ(func2(), 103); + EXPECT_EQ(*shared_int, 103); + + // We set func2 to a lambda that captures 'functor', which forces it on + // the heap + func2 = [functor]() { return functor(3); }; + EXPECT_TRUE(func2.hasAllocatedMemory()); + EXPECT_EQ(func2(), 999); + EXPECT_EQ(shared_int.use_count(), 1); + EXPECT_EQ(*shared_int, 103); + + func2 = [shared_int]() { return ++*shared_int; }; + EXPECT_EQ(shared_int.use_count(), 2); + EXPECT_EQ(func2(), 104); + EXPECT_EQ(*shared_int, 104); + + // We set func2 to function pointer, which always fits into the + // Function object and is no-except-movable + func2 = &func_int_return_987; + EXPECT_FALSE(func2.hasAllocatedMemory()); + EXPECT_EQ(func2(), 987); + EXPECT_EQ(shared_int.use_count(), 1); + EXPECT_EQ(*shared_int, 104); +} +TEST(Function, Refcount_T) { + refcount_test(); +} +TEST(Function, Refcount_N) { + refcount_test(); +} + +// TEST ===================================================================== +// Target + +template +void target_test() { + std::function func = [](int x) { return x + 25; }; + EXPECT_EQ(func(100), 125); + + Function ufunc = std::move(func); + EXPECT_THROW(func(0), std::bad_function_call); + EXPECT_EQ(ufunc(200), 225); + + EXPECT_EQ(ufunc.target_type(), typeid(std::function)); + + EXPECT_FALSE(ufunc.template target()); + EXPECT_FALSE(ufunc.template target>()); + + std::function& ufunc_target = + *ufunc.template target>(); + + EXPECT_EQ(ufunc_target(300), 325); +} + +TEST(Function, Target_T) { + target_test(); +} +TEST(Function, Target_N) { + target_test(); +} + +// TEST ===================================================================== +// OverloadedFunctor + +TEST(Function, OverloadedFunctor) { + struct OverloadedFunctor { + // variant 1 + int operator()(int x) { + return 100 + 1 * x; + } + + // variant 2 (const-overload of v1) + int operator()(int x) const { + return 100 + 2 * x; + } + + // variant 3 + int operator()(int x, int) { + return 100 + 3 * x; + } + + // variant 4 (const-overload of v3) + int operator()(int x, int) const { + return 100 + 4 * x; + } + + // variant 5 (non-const, has no const-overload) + int operator()(int x, char const*) { + return 100 + 5 * x; + } + + // variant 6 (const only) + int operator()(int x, std::vector const&) const { + return 100 + 6 * x; + } + }; + OverloadedFunctor of; + + Function variant1 = of; + EXPECT_EQ(variant1(15), 100 + 1 * 15); + + Function variant2 = of; + EXPECT_EQ(variant2(16), 100 + 2 * 16); + + Function variant3 = of; + EXPECT_EQ(variant3(17, 0), 100 + 3 * 17); + + Function variant4 = of; + EXPECT_EQ(variant4(18, 0), 100 + 4 * 18); + + Function variant5 = of; + EXPECT_EQ(variant5(19, "foo"), 100 + 5 * 19); + + Function const&)> variant6 = of; + EXPECT_EQ(variant6(20, {}), 100 + 6 * 20); + EXPECT_EQ(variant6(20, {1, 2, 3}), 100 + 6 * 20); + + Function const&) const> variant6const = of; + EXPECT_EQ(variant6const(21, {}), 100 + 6 * 21); + + // Cast const-functions to non-const and the other way around: if the functor + // has both const and non-const operator()s for a given parameter signature, + // constructing a Function must select one of them, depending on + // whether the function type template parameter is const-qualified or not. + // When the const-ness is later changed (by moving the + // Function into a Function or by + // calling the folly::constCastFunction which moves it into a + // Function), the Function must still execute + // the initially selected function. + + auto variant1_const = folly::constCastFunction(std::move(variant1)); + EXPECT_THROW(variant1(0), std::bad_function_call); + EXPECT_EQ(variant1_const(22), 100 + 1 * 22); + + Function variant2_nonconst = std::move(variant2); + EXPECT_THROW(variant2(0), std::bad_function_call); + EXPECT_EQ(variant2_nonconst(23), 100 + 2 * 23); + + auto variant3_const = folly::constCastFunction(std::move(variant3)); + EXPECT_THROW(variant3(0, 0), std::bad_function_call); + EXPECT_EQ(variant3_const(24, 0), 100 + 3 * 24); + + Function variant4_nonconst = std::move(variant4); + EXPECT_THROW(variant4(0, 0), std::bad_function_call); + EXPECT_EQ(variant4_nonconst(25, 0), 100 + 4 * 25); + + auto variant5_const = folly::constCastFunction(std::move(variant5)); + EXPECT_THROW(variant5(0, ""), std::bad_function_call); + EXPECT_EQ(variant5_const(26, "foo"), 100 + 5 * 26); + + auto variant6_const = folly::constCastFunction(std::move(variant6)); + EXPECT_THROW(variant6(0, {}), std::bad_function_call); + EXPECT_EQ(variant6_const(27, {}), 100 + 6 * 27); + + Function const&)> variant6const_nonconst = + std::move(variant6const); + EXPECT_THROW(variant6const(0, {}), std::bad_function_call); + EXPECT_EQ(variant6const_nonconst(28, {}), 100 + 6 * 28); +} + +// TEST ===================================================================== +// Lambda + +TEST(Function, Lambda) { + // Non-mutable lambdas: can be stored in a non-const... + Function func = [](int x) { return 1000 + x; }; + EXPECT_EQ(func(1), 1001); + + // ...as well as in a const Function + Function func_const = [](int x) { return 2000 + x; }; + EXPECT_EQ(func_const(1), 2001); + + // Mutable lambda: can only be stored in a const Function: + int number = 3000; + Function func_mutable = [number]() mutable { return ++number; }; + EXPECT_EQ(func_mutable(), 3001); + EXPECT_EQ(func_mutable(), 3002); + + // test after const-casting + + Function func_made_const = + folly::constCastFunction(std::move(func)); + EXPECT_EQ(func_made_const(2), 1002); + EXPECT_THROW(func(0), std::bad_function_call); + + Function func_const_made_nonconst = std::move(func_const); + EXPECT_EQ(func_const_made_nonconst(2), 2002); + EXPECT_THROW(func_const(0), std::bad_function_call); + + Function func_mutable_made_const = + folly::constCastFunction(std::move(func_mutable)); + EXPECT_EQ(func_mutable_made_const(), 3003); + EXPECT_EQ(func_mutable_made_const(), 3004); + EXPECT_THROW(func_mutable(), std::bad_function_call); +} + +// TEST ===================================================================== +// DataMember & MemberFunction + +struct MemberFunc { + int x; + int getX() const { + return x; + } + void setX(int xx) { + x = xx; + } +}; + +TEST(Function, DataMember) { + MemberFunc mf; + MemberFunc const& cmf = mf; + mf.x = 123; + + Function data_getter1 = &MemberFunc::x; + EXPECT_EQ(data_getter1(&cmf), 123); + Function data_getter2 = &MemberFunc::x; + EXPECT_EQ(data_getter2(&mf), 123); + Function data_getter3 = &MemberFunc::x; + EXPECT_EQ(data_getter3(cmf), 123); + Function data_getter4 = &MemberFunc::x; + EXPECT_EQ(data_getter4(mf), 123); +} + +TEST(Function, MemberFunction) { + MemberFunc mf; + MemberFunc const& cmf = mf; + mf.x = 123; + + Function getter1 = &MemberFunc::getX; + EXPECT_EQ(getter1(&cmf), 123); + Function getter2 = &MemberFunc::getX; + EXPECT_EQ(getter2(&mf), 123); + Function getter3 = &MemberFunc::getX; + EXPECT_EQ(getter3(cmf), 123); + Function getter4 = &MemberFunc::getX; + EXPECT_EQ(getter4(mf), 123); + + Function setter1 = &MemberFunc::setX; + setter1(&mf, 234); + EXPECT_EQ(mf.x, 234); + + Function setter2 = &MemberFunc::setX; + setter2(mf, 345); + EXPECT_EQ(mf.x, 345); +} + +// TEST ===================================================================== +// CaptureCopyMoveCount & ParameterCopyMoveCount + +class CopyMoveTracker { + public: + struct ConstructorTag {}; + + CopyMoveTracker() = delete; + explicit CopyMoveTracker(ConstructorTag) + : data_(std::make_shared>(0, 0)) {} + + CopyMoveTracker(CopyMoveTracker const& o) noexcept : data_(o.data_) { + ++data_->first; + } + CopyMoveTracker& operator=(CopyMoveTracker const& o) noexcept { + data_ = o.data_; + ++data_->first; + return *this; + } + + CopyMoveTracker(CopyMoveTracker&& o) noexcept : data_(o.data_) { + ++data_->second; + } + CopyMoveTracker& operator=(CopyMoveTracker&& o) noexcept { + data_ = o.data_; + ++data_->second; + return *this; + } + + size_t copyCount() const { + return data_->first; + } + size_t moveCount() const { + return data_->second; + } + size_t refCount() const { + return data_.use_count(); + } + void resetCounters() { + data_->first = data_->second = 0; + } + + private: + // copy, move + std::shared_ptr> data_; +}; + +TEST(Function, CaptureCopyMoveCount) { + // This test checks that no unnecessary copies/moves are made. + + CopyMoveTracker cmt(CopyMoveTracker::ConstructorTag{}); + EXPECT_EQ(cmt.copyCount(), 0); + EXPECT_EQ(cmt.moveCount(), 0); + EXPECT_EQ(cmt.refCount(), 1); + + // Move into lambda, move lambda into Function + auto lambda1 = [cmt = std::move(cmt)]() { + return cmt.moveCount(); + }; + Function uf1 = std::move(lambda1); + + // Max copies: 0. Max copy+moves: 2. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2); + EXPECT_LE(cmt.copyCount(), 0); + + cmt.resetCounters(); + + // Move into lambda, copy lambda into Function + auto lambda2 = [cmt = std::move(cmt)]() { + return cmt.moveCount(); + }; + Function uf2 = lambda2; + + // Max copies: 1. Max copy+moves: 2. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2); + EXPECT_LE(cmt.copyCount(), 1); + + // Invoking Function must not make copies/moves of the callable + cmt.resetCounters(); + uf1(); + uf2(); + EXPECT_EQ(cmt.copyCount(), 0); + EXPECT_EQ(cmt.moveCount(), 0); +} + +TEST(Function, ParameterCopyMoveCount) { + // This test checks that no unnecessary copies/moves are made. + + CopyMoveTracker cmt(CopyMoveTracker::ConstructorTag{}); + EXPECT_EQ(cmt.copyCount(), 0); + EXPECT_EQ(cmt.moveCount(), 0); + EXPECT_EQ(cmt.refCount(), 1); + + // pass by value + Function uf1 = [](CopyMoveTracker c) { + return c.moveCount(); + }; + + cmt.resetCounters(); + uf1(cmt); + // Max copies: 1. Max copy+moves: 2. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2); + EXPECT_LE(cmt.copyCount(), 1); + + cmt.resetCounters(); + uf1(std::move(cmt)); + // Max copies: 1. Max copy+moves: 2. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2); + EXPECT_LE(cmt.copyCount(), 0); + + // pass by reference + Function uf2 = [](CopyMoveTracker& c) { + return c.moveCount(); + }; + + cmt.resetCounters(); + uf2(cmt); + // Max copies: 0. Max copy+moves: 0. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0); + EXPECT_LE(cmt.copyCount(), 0); + + // pass by const reference + Function uf3 = [](CopyMoveTracker const& c) { + return c.moveCount(); + }; + + cmt.resetCounters(); + uf3(cmt); + // Max copies: 0. Max copy+moves: 0. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0); + EXPECT_LE(cmt.copyCount(), 0); + + // pass by rvalue reference + Function uf4 = [](CopyMoveTracker&& c) { + return c.moveCount(); + }; + + cmt.resetCounters(); + uf4(std::move(cmt)); + // Max copies: 0. Max copy+moves: 0. + EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0); + EXPECT_LE(cmt.copyCount(), 0); +} + +// TEST ===================================================================== +// CopyMoveThrows + +enum ExceptionType { COPY, MOVE }; + +template +class CopyMoveException : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +template +struct CopyMoveThrowsCallable { + int allowCopyOperations{0}; + int allowMoveOperations{0}; + + CopyMoveThrowsCallable() = default; + CopyMoveThrowsCallable(CopyMoveThrowsCallable const& o) noexcept( + !CopyThrows) { + *this = o; + } + CopyMoveThrowsCallable& operator=(CopyMoveThrowsCallable const& o) noexcept( + !CopyThrows) { + allowCopyOperations = o.allowCopyOperations; + allowMoveOperations = o.allowMoveOperations; + + if (allowCopyOperations > 0) { + --allowCopyOperations; + } else if (CopyThrows) { + throw CopyMoveException("CopyMoveThrowsCallable copy"); + } + return *this; + } + CopyMoveThrowsCallable(CopyMoveThrowsCallable&& o) noexcept(!MoveThrows) { + *this = std::move(o); + } + CopyMoveThrowsCallable& operator=(CopyMoveThrowsCallable&& o) noexcept( + !MoveThrows) { + allowCopyOperations = o.allowCopyOperations; + allowMoveOperations = o.allowMoveOperations; + + if (o.allowMoveOperations > 0) { + --allowMoveOperations; + } else if (MoveThrows) { + throw CopyMoveException("CopyMoveThrowsCallable move"); + } + return *this; + } + + void operator()() const {} +}; + +TEST(Function, CopyMoveThrowsCallable) { + EXPECT_TRUE((std::is_nothrow_move_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_TRUE((std::is_nothrow_move_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_FALSE((std::is_nothrow_move_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_FALSE((std::is_nothrow_move_constructible< + CopyMoveThrowsCallable>::value)); + + EXPECT_TRUE((std::is_nothrow_copy_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_FALSE((std::is_nothrow_copy_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_TRUE((std::is_nothrow_copy_constructible< + CopyMoveThrowsCallable>::value)); + EXPECT_FALSE((std::is_nothrow_copy_constructible< + CopyMoveThrowsCallable>::value)); +} + +template +void copy_and_move_throws_test() { + CopyMoveThrowsCallable c; + Function uf; + + if (CopyThrows) { + EXPECT_THROW((uf = c), CopyMoveException); + } else { + EXPECT_NO_THROW((uf = c)); + } + + if (MoveThrows) { + EXPECT_THROW((uf = std::move(c)), CopyMoveException); + } else { + EXPECT_NO_THROW((uf = std::move(c))); + } + + c.allowMoveOperations = 1; + uf = std::move(c); + if (NEM == FunctionMoveCtor::MAY_THROW && MoveThrows) { + Function uf2; + EXPECT_THROW((uf2 = std::move(uf)), CopyMoveException); + } else { + Function uf2; + EXPECT_NO_THROW((uf2 = std::move(uf))); + } + + c.allowMoveOperations = 0; + c.allowCopyOperations = 1; + uf = c; + if (NEM == FunctionMoveCtor::MAY_THROW && MoveThrows) { + Function uf2; + EXPECT_THROW((uf2 = std::move(uf)), CopyMoveException); + } else { + Function uf2; + EXPECT_NO_THROW((uf2 = std::move(uf))); + } +} + +TEST(Function, CopyAndMoveThrows_TNN) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_NNN) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_TTN) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_NTN) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_TNT) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_NNT) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_TTT) { + copy_and_move_throws_test(); +} + +TEST(Function, CopyAndMoveThrows_NTT) { + copy_and_move_throws_test(); +} + +// TEST ===================================================================== +// VariadicTemplate & VariadicArguments + +struct VariadicTemplateSum { + int operator()() const { + return 0; + } + template + int operator()(int x, Args... args) const { + return x + (*this)(args...); + } +}; + +TEST(Function, VariadicTemplate) { + Function uf1 = VariadicTemplateSum(); + Function uf2 = VariadicTemplateSum(); + Function uf3 = VariadicTemplateSum(); + + EXPECT_EQ(uf1(66), 66); + EXPECT_EQ(uf2(55, 44), 99); + EXPECT_EQ(uf3(33, 22, 11), 66); +} + +struct VariadicArgumentsSum { + int operator()(int count, ...) const { + int result = 0; + va_list args; + va_start(args, count); + for (int i = 0; i < count; ++i) { + result += va_arg(args, int); + } + va_end(args); + return result; + } +}; + +TEST(Function, VariadicArguments) { + Function uf1 = VariadicArgumentsSum(); + Function uf2 = VariadicArgumentsSum(); + Function uf3 = VariadicArgumentsSum(); + + EXPECT_EQ(uf1(0), 0); + EXPECT_EQ(uf2(1, 66), 66); + EXPECT_EQ(uf3(2, 55, 44), 99); +} + +// TEST ===================================================================== +// SafeCaptureByReference + +// A function can use Function const& as a parameter to signal that it +// is safe to pass a lambda that captures local variables by reference. +// It is safe because we know the function called can only invoke the +// Function until it returns. It can't store a copy of the Function +// (because it's not copyable), and it can't move the Function somewhere +// else (because it gets only a const&). + +template +void for_each( + T const& range, + Function const& func) { + for (auto const& elem : range) { + func(elem); + } +} + +TEST(Function, SafeCaptureByReference) { + std::vector const vec = {20, 30, 40, 2, 3, 4, 200, 300, 400}; + + int sum = 0; + + // for_each's second parameter is of type Function<...> const&. + // Hence we know we can safely pass it a lambda that references local + // variables. There is no way the reference to x will be stored anywhere. + for_each>(vec, [&sum](int x) { sum += x; }); + + // gcc versions before 4.9 cannot deduce the type T in the above call + // to for_each. Modern compiler versions can compile the following line: + // for_each(vec, [&sum](int x) { sum += x; }); + + EXPECT_EQ(sum, 999); +} diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index 0843c104..7d037b96 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -249,4 +249,8 @@ futures_test_SOURCES = \ futures_test_LDADD = libfollytestmain.la TESTS += futures_test +function_test_SOURCES = FunctionTest.cpp +function_test_LDADD = libfollytestmain.la +TESTS += function_test + check_PROGRAMS += $(TESTS) diff --git a/folly/test/function_benchmark/benchmark_impl.cpp b/folly/test/function_benchmark/benchmark_impl.cpp index 0a6a9320..cd2ed942 100644 --- a/folly/test/function_benchmark/benchmark_impl.cpp +++ b/folly/test/function_benchmark/benchmark_impl.cpp @@ -36,6 +36,13 @@ void BM_std_function_invoke_impl(int iters, } } +void BM_Function_invoke_impl(int iters, + const folly::Function& fn) { + for (int n = 0; n < iters; ++n) { + fn(); + } +} + void BM_mem_fn_invoke_impl(int iters, TestClass* tc, void (TestClass::*memfn)()) { diff --git a/folly/test/function_benchmark/benchmark_impl.h b/folly/test/function_benchmark/benchmark_impl.h index 964ba313..d7766fbc 100644 --- a/folly/test/function_benchmark/benchmark_impl.h +++ b/folly/test/function_benchmark/benchmark_impl.h @@ -18,11 +18,15 @@ #include +#include + class TestClass; class VirtualClass; void BM_fn_ptr_invoke_impl(int iters, void (*fn)()); void BM_std_function_invoke_impl(int iters, const std::function& fn); +void BM_Function_invoke_impl(int iters, + const folly::Function& fn); void BM_mem_fn_invoke_impl(int iters, TestClass* tc, void (TestClass::*memfn)()); diff --git a/folly/test/function_benchmark/main.cpp b/folly/test/function_benchmark/main.cpp index b0836719..171d7aed 100644 --- a/folly/test/function_benchmark/main.cpp +++ b/folly/test/function_benchmark/main.cpp @@ -45,6 +45,11 @@ BENCHMARK(std_function_invoke, iters) { BM_std_function_invoke_impl(iters, doNothing); } +// Invoking a function through a folly::Function object +BENCHMARK(Function_invoke, iters) { + BM_Function_invoke_impl(iters, doNothing); +} + // Invoking a member function through a member function pointer BENCHMARK(mem_fn_invoke, iters) { TestClass tc; @@ -111,6 +116,15 @@ BENCHMARK(std_function_create_invoke, iters) { } } +// Creating a folly::Function object from a function pointer, and +// invoking it +BENCHMARK(Function_create_invoke, iters) { + for (size_t n = 0; n < iters; ++n) { + folly::Function fn = doNothing; + fn(); + } +} + // Creating a pointer-to-member and invoking it BENCHMARK(mem_fn_create_invoke, iters) { TestClass tc; @@ -155,6 +169,14 @@ BENCHMARK(scope_guard_std_function_rvalue, iters) { } } +// Using ScopeGuard to invoke a folly::Function, +// but create the ScopeGuard with an rvalue to a folly::Function +BENCHMARK(scope_guard_Function_rvalue, iters) { + for (size_t n = 0; n < iters; ++n) { + ScopeGuard g = makeGuard(folly::Function(doNothing)); + } +} + // Using ScopeGuard to invoke a function pointer BENCHMARK(scope_guard_fn_ptr, iters) { for (size_t n = 0; n < iters; ++n) { -- 2.34.1