From: Eric Niebler Date: Wed, 27 Apr 2016 17:02:00 +0000 (-0700) Subject: Reimplement folly::Function to improve compile times. X-Git-Tag: 2016.07.26~314 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=5022d5469d551c2a62d367792da9453392c464fa;p=folly.git Reimplement folly::Function to improve compile times. Summary: folly::Function is causing significant compile time regressions. Reimplement it in a simpler way. These are the times for a file containing 1000 instantiations of folly::Fuction (old), folly::Function (new), and std::function with **g++ 4.8 -O3** on my CentOS7 server. | | Old `folly::Function` | `std::function` | New `folly::Function` | |--------|-----------------------|-----------------|-----------------------| | Time | 10m37s | 0m16.81s | 0m14.75s | And for the executable size: | | Old `folly::Function` | `std::function` | New `folly::Function` | |--------|-----------------------|-----------------|-----------------------| | Size | 10,409,504 | 732,150 | 562,781 | That's a **43X** improvement in compile times and an **18X** reduction in executable bloat over the old implementation. The times for **clang (trunk)** are very different: | | Old `folly::Function` | `std::function` | New `folly::Function` | |-------|-----------------------|-----------------|-----------------------| | Time | 4m6s | 0m45.27s | 0m11.78s | That's a **20X** improvement over the old implementation and almost a **4X** improvement over `std::function`. For **gcc-5.3.0**, compile times are again different: | | Old `folly::Function` | `std::function` | New `folly::Function` | |-------|-----------------------|-----------------|-----------------------| | Time | 2m49s | 0m18.99s | 0m20.70s | With gcc-5.3, the new implementation "only" compiles 8x faster than the old one, and is roughly the same as `std::function`. Reviewed By: spacedentist, ot, luciang Differential Revision: D3199985 fb-gh-sync-id: b97982a9dc3a63140510babea34988932e89f2d9 fbshipit-source-id: b97982a9dc3a63140510babea34988932e89f2d9 --- diff --git a/folly/Function-inl.h b/folly/Function-inl.h deleted file mode 100644 index 7c34f92c..00000000 --- a/folly/Function-inl.h +++ /dev/null @@ -1,486 +0,0 @@ -/* - * 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, - typename std::enable_if::NonConstFunctionType>::value>:: - type*) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && - EmbedFunctorSize >= OtherEmbedFunctorSize) { - using OtherFunction = - Function; - - 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 deleted file mode 100644 index 5e91fbb5..00000000 --- a/folly/Function-pre.h +++ /dev/null @@ -1,332 +0,0 @@ -/* - * 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 to check whether the return type of a callable matches that of -// a folly::Function object. Either because the former is convertible to -// the latter, or the latter is void (possibly cv-qualified) -template -using ReturnTypeMatches = std::integral_constant< - bool, - std::is_convertible::value || - std::is_same::type, void>::value>; - -// 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 = - ReturnTypeMatches::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 = - ReturnTypeMatches::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 T is a Function with the same -// function type as OtherFunctionType (except for const-ness which may differ) -template -struct IsFunction : std::false_type {}; - -template < - typename FunctionType, - FunctionMoveCtor NTM, - size_t EmbedFunctorSize, - typename OtherFunctionType> -struct IsFunction< - ::folly::Function, - OtherFunctionType> - : std::is_same< - typename FunctionTypeTraits::NonConstFunctionType, - typename FunctionTypeTraits< - OtherFunctionType>::NonConstFunctionType> {}; - -// Helper template to check if a functor can be called with arguments of type -// Args..., if it returns a type convertible to R (or R is void), and also is -// not a folly::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, FunctionType>::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 static_cast(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 static_cast(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 -struct InvokeFromSharedPtr final { - std::shared_ptr ptr_; - - explicit InvokeFromSharedPtr(std::shared_ptr ptr) - : ptr_(std::move(ptr)) {} - - template - auto operator()(Args&&... args) - -> decltype((*ptr_)(std::forward(args)...)) { - return (*ptr_)(std::forward(args)...); - } -}; - -} // namespace function -} // namespace detail -} // namespace folly diff --git a/folly/Function.h b/folly/Function.h index 399efd54..c5f4a483 100644 --- a/folly/Function.h +++ b/folly/Function.h @@ -1,6 +1,8 @@ /* * Copyright 2016 Facebook, Inc. * + * @author Eric Niebler (eniebler@fb.com), Sven Over (over@fb.com) + * * 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 @@ -12,6 +14,8 @@ * 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. + * + * Acknowledgements: Giuseppe Ottaviano (ott@fb.com) */ /** @@ -73,10 +77,9 @@ * 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. + * The 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; @@ -210,153 +213,198 @@ * However, in that case what you do is potentially dangerous and requires * the equivalent of a `const_cast`, hence you need to call * `constCastFunction`. - * - * `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 -#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)> +namespace impl { +template class Function; -} // folly +template +Function constCastFunction( + Function&&) noexcept; +} -// boring predeclarations and details -#include "Function-pre.h" +namespace detail { +namespace function { -namespace folly { +enum class Op { MOVE, NUKE, FULL, HEAP }; -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; - }; +union Data { + void* big; + typename std::aligned_storage<6 * sizeof(void*)>::type small; +}; - public: - // not copyable - Function(Function const&) = delete; - Function& operator=(Function const&) = delete; +struct Tag {}; + +template +using ConstIf = typename std::conditional::type; + +template ::type> +using IsSmall = std::integral_constant< + bool, + (sizeof(FunT) <= sizeof(Data::small) && +#if defined(__GNUC__) && !defined(__clang__) + // GCC has a name mangling bug that causes hard errors if we use noexcept + // directly here. Last tested at gcc 5.3.0. + // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70790 + std::is_nothrow_move_constructible::value +#else + // Same as is_nothrow_move_constructible, but w/ no template instantiation. + noexcept(FunT(std::declval())) +#endif + )>; + +template +bool isNullPtrFn(T* p) { + return p == nullptr; +} +template +std::false_type isNullPtrFn(T&&) { + return {}; +} - /** - * Default constructor. Constructs an empty Function. - */ - Function() noexcept { - initializeEmptyExecutor(); - } +template +ReturnType uninitCall(Data&, Args&&...) { + throw std::bad_function_call(); +} +inline bool uninitNoop(Op, Data*, Data*) { + return false; +} +} // namespace function +} // namespace detail - ~Function() { - destroyExecutor(); +namespace impl { - static_assert( - kStorageSize == sizeof(*this), - "There is something wrong with the size of Function"); - } +template +class Function final { + using Data = detail::function::Data; + using Op = detail::function::Op; + using Tag = detail::function::Tag; + using Call = ReturnType (*)(Data&, Args&&...); + using Exec = bool (*)(Op, Data*, Data*); - // construct/assign from Function - /** - * Move constructor - */ - Function(Function&& other) noexcept(hasNoExceptMoveCtor()); - /** - * Move assignment operator - */ - Function& operator=(Function&& rhs) noexcept(hasNoExceptMoveCtor()); + template + using ConstIf = detail::function::ConstIf; + template + using IsSmall = detail::function::IsSmall; - /** - * Construct a std::function by moving in the contents of this `Function`. - * Note that the returned std::function will share its state (i.e. captured - * data) across all copies you make of it, so be very careful when copying. - */ - std::function asStdFunction() && { - return detail::function::InvokeFromSharedPtr( - std::make_shared(std::move(*this))); + Data data_; + Call call_; + Exec exec_; + + friend Function constCastFunction<>( + Function&&) noexcept; + friend class Function; + + template ::type> + Function( + Fun&& fun, + typename std::enable_if::value, Tag>:: + type) noexcept(noexcept(FunT(std::declval()))) + : Function() { + struct Ops { + static ReturnType call(Data& p, Args&&... args) { + return static_cast((*static_cast*>( + (void*)&p.small))(static_cast(args)...)); + } + static bool exec(Op o, Data* src, Data* dst) { + switch (o) { + case Op::MOVE: + ::new ((void*)&dst->small) + FunT(std::move(*static_cast((void*)&src->small))); + FOLLY_FALLTHROUGH; + case Op::NUKE: + static_cast((void*)&src->small)->~FunT(); + break; + case Op::FULL: + return true; + case Op::HEAP: + break; + } + return false; + } + }; + if (!detail::function::isNullPtrFn(fun)) { + ::new (&data_.small) FunT(static_cast(fun)); + exec_ = &Ops::exec; + call_ = &Ops::call; + } + } + + template ::type> + Function(Fun&& fun, typename std::enable_if::value, Tag>::type) + : Function() { + struct Ops { + static ReturnType call(Data& p, Args&&... args) { + return static_cast((*static_cast*>(p.big))( + static_cast(args)...)); + } + static bool exec(Op o, Data* src, Data* dst) { + switch (o) { + case Op::MOVE: + dst->big = src->big; + src->big = nullptr; + break; + case Op::NUKE: + delete static_cast(src->big); + break; + case Op::FULL: + case Op::HEAP: + break; + } + return true; + } + }; + data_.big = new FunT(static_cast(fun)); + call_ = &Ops::call; + exec_ = &Ops::exec; } + template ::type> + using ResultOf = decltype(static_cast( + std::declval&>()(std::declval()...))); + public: /** - * Constructs a `Function` by moving from one with different template - * parameters with regards to const-ness, no-except-movability and internal - * storage size. + * Default constructor. Constructs an empty Function. */ - template < - typename OtherFunctionType, - FunctionMoveCtor OtherNTM, - size_t OtherEmbedFunctorSize> - Function( - Function&& other, - typename std::enable_if::NonConstFunctionType>::value>::type* = - 0) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && - EmbedFunctorSize >= OtherEmbedFunctorSize); + Function() noexcept + : call_(&detail::function::uninitCall), + exec_(&detail::function::uninitNoop) {} + + // not copyable + // NOTE: Deleting the non-const copy constructor is unusual but necessary to + // prevent copies from non-const `Function` object from selecting the + // perfect forwarding implicit converting constructor below + // (i.e., `template Function(Fun&&)`). + Function(Function&) = delete; + Function(const Function&) = delete; /** - * Moves a `Function` with different template parameters with regards - * to const-ness, no-except-movability and internal storage size into this - * one. + * Move constructor */ - template < - typename RhsFunctionType, - FunctionMoveCtor RhsNTM, - size_t RhsEmbedFunctorSize> - Function& operator=(Function&& - rhs) noexcept(RhsNTM == FunctionMoveCtor::NO_THROW); + Function(Function&& that) noexcept : Function() { + that.exec_(Op::MOVE, &that.data_, &data_); + std::swap(call_, that.call_); + std::swap(exec_, that.exec_); + } /** * 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, @@ -368,199 +416,238 @@ class Function final * 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 + * `operator() const` otherwise. + * + * \note `typename = ResultOf` prevents this overload from being + * selected by overload resolution when `fun` is not a compatible function. */ - 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)); - } + template > + /* implicit */ Function(Fun&& fun) noexcept( + noexcept(Function(std::declval(), Tag{}))) + : Function(static_cast(fun), Tag{}) {} /** - * Assigns a callable object to this `Function`. + * For moving a `Function` into a `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; + template < + bool OtherConst, + typename std::enable_if::type = 0> + Function(Function&& that) noexcept + : Function() { + that.exec_(Op::MOVE, &that.data_, &data_); + std::swap(call_, that.call_); + std::swap(exec_, that.exec_); } /** - * 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. + * If `ptr` is null, constructs an empty `Function`. Otherwise, + * this constructor is equivalent to `Function(std::mem_fn(ptr))`. */ - template - void - swap(Function& o) noexcept( - hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW); + template < + typename Member, + typename Class, + // Prevent this overload from being selected when `ptr` is not a + // compatible member function pointer. + typename = decltype(Function(std::mem_fn((Member Class::*)0)))> + /* implicit */ Function(Member Class::*ptr) noexcept : Function() { + if (ptr) { + *this = std::mem_fn(ptr); + } + } + + ~Function() { + exec_(Op::NUKE, &data_, nullptr); + } + + Function& operator=(Function&) = delete; + Function& operator=(const Function&) = delete; /** - * Returns `true` if this `Function` contains a callable, i.e. is - * non-empty. + * Move assignment operator */ - explicit operator bool() const noexcept; + Function& operator=(Function&& that) noexcept { + if (&that != this) { + // Q: Why is is safe to destroy and reconstruct this object in place? + // A: Two reasons: First, `Function` is a final class, so in doing this + // we aren't slicing off any derived parts. And second, the move + // operation is guaranteed not to throw so we always leave the object + // in a valid state. + this->~Function(); + ::new (this) Function(std::move(that)); + } + return *this; + } /** - * 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. + * Assigns a callable object to this `Function`. If the operation fails, + * `*this` is left unmodified. + * + * \note `typename = ResultOf` prevents this overload from being + * selected by overload resolution when `fun` is not a compatible function. */ - bool hasAllocatedMemory() const noexcept; + template > + Function& operator=(Fun&& fun) noexcept( + noexcept(/* implicit */ Function(std::declval()))) { + // Doing this in place is more efficient when we can do so safely. + if (noexcept(/* implicit */ Function(std::declval()))) { + // Q: Why is is safe to destroy and reconstruct this object in place? + // A: See the explanation in the move assignment operator. + this->~Function(); + ::new (this) Function(static_cast(fun)); + } else { + // Construct a temporary and (nothrow) swap. + Function(static_cast(fun)).swap(*this); + } + return *this; + } /** - * Returns the `type_info` (as returned by `typeid`) of the callable stored - * in this `Function`. Returns `typeid(void)` if empty. + * Clears this `Function`. */ - std::type_info const& target_type() const noexcept; + Function& operator=(std::nullptr_t) noexcept { + return (*this = Function()); + } /** - * Returns a pointer to the stored callable if its type matches `T`, and - * `nullptr` otherwise. + * If `ptr` is null, clears this `Function`. Otherwise, this assignment + * operator is equivalent to `*this = std::mem_fn(ptr)`. */ - template - T* target() noexcept; + template + auto operator=(Member Class::*ptr) noexcept + // Prevent this overload from being selected when `ptr` is not a + // compatible member function pointer. + -> decltype(operator=(std::mem_fn(ptr))) { + return ptr ? (*this = std::mem_fn(ptr)) : (*this = Function()); + } /** - * Returns a const-pointer to the stored callable if its type matches `T`, - * and `nullptr` otherwise. + * Call the wrapped callable object with the specified arguments. + * If this `Function` object is a const `folly::Function` object, + * this overload shall not participate in overload resolution. */ - template - const T* target() const noexcept; + template < + // `True` makes `operator()` a template so we can SFINAE on `Const`, + // which is non-deduced here. + bool True = true, + typename std::enable_if::type = 0> + ReturnType operator()(Args... args) { + return call_(data_, static_cast(args)...); + } /** - * 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! + * Call the wrapped callable object with the specified arguments. + * If this `Function` object is not a const `folly::Function` object, + * this overload shall not participate in overload resolution. */ - 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 < + // `True` makes `operator()` a template so we can SFINAE on `Const`, + // which is non-deduced here. + bool True = true, + typename std::enable_if::type = 0> + ReturnType operator()(Args... args) const { + return call_(const_cast(data_), static_cast(args)...); + } - template - void createExecutor(F&& f) noexcept( - noexcept(typename std::decay::type(std::forward(f)))); + /** + * Exchanges the callable objects of `*this` and `that`. + */ + void swap(Function& that) noexcept { + std::swap(*this, that); + } - void destroyExecutor() noexcept; + /** + * Returns `true` if this `Function` contains a callable, i.e. is + * non-empty. + */ + explicit operator bool() const noexcept { + return exec_(Op::FULL, nullptr, nullptr); + } - struct MinStorageSize; + /** + * 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 { + return exec_(Op::HEAP, nullptr, nullptr); + } - typename std::aligned_storage::type data_; - static constexpr size_t kStorageSize = sizeof(data_); + /** + * Construct a `std::function` by moving in the contents of this `Function`. + * Note that the returned `std::function` will share its state (i.e. captured + * data) across all copies you make of it, so be very careful when copying. + */ + std::function asStdFunction() && { + struct Impl { + std::shared_ptr sp_; + ReturnType operator()(Args&&... args) const { + return (*sp_)(static_cast(args)...); + } + }; + return Impl{std::make_shared(std::move(*this))}; + } }; -// operator== -template -inline bool operator==( - Function const& f, - std::nullptr_t) noexcept { - return !f; +template +void swap( + Function& lhs, + Function& rhs) noexcept { + lhs.swap(rhs); } -template -inline bool operator==( - std::nullptr_t, - Function const& f) noexcept { - return !f; +template +bool operator==(const Function& fn, std::nullptr_t) { + return !fn; } -template -inline bool operator!=( - Function const& f, - std::nullptr_t) noexcept { - return !!f; +template +bool operator==(std::nullptr_t, const Function& fn) { + return !fn; } -template -inline bool operator!=( - std::nullptr_t, - Function const& f) noexcept { - return !!f; +template +bool operator!=(const Function& fn, std::nullptr_t) { + return !(fn == nullptr); } -/** - * 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(); +template +bool operator!=(std::nullptr_t, const Function& fn) { + return !(nullptr == fn); } -template -void swap( - Function& lhs, - Function& rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); +template +Function constCastFunction( + Function&& that) noexcept { + Function fn{}; + that.exec_(detail::function::Op::MOVE, &that.data_, &fn.data_); + std::swap(fn.call_, that.call_); + std::swap(fn.exec_, that.exec_); + return fn; } -template < - typename FunctionType, - FunctionMoveCtor NOM1, - FunctionMoveCtor NOM2, - size_t S1, - size_t S2> -void swap( - Function& lhs, - Function& rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); +template +Function constCastFunction( + Function&& that) noexcept { + return std::move(that); } -} // namespace folly +template +struct MakeFunction {}; + +template +struct MakeFunction { + using type = Function; +}; -#include "Function-inl.h" +template +struct MakeFunction { + using type = Function; +}; +} // namespace impl + +/* using override */ using impl::constCastFunction; + +template +using Function = typename impl::MakeFunction::type; +} diff --git a/folly/Makefile.am b/folly/Makefile.am index 5eb27680..f82d4898 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -341,8 +341,6 @@ nobase_follyinclude_HEADERS = \ Traits.h \ Unicode.h \ Function.h \ - Function-inl.h \ - Function-pre.h \ Uri.h \ Uri-inl.h \ Varint.h \ diff --git a/folly/futures/detail/Core.h b/folly/futures/detail/Core.h index a4105900..b2fb18d8 100644 --- a/folly/futures/detail/Core.h +++ b/folly/futures/detail/Core.h @@ -374,11 +374,7 @@ class Core { // sizeof(Core) == size(Core). // See Core::convert for details. - folly::Function< - void(Try&&), - folly::FunctionMoveCtor::MAY_THROW, - 8 * sizeof(void*)> - callback_; + folly::Function&&)> callback_; // place result_ next to increase the likelihood that the value will be // contained entirely in one cache line folly::Optional> result_; diff --git a/folly/test/FunctionTest.cpp b/folly/test/FunctionTest.cpp index 24a54d0d..7c5159c9 100644 --- a/folly/test/FunctionTest.cpp +++ b/folly/test/FunctionTest.cpp @@ -21,7 +21,6 @@ #include #include -using folly::FunctionMoveCtor; using folly::Function; namespace { @@ -31,9 +30,6 @@ int func_int_int_add_25(int x) { 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; } @@ -53,157 +49,57 @@ struct Functor { 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(43, func(42)); - 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(52, func_safe_move(42)); - 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(52, func_movethrows(42)); - EXPECT_FALSE(func_movethrows.hasAllocatedMemory()); - EXPECT_FALSE( - std::is_nothrow_move_constructible::value); -} +} // namespace // TEST ===================================================================== // InvokeFunctor & InvokeReference -template -void invoke_functor_test() { +TEST(Function, InvokeFunctor) { Functor func; + static_assert( + sizeof(func) > sizeof(Function), + "sizeof(Function) is much larger than expected"); 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); + Function getter = std::move(func); + + // Function will allocate memory on the heap to store the functor object + EXPECT_TRUE(getter.hasAllocatedMemory()); EXPECT_EQ(123, getter(5)); } -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() { +TEST(Function, InvokeReference) { 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); + // Have Functions for getter and setter, both referencing the same funtor + Function getter = std::ref(func); + Function setter = std::ref(func); EXPECT_EQ(123, getter(5)); EXPECT_EQ(123, setter(5, 456)); EXPECT_EQ(456, setter(5, 567)); EXPECT_EQ(567, getter(5)); } -TEST(Function, InvokeReference_T) { - invoke_reference_test(); -} -TEST(Function, InvokeReference_N) { - invoke_reference_test(); -} // TEST ===================================================================== // Emptiness -template -void emptiness_test() { - Function f; +TEST(Function, Emptiness_T) { + 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; }); + Function g([](int x) { return x + 1; }); EXPECT_NE(g, nullptr); EXPECT_NE(nullptr, g); EXPECT_TRUE(g); EXPECT_EQ(100, g(99)); - Function h(&func_int_int_add_25); + Function h(&func_int_int_add_25); EXPECT_NE(h, nullptr); EXPECT_NE(nullptr, h); EXPECT_TRUE(h); @@ -216,60 +112,13 @@ void emptiness_test() { 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 +template void swap_test() { - Function mf1(func_int_int_add_25); - Function mf2(func_int_int_add_111); + Function mf1(func_int_int_add_25); + Function mf2(func_int_int_add_111); EXPECT_EQ(125, mf1(100)); EXPECT_EQ(211, mf2(100)); @@ -314,37 +163,18 @@ void swap_test() { EXPECT_EQ(nullptr, mf3); EXPECT_EQ(322, mf1(100)); } -TEST(Function, SwapMethod_TT) { - swap_test(); -} -TEST(Function, SwapMethod_TN) { - swap_test(); -} -TEST(Function, SwapMethod_NT) { - swap_test(); +TEST(Function, SwapMethod) { + swap_test(); } -TEST(Function, SwapMethod_NN) { - swap_test(); -} -TEST(Function, SwapFunction_TT) { - swap_test(); -} -TEST(Function, SwapFunction_TN) { - swap_test(); -} -TEST(Function, SwapFunction_NT) { - swap_test(); -} -TEST(Function, SwapFunction_NN) { - swap_test(); +TEST(Function, SwapFunction) { + swap_test(); } // TEST ===================================================================== // Bind -template -void bind_test() { - Function fnc = floatMult; +TEST(Function, Bind) { + 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(8, task()); @@ -352,18 +182,11 @@ void bind_test() { EXPECT_THROW(task(), std::bad_function_call); EXPECT_EQ(8, task2()); } -TEST(Function, Bind_T) { - bind_test(); -} -TEST(Function, Bind_N) { - bind_test(); -} // TEST ===================================================================== // NonCopyableLambda -template -void non_copyable_lambda_test() { +TEST(Function, NonCopyableLambda) { auto unique_ptr_int = folly::make_unique(900); EXPECT_EQ(900, *unique_ptr_int); @@ -376,184 +199,11 @@ void non_copyable_lambda_test() { EXPECT_EQ(901, functor()); - Function func = std::move(functor); - EXPECT_EQ( - func.hasAllocatedMemory(), - S < 2 || (NEM == FunctionMoveCtor::NO_THROW && - !std::is_nothrow_move_constructible::value)); + Function func = std::move(functor); + EXPECT_TRUE(func.hasAllocatedMemory()); EXPECT_EQ(902, func()); } -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(123, functor(3)); - - // Function with large callable storage area (twice the size of - // the functor) - Function func2x = - std::move(functor); - EXPECT_FALSE(func2x.hasAllocatedMemory()); - EXPECT_EQ(123, func2x(3, 200)); - EXPECT_EQ(200, func2x(3, 201)); - - // 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(201, func1x(3, 202)); - EXPECT_EQ(202, func1x(3, 203)); - - // 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(203, func0x(3, 204)); - EXPECT_EQ(204, func0x(3, 205)); - - // 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(205, funcnot(3, 206)); - EXPECT_EQ(206, funcnot(3, 207)); -} -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(100, *shared_int); - EXPECT_EQ(1, shared_int.use_count()); - - Function func1 = [shared_int]() { return ++*shared_int; }; - EXPECT_EQ(2, shared_int.use_count()); - EXPECT_EQ(101, func1()); - EXPECT_EQ(101, *shared_int); - - // func2: made to not fit functor. - Function func2 = std::move(func1); - EXPECT_THROW(func1(), std::bad_function_call); - EXPECT_EQ(2, shared_int.use_count()); - EXPECT_FALSE(func1); - EXPECT_EQ(102, func2()); - EXPECT_EQ(102, *shared_int); - - func2 = [shared_int]() { return ++*shared_int; }; - EXPECT_EQ(2, shared_int.use_count()); - EXPECT_EQ(103, func2()); - EXPECT_EQ(103, *shared_int); - - // 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(999, func2()); - EXPECT_EQ(1, shared_int.use_count()); - EXPECT_EQ(103, *shared_int); - - func2 = [shared_int]() { return ++*shared_int; }; - EXPECT_EQ(2, shared_int.use_count()); - EXPECT_EQ(104, func2()); - EXPECT_EQ(104, *shared_int); - - // 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(987, func2()); - EXPECT_EQ(1, shared_int.use_count()); - EXPECT_EQ(104, *shared_int); -} -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(125, func(100)); - - Function ufunc = std::move(func); - EXPECT_THROW(func(0), std::bad_function_call); - EXPECT_EQ(225, ufunc(200)); - - EXPECT_EQ(typeid(std::function), ufunc.target_type()); - - EXPECT_FALSE(ufunc.template target()); - EXPECT_FALSE(ufunc.template target>()); - - std::function& ufunc_target = - *ufunc.template target>(); - - EXPECT_EQ(325, ufunc_target(300)); -} - -TEST(Function, Target_T) { - target_test(); -} -TEST(Function, Target_N) { - target_test(); -} // TEST ===================================================================== // OverloadedFunctor @@ -885,149 +535,6 @@ TEST(Function, ParameterCopyMoveCount) { 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 @@ -1231,6 +738,9 @@ TEST(Function, ConvertReturnType) { EXPECT_EQ(66, cf9().x); } +// TEST ===================================================================== +// asStdFunction_* + TEST(Function, asStdFunction_void) { int i = 0; folly::Function f = [&] { ++i; }; @@ -1302,3 +812,40 @@ TEST(Function, asStdFunction_args_const) { sf(42, 42); EXPECT_EQ(1, i); } + +TEST(Function, NoAllocatedMemoryAfterMove) { + Functor foo; + + Function func = foo; + EXPECT_TRUE(func.hasAllocatedMemory()); + + Function func2 = std::move(func); + EXPECT_TRUE(func2.hasAllocatedMemory()); + EXPECT_FALSE(func.hasAllocatedMemory()); +} + +TEST(Function, ConstCastEmbedded) { + int x = 0; + auto functor = [&x]() { ++x; }; + + Function func(functor); + EXPECT_FALSE(func.hasAllocatedMemory()); + + Function func2(std::move(func)); + EXPECT_FALSE(func2.hasAllocatedMemory()); +} + +TEST(Function, EmptyAfterConstCast) { + Function func; + EXPECT_FALSE(func); + + Function func2 = constCastFunction(std::move(func)); + EXPECT_FALSE(func2); +} + +TEST(Function, SelfMoveAssign) { + Function f = [] { return 0; }; + Function& g = f; + f = std::move(g); + EXPECT_TRUE(f); +}