From: Eric Niebler <eniebler@fb.com> 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 <typename FunctionType> -class Executors<FunctionType>::ExecutorIf - : public Executors<FunctionType>::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<std::type_info const&, void*> 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 <typename FunctionType> -class Executors<FunctionType>::EmptyExecutor final - : public Executors<FunctionType>::ExecutorIf { - public: - EmptyExecutor() noexcept : ExecutorIf(&EmptyExecutor::invokeEmpty) {} - ~EmptyExecutor() {} - detail::function::AllocationStatus getAllocationStatus() const noexcept { - return detail::function::AllocationStatus::EMPTY; - } - - std::pair<std::type_info const&, void*> target() const noexcept { - return {typeid(void), nullptr}; - } - - template <typename DestinationExecutors> - 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<Executors<typename Traits::NonConstFunctionType>>(dest); - } - void moveTo( - typename ConstFunctionExecutors::ExecutorIf* dest, - size_t /*size*/, - FunctionMoveCtor /*throws*/) noexcept { - moveToImpl<Executors<typename Traits::ConstFunctionType>>(dest); - } -}; - -// function::FunctorPtrExecutor -template <typename FunctionType> -template <typename F, typename SelectFunctionTag> -class Executors<FunctionType>::FunctorPtrExecutor final - : public Executors<FunctionType>::ExecutorIf { - public: - FunctorPtrExecutor(F&& f) - : ExecutorIf( - &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>), - functorPtr_(new F(std::move(f))) {} - FunctorPtrExecutor(F const& f) - : ExecutorIf( - &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>), - functorPtr_(new F(f)) {} - FunctorPtrExecutor(std::unique_ptr<F> f) - : ExecutorIf( - &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>), - functorPtr_(std::move(f)) {} - ~FunctorPtrExecutor() {} - detail::function::AllocationStatus getAllocationStatus() const noexcept { - return detail::function::AllocationStatus::ALLOCATED; - } - - static auto getFunctor( - typename Traits::template QualifiedPointer<ExecutorIf> self) -> - typename SelectFunctionTag::template QualifiedPointer<F> { - return FunctorPtrExecutor::selectFunctionHelper( - static_cast< - typename Traits::template QualifiedPointer<FunctorPtrExecutor>>( - self) - ->functorPtr_.get(), - SelectFunctionTag()); - } - - std::pair<std::type_info const&, void*> target() const noexcept { - return {typeid(F), const_cast<F*>(functorPtr_.get())}; - } - - template <typename DestinationExecutors> - void moveToImpl(typename DestinationExecutors::ExecutorIf* dest) noexcept { - new (dest) typename DestinationExecutors:: - template FunctorPtrExecutor<F, SelectFunctionTag>( - std::move(functorPtr_)); - this->~FunctorPtrExecutor(); - new (this) EmptyExecutor(); - } - - void moveTo( - typename NonConstFunctionExecutors::ExecutorIf* dest, - size_t /*size*/, - FunctionMoveCtor /*throws*/) noexcept { - moveToImpl<Executors<typename Traits::NonConstFunctionType>>(dest); - } - void moveTo( - typename ConstFunctionExecutors::ExecutorIf* dest, - size_t /*size*/, - FunctionMoveCtor /*throws*/) noexcept { - moveToImpl<Executors<typename Traits::ConstFunctionType>>(dest); - } - - private: - std::unique_ptr<F> functorPtr_; -}; - -// function::FunctorExecutor -template <typename FunctionType> -template <typename F, typename SelectFunctionTag> -class Executors<FunctionType>::FunctorExecutor final - : public Executors<FunctionType>::ExecutorIf { - public: - static constexpr bool kFunctorIsNTM = - std::is_nothrow_move_constructible<F>::value; - - FunctorExecutor(F&& f) - : ExecutorIf(&FunctorExecutor::template invokeFunctor<FunctorExecutor>), - functor_(std::move(f)) {} - FunctorExecutor(F const& f) - : ExecutorIf(&FunctorExecutor::template invokeFunctor<FunctorExecutor>), - functor_(f) {} - ~FunctorExecutor() {} - detail::function::AllocationStatus getAllocationStatus() const noexcept { - return detail::function::AllocationStatus::EMBEDDED; - } - - static auto getFunctor( - typename Traits::template QualifiedPointer<ExecutorIf> self) -> - typename SelectFunctionTag::template QualifiedPointer<F> { - return FunctorExecutor::selectFunctionHelper( - &static_cast< - typename Traits::template QualifiedPointer<FunctorExecutor>>(self) - ->functor_, - SelectFunctionTag()); - } - - std::pair<std::type_info const&, void*> target() const noexcept { - return {typeid(F), const_cast<F*>(&functor_)}; - } - - template <typename DestinationExecutors> - 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<F, SelectFunctionTag>(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<F, SelectFunctionTag>( - std::move(functor_)); - } - this->~FunctorExecutor(); - new (this) EmptyExecutor(); - } - void moveTo( - typename NonConstFunctionExecutors::ExecutorIf* dest, - size_t size, - FunctionMoveCtor throws) noexcept(kFunctorIsNTM) { - moveToImpl<Executors<typename Traits::NonConstFunctionType>>( - dest, size, throws); - } - void moveTo( - typename ConstFunctionExecutors::ExecutorIf* dest, - size_t size, - FunctionMoveCtor throws) noexcept(kFunctorIsNTM) { - moveToImpl<Executors<typename Traits::ConstFunctionType>>( - dest, size, throws); - } - - private: - F functor_; -}; -} // namespace function -} // namespace detail - -// --------------------------------------------------------------------------- -// MOVE CONSTRUCTORS & MOVE ASSIGNMENT OPERATORS - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -Function<FunctionType, NTM, EmbedFunctorSize>::Function( - Function&& other) noexcept(hasNoExceptMoveCtor()) { - other.access<ExecutorIf>()->moveTo(access<ExecutorIf>(), kStorageSize, NTM); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -Function<FunctionType, NTM, EmbedFunctorSize>& -Function<FunctionType, NTM, EmbedFunctorSize>::operator=( - Function&& rhs) noexcept(hasNoExceptMoveCtor()) { - destroyExecutor(); - SCOPE_FAIL { - initializeEmptyExecutor(); - }; - rhs.access<ExecutorIf>()->moveTo(access<ExecutorIf>(), kStorageSize, NTM); - return *this; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template < - typename OtherFunctionType, - FunctionMoveCtor OtherNTM, - size_t OtherEmbedFunctorSize> -Function<FunctionType, NTM, EmbedFunctorSize>::Function( - Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other, - typename std::enable_if<std::is_same< - typename Traits::NonConstFunctionType, - typename detail::function::FunctionTypeTraits< - OtherFunctionType>::NonConstFunctionType>::value>:: - type*) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && - EmbedFunctorSize >= OtherEmbedFunctorSize) { - using OtherFunction = - Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>; - - static_assert( - !Traits::IsConst::value || OtherFunction::Traits::IsConst::value, - "Function: cannot move Function<R(Args...)> into " - "Function<R(Args...) const>; " - "use folly::constCastFunction!"); - - other.template access<typename OtherFunction::ExecutorIf>()->moveTo( - access<ExecutorIf>(), kStorageSize, NTM); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template < - typename OtherFunctionType, - FunctionMoveCtor OtherNTM, - size_t OtherEmbedFunctorSize> -Function<FunctionType, NTM, EmbedFunctorSize>& -Function<FunctionType, NTM, EmbedFunctorSize>::operator=( - Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& - rhs) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW) { - using OtherFunction = - Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>; - - 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<R(Args...)> into " - "Function<R(Args...) const>; " - "use folly::constCastFunction!"); - - destroyExecutor(); - SCOPE_FAIL { - initializeEmptyExecutor(); - }; - rhs.template access<typename OtherFunction::ExecutorIf>()->moveTo( - access<ExecutorIf>(), kStorageSize, NTM); - return *this; -} - -// --------------------------------------------------------------------------- -// PUBLIC METHODS - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize> -inline void Function<FunctionType, NTM, EmbedFunctorSize>:: - swap(Function<FunctionType, OtherNTM, OtherEmbedFunctorSize>& o) noexcept( - hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW) { - Function<FunctionType, NTM, EmbedFunctorSize> tmp(std::move(*this)); - *this = std::move(o); - o = std::move(tmp); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -Function<FunctionType, NTM, EmbedFunctorSize>::operator bool() const noexcept { - return access<ExecutorIf>()->getAllocationStatus() != - detail::function::AllocationStatus::EMPTY; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline bool Function<FunctionType, NTM, EmbedFunctorSize>::hasAllocatedMemory() - const noexcept { - return access<ExecutorIf>()->getAllocationStatus() == - detail::function::AllocationStatus::ALLOCATED; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline std::type_info const& -Function<FunctionType, NTM, EmbedFunctorSize>::target_type() const noexcept { - return access<ExecutorIf>()->target().first; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <typename T> -T* Function<FunctionType, NTM, EmbedFunctorSize>::target() noexcept { - auto type_target_pair = access<ExecutorIf>()->target(); - if (type_target_pair.first == typeid(T)) { - return static_cast<T*>(type_target_pair.second); - } - return nullptr; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <typename T> -T const* Function<FunctionType, NTM, EmbedFunctorSize>::target() const - noexcept { - auto type_target_pair = access<ExecutorIf>()->target(); - if (type_target_pair.first == typeid(T)) { - return static_cast<T const*>(type_target_pair.second); - } - return nullptr; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> - Function< - typename detail::function::FunctionTypeTraits< - FunctionType>::ConstFunctionType, - NTM, - EmbedFunctorSize> - Function<FunctionType, NTM, EmbedFunctorSize>::castToConstFunction() && - noexcept(hasNoExceptMoveCtor()) { - using ReturnType = - Function<typename Traits::ConstFunctionType, NTM, EmbedFunctorSize>; - - ReturnType result; - result.destroyExecutor(); - SCOPE_FAIL { - result.initializeEmptyExecutor(); - }; - access<ExecutorIf>()->moveTo( - result.template access<typename ReturnType::ExecutorIf>(), - kStorageSize, - NTM); - return result; -} - -// --------------------------------------------------------------------------- -// PRIVATE METHODS - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <typename T> -T* Function<FunctionType, NTM, EmbedFunctorSize>::access() { - static_assert( - std::is_base_of<ExecutorIf, T>::value, - "Function::access<T>: 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<T*>(&data_); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <typename T> -T const* Function<FunctionType, NTM, EmbedFunctorSize>::access() const { - static_assert( - std::is_base_of<ExecutorIf, T>::value, - "Function::access<T>: 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<T const*>(&data_); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -void Function<FunctionType, NTM, EmbedFunctorSize>:: - initializeEmptyExecutor() noexcept { - new (access<EmptyExecutor>()) EmptyExecutor; -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -template <typename F> -void Function<FunctionType, NTM, EmbedFunctorSize>:: - createExecutor(F&& f) noexcept( - noexcept(typename std::decay<F>::type(std::forward<F>(f)))) { - using ValueType = typename std::decay<F>::type; - static constexpr bool kFunctorIsNTM = - std::is_nothrow_move_constructible<ValueType>::value; - using ExecutorType = typename std::conditional< - (sizeof(FunctorExecutor< - ValueType, - typename Traits::DefaultSelectFunctionTag>) > kStorageSize || - (hasNoExceptMoveCtor() && !kFunctorIsNTM)), - FunctorPtrExecutor<ValueType, typename Traits::DefaultSelectFunctionTag>, - FunctorExecutor<ValueType, typename Traits::DefaultSelectFunctionTag>>:: - type; - new (access<ExecutorType>()) ExecutorType(std::forward<F>(f)); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -void Function<FunctionType, NTM, EmbedFunctorSize>::destroyExecutor() noexcept { - access<ExecutorIf>()->~ExecutorIf(); -} - -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -struct Function<FunctionType, NTM, EmbedFunctorSize>::MinStorageSize { - using NotEmbeddedFunctor = - FunctorPtrExecutor<void(void), detail::function::SelectConstFunctionTag>; - - 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 <memory> - -namespace folly { - -namespace detail { -namespace function { - -struct SelectConstFunctionTag { - template <typename T> - using QualifiedPointer = T const*; -}; -struct SelectNonConstFunctionTag { - template <typename T> - 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 <typename CallableR, typename FollyFunctionR> -using ReturnTypeMatches = std::integral_constant< - bool, - std::is_convertible<CallableR, FollyFunctionR>::value || - std::is_same<typename std::decay<FollyFunctionR>::type, void>::value>; - -// Helper class to extract properties from a function type -template <typename T> -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 <typename T> -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 <typename X> - class InvokeOperator {}; - class ExecutorMixin {}; -}; - -// FunctionTypeTraits for non-const function types -template <typename R, typename... Args> -struct FunctionTypeTraits<R(Args...)> { - using SuitableForFunction = std::true_type; - using ResultType = R; - using ArgsTuple = std::tuple<Args...>; - using ArgsRefTuple = std::tuple<Args&&...>; - using NonConstFunctionType = R(Args...); - using ConstFunctionType = R(Args...) const; - using IsConst = std::false_type; - using DefaultSelectFunctionTag = SelectNonConstFunctionTag; - template <typename F> - using IsCallable = - ReturnTypeMatches<typename std::result_of<F&(Args...)>::type, R>; - template <typename T> - using QualifiedPointer = T*; - template <typename Obj> - using InvokeFunctionPtr = R (*)(Obj*, Args&&...); - - // Function inherits from InvokeOperator<Function>. This is - // where Function's operator() is defined. - template <typename FunctionType> - 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<FunctionType*>(this) - ->template access<typename FunctionType::ExecutorIf>(); - return executor->invokePtr(executor, std::forward<Args>(args)...); - } - }; - - class ExecutorMixin; -}; - -// FunctionTypeTraits for const function types -template <typename R, typename... Args> -struct FunctionTypeTraits<R(Args...) const> { - using SuitableForFunction = std::true_type; - using ResultType = R; - using ArgsTuple = std::tuple<Args...>; - using ArgsRefTuple = std::tuple<Args&&...>; - using NonConstFunctionType = R(Args...); - using ConstFunctionType = R(Args...) const; - using IsConst = std::true_type; - using DefaultSelectFunctionTag = SelectConstFunctionTag; - template <typename F> - using IsCallable = - ReturnTypeMatches<typename std::result_of<F const&(Args...)>::type, R>; - template <typename T> - using QualifiedPointer = T const*; - template <typename Obj> - using InvokeFunctionPtr = R (*)(Obj const*, Args&&...); - - // Function inherits from InvokeOperator<Function>. This is - // where Function's operator() is defined. - template <typename FunctionType> - 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<FunctionType const*>(this) - ->template access<typename FunctionType::ExecutorIf>(); - return executor->invokePtr(executor, std::forward<Args>(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 <typename T, typename OtherFunctionType> -struct IsFunction : std::false_type {}; - -template < - typename FunctionType, - FunctionMoveCtor NTM, - size_t EmbedFunctorSize, - typename OtherFunctionType> -struct IsFunction< - ::folly::Function<FunctionType, NTM, EmbedFunctorSize>, - OtherFunctionType> - : std::is_same< - typename FunctionTypeTraits<FunctionType>::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 <typename FunctionType> -struct IsCallableHelper { - using Traits = FunctionTypeTraits<FunctionType>; - - template <typename F> - static std::integral_constant<bool, Traits::template IsCallable<F>::value> - test(int); - template <typename F> - static std::false_type test(...); -}; - -template <typename F, typename FunctionType> -struct IsCallable - : public std::integral_constant< - bool, - (!IsFunction<typename std::decay<F>::type, FunctionType>::value && - decltype(IsCallableHelper<FunctionType>::template test< - typename std::decay<F>::type>(0))::value)> {}; - -// MaybeUnaryOrBinaryFunction: helper template class for deriving -// Function from std::unary_function or std::binary_function -template <typename R, typename ArgsTuple> -struct MaybeUnaryOrBinaryFunctionImpl { - using result_type = R; -}; - -template <typename R, typename Arg> -struct MaybeUnaryOrBinaryFunctionImpl<R, std::tuple<Arg>> - : public std::unary_function<Arg, R> {}; - -template <typename R, typename Arg1, typename Arg2> -struct MaybeUnaryOrBinaryFunctionImpl<R, std::tuple<Arg1, Arg2>> - : public std::binary_function<Arg1, Arg2, R> {}; - -template <typename FunctionType> -using MaybeUnaryOrBinaryFunction = MaybeUnaryOrBinaryFunctionImpl< - typename FunctionTypeTraits<FunctionType>::ResultType, - typename FunctionTypeTraits<FunctionType>::ArgsTuple>; - -// Invoke helper -template <typename F, typename... Args> -inline auto invoke(F&& f, Args&&... args) - -> decltype(std::forward<F>(f)(std::forward<Args>(args)...)) { - return std::forward<F>(f)(std::forward<Args>(args)...); -} - -template <typename M, typename C, typename... Args> -inline auto invoke(M(C::*d), Args&&... args) - -> decltype(std::mem_fn(d)(std::forward<Args>(args)...)) { - return std::mem_fn(d)(std::forward<Args>(args)...); -} - -// Executors helper class -template <typename FunctionType> -struct Executors { - class ExecutorIf; - class EmptyExecutor; - template <class F, class SelectFunctionTag> - class FunctorPtrExecutor; - template <class F, class SelectFunctionTag> - class FunctorExecutor; - - using Traits = FunctionTypeTraits<FunctionType>; - using NonConstFunctionExecutors = - Executors<typename Traits::NonConstFunctionType>; - using ConstFunctionExecutors = Executors<typename Traits::ConstFunctionType>; - using InvokeFunctionPtr = typename Traits::template InvokeFunctionPtr< - Executors<FunctionType>::ExecutorIf>; -}; - -template <typename R, typename... Args> -class FunctionTypeTraits<R(Args...)>::ExecutorMixin { - public: - using ExecutorIf = typename Executors<R(Args...)>::ExecutorIf; - using InvokeFunctionPtr = typename Executors<R(Args...)>::InvokeFunctionPtr; - - ExecutorMixin(InvokeFunctionPtr invoke_ptr) : invokePtr(invoke_ptr) {} - virtual ~ExecutorMixin() {} - - template <typename F> - static F* selectFunctionHelper(F* f, SelectNonConstFunctionTag) { - return f; - } - - template <typename F> - static F const* selectFunctionHelper(F* f, SelectConstFunctionTag) { - return f; - } - - static R invokeEmpty(ExecutorIf*, Args&&...) { - throw std::bad_function_call(); - } - - template <typename Ex> - static R invokeFunctor(ExecutorIf* executor, Args&&... args) { - return static_cast<R>(folly::detail::function::invoke( - *Ex::getFunctor(executor), std::forward<Args>(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 <typename R, typename... Args> -class FunctionTypeTraits<R(Args...) const>::ExecutorMixin { - public: - using ExecutorIf = typename Executors<R(Args...) const>::ExecutorIf; - using InvokeFunctionPtr = - typename Executors<R(Args...) const>::InvokeFunctionPtr; - - ExecutorMixin(InvokeFunctionPtr invoke_ptr) : invokePtr(invoke_ptr) {} - virtual ~ExecutorMixin() {} - - template <typename F> - static F* selectFunctionHelper(F const* f, SelectNonConstFunctionTag) { - return const_cast<F*>(f); - } - - template <typename F> - static F const* selectFunctionHelper(F const* f, SelectConstFunctionTag) { - return f; - } - - static R invokeEmpty(ExecutorIf const*, Args&&...) { - throw std::bad_function_call(); - } - - template <typename Ex> - static R invokeFunctor(ExecutorIf const* executor, Args&&... args) { - return static_cast<R>(folly::detail::function::invoke( - *Ex::getFunctor(executor), std::forward<Args>(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 Function> -struct InvokeFromSharedPtr final { - std::shared_ptr<Function> ptr_; - - explicit InvokeFromSharedPtr(std::shared_ptr<Function> ptr) - : ptr_(std::move(ptr)) {} - - template <typename... Args> - auto operator()(Args&&... args) - -> decltype((*ptr_)(std::forward<Args>(args)...)) { - return (*ptr_)(std::forward<Args>(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 <functional> +#include <memory> +#include <new> #include <type_traits> -#include <typeinfo> #include <utility> -#include <folly/ScopeGuard.h> -#include <folly/portability/Constexpr.h> +#include <folly/CppAttributes.h> 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<void(void)>)> +namespace impl { +template <typename FunctionType, bool Const = false> class Function; -} // folly +template <typename ReturnType, typename... Args> +Function<ReturnType(Args...), true> constCastFunction( + Function<ReturnType(Args...), false>&&) noexcept; +} -// boring predeclarations and details -#include "Function-pre.h" +namespace detail { +namespace function { -namespace folly { +enum class Op { MOVE, NUKE, FULL, HEAP }; -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -class Function final - : public detail::function::FunctionTypeTraits<FunctionType>:: - template InvokeOperator< - Function<FunctionType, NTM, EmbedFunctorSize>>, - public detail::function::MaybeUnaryOrBinaryFunction<FunctionType> { - private: - using Traits = detail::function::FunctionTypeTraits<FunctionType>; - static_assert( - Traits::SuitableForFunction::value, - "Function<FunctionType>: FunctionType must be of the " - "form 'R(Args...)' or 'R(Args...) const'"); - - using ThisType = Function<FunctionType, NTM, EmbedFunctorSize>; - using InvokeOperator = typename Traits::template InvokeOperator<ThisType>; - - 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 <bool If, typename T> +using ConstIf = typename std::conditional<If, const T, T>::type; + +template <typename Fun, typename FunT = typename std::decay<Fun>::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<FunT>::value +#else + // Same as is_nothrow_move_constructible, but w/ no template instantiation. + noexcept(FunT(std::declval<FunT&&>())) +#endif + )>; + +template <typename T> +bool isNullPtrFn(T* p) { + return p == nullptr; +} +template <typename T> +std::false_type isNullPtrFn(T&&) { + return {}; +} - /** - * Default constructor. Constructs an empty Function. - */ - Function() noexcept { - initializeEmptyExecutor(); - } +template <typename ReturnType, typename... Args> +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 <typename ReturnType, typename... Args, bool Const> +class Function<ReturnType(Args...), Const> 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 <typename T> + using ConstIf = detail::function::ConstIf<Const, T>; + template <typename Fun> + using IsSmall = detail::function::IsSmall<Fun>; - /** - * 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<typename Traits::NonConstFunctionType> asStdFunction() && { - return detail::function::InvokeFromSharedPtr<Function>( - std::make_shared<Function>(std::move(*this))); + Data data_; + Call call_; + Exec exec_; + + friend Function<ReturnType(Args...), true> constCastFunction<>( + Function<ReturnType(Args...), false>&&) noexcept; + friend class Function<ReturnType(Args...), !Const>; + + template <typename Fun, typename FunT = typename std::decay<Fun>::type> + Function( + Fun&& fun, + typename std::enable_if<IsSmall<Fun>::value, Tag>:: + type) noexcept(noexcept(FunT(std::declval<Fun>()))) + : Function() { + struct Ops { + static ReturnType call(Data& p, Args&&... args) { + return static_cast<ReturnType>((*static_cast<ConstIf<FunT>*>( + (void*)&p.small))(static_cast<Args&&>(args)...)); + } + static bool exec(Op o, Data* src, Data* dst) { + switch (o) { + case Op::MOVE: + ::new ((void*)&dst->small) + FunT(std::move(*static_cast<FunT*>((void*)&src->small))); + FOLLY_FALLTHROUGH; + case Op::NUKE: + static_cast<FunT*>((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&&>(fun)); + exec_ = &Ops::exec; + call_ = &Ops::call; + } + } + + template <typename Fun, typename FunT = typename std::decay<Fun>::type> + Function(Fun&& fun, typename std::enable_if<!IsSmall<Fun>::value, Tag>::type) + : Function() { + struct Ops { + static ReturnType call(Data& p, Args&&... args) { + return static_cast<ReturnType>((*static_cast<ConstIf<FunT>*>(p.big))( + static_cast<Args&&>(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<FunT*>(src->big); + break; + case Op::FULL: + case Op::HEAP: + break; + } + return true; + } + }; + data_.big = new FunT(static_cast<Fun&&>(fun)); + call_ = &Ops::call; + exec_ = &Ops::exec; } + template <typename F, typename G = typename std::decay<F>::type> + using ResultOf = decltype(static_cast<ReturnType>( + std::declval<ConstIf<G>&>()(std::declval<Args>()...))); + 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<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other, - typename std::enable_if<std::is_same< - typename Traits::NonConstFunctionType, - typename detail::function::FunctionTypeTraits< - OtherFunctionType>::NonConstFunctionType>::value>::type* = - 0) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && - EmbedFunctorSize >= OtherEmbedFunctorSize); + Function() noexcept + : call_(&detail::function::uninitCall<ReturnType, Args...>), + 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 <typename Fun> 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<RhsFunctionType, RhsNTM, RhsEmbedFunctorSize>&& - 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<Fun>` prevents this overload from being + * selected by overload resolution when `fun` is not a compatible function. */ - template <typename F> - /* implicit */ Function( - F&& f, - typename std::enable_if< - detail::function::IsCallable<F, FunctionType>::value>::type* = - 0) noexcept(noexcept(typename std::decay<F>:: - type(std::forward<F>(f)))) { - createExecutor(std::forward<F>(f)); - } + template <class Fun, typename = ResultOf<Fun>> + /* implicit */ Function(Fun&& fun) noexcept( + noexcept(Function(std::declval<Fun>(), Tag{}))) + : Function(static_cast<Fun&&>(fun), Tag{}) {} /** - * Assigns a callable object to this `Function`. + * For moving a `Function<X(Ys..) const>` into a `Function<X(Ys...)>`. */ - template <typename F> - typename std::enable_if< - detail::function::IsCallable<F, FunctionType>::value, - Function&>::type - operator=(F&& f) noexcept( - noexcept(typename std::decay<F>::type(std::forward<F>(f)))) { - destroyExecutor(); - SCOPE_FAIL { - initializeEmptyExecutor(); - }; - createExecutor(std::forward<F>(f)); - return *this; + template < + bool OtherConst, + typename std::enable_if<!Const && OtherConst, int>::type = 0> + Function(Function<ReturnType(Args...), OtherConst>&& 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 <FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize> - void - swap(Function<FunctionType, OtherNTM, OtherEmbedFunctorSize>& 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<Fun>` prevents this overload from being + * selected by overload resolution when `fun` is not a compatible function. */ - bool hasAllocatedMemory() const noexcept; + template <class Fun, typename = ResultOf<Fun>> + Function& operator=(Fun&& fun) noexcept( + noexcept(/* implicit */ Function(std::declval<Fun>()))) { + // Doing this in place is more efficient when we can do so safely. + if (noexcept(/* implicit */ Function(std::declval<Fun>()))) { + // 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&&>(fun)); + } else { + // Construct a temporary and (nothrow) swap. + Function(static_cast<Fun&&>(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 <typename T> - T* target() noexcept; + template <typename Member, typename Class> + 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 <typename T> - 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<True && !Const, int>::type = 0> + ReturnType operator()(Args... args) { + return call_(data_, static_cast<Args&&>(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<typename Traits::ConstFunctionType, NTM, EmbedFunctorSize> - castToConstFunction() && noexcept(hasNoExceptMoveCtor()); - - using SignatureType = FunctionType; - using ResultType = typename Traits::ResultType; - using ArgsTuple = typename Traits::ArgsTuple; - - private: - template <class, FunctionMoveCtor, size_t> - friend class Function; - - friend struct detail::function::FunctionTypeTraits<FunctionType>; - - using ExecutorIf = - typename detail::function::Executors<FunctionType>::ExecutorIf; - using EmptyExecutor = - typename detail::function::Executors<FunctionType>::EmptyExecutor; - template <typename F, typename SelectFunctionTag> - using FunctorPtrExecutor = typename detail::function::Executors< - FunctionType>::template FunctorPtrExecutor<F, SelectFunctionTag>; - template <typename F, typename SelectFunctionTag> - using FunctorExecutor = typename detail::function::Executors< - FunctionType>::template FunctorExecutor<F, SelectFunctionTag>; - - template <typename T> - T const* access() const; - - template <typename T> - 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<True && Const, int>::type = 0> + ReturnType operator()(Args... args) const { + return call_(const_cast<Data&>(data_), static_cast<Args&&>(args)...); + } - template <typename F> - void createExecutor(F&& f) noexcept( - noexcept(typename std::decay<F>::type(std::forward<F>(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<MinStorageSize::value>::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<ReturnType(Args...)> asStdFunction() && { + struct Impl { + std::shared_ptr<Function> sp_; + ReturnType operator()(Args&&... args) const { + return (*sp_)(static_cast<Args&&>(args)...); + } + }; + return Impl{std::make_shared<Function>(std::move(*this))}; + } }; -// operator== -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline bool operator==( - Function<FunctionType, NTM, EmbedFunctorSize> const& f, - std::nullptr_t) noexcept { - return !f; +template <typename FunctionType, bool Const> +void swap( + Function<FunctionType, Const>& lhs, + Function<FunctionType, Const>& rhs) noexcept { + lhs.swap(rhs); } -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline bool operator==( - std::nullptr_t, - Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept { - return !f; +template <typename FunctionType, bool Const> +bool operator==(const Function<FunctionType, Const>& fn, std::nullptr_t) { + return !fn; } -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline bool operator!=( - Function<FunctionType, NTM, EmbedFunctorSize> const& f, - std::nullptr_t) noexcept { - return !!f; +template <typename FunctionType, bool Const> +bool operator==(std::nullptr_t, const Function<FunctionType, Const>& fn) { + return !fn; } -template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -inline bool operator!=( - std::nullptr_t, - Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept { - return !!f; +template <typename FunctionType, bool Const> +bool operator!=(const Function<FunctionType, Const>& 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 <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> -Function< - typename detail::function::FunctionTypeTraits< - FunctionType>::ConstFunctionType, - NTM, - EmbedFunctorSize> -constCastFunction(Function<FunctionType, NTM, EmbedFunctorSize>&& - from) noexcept(NTM == FunctionMoveCtor::NO_THROW) { - return std::move(from).castToConstFunction(); +template <typename FunctionType, bool Const> +bool operator!=(std::nullptr_t, const Function<FunctionType, Const>& fn) { + return !(nullptr == fn); } -template <typename FunctionType, FunctionMoveCtor NOM, size_t S> -void swap( - Function<FunctionType, NOM, S>& lhs, - Function<FunctionType, NOM, S>& rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); +template <typename ReturnType, typename... Args> +Function<ReturnType(Args...), true> constCastFunction( + Function<ReturnType(Args...), false>&& that) noexcept { + Function<ReturnType(Args...), true> 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<FunctionType, NOM1, S1>& lhs, - Function<FunctionType, NOM2, S2>& rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); +template <typename FunctionType> +Function<FunctionType, true> constCastFunction( + Function<FunctionType, true>&& that) noexcept { + return std::move(that); } -} // namespace folly +template <typename FunctionType> +struct MakeFunction {}; + +template <typename ReturnType, typename... Args> +struct MakeFunction<ReturnType(Args...)> { + using type = Function<ReturnType(Args...), false>; +}; -#include "Function-inl.h" +template <typename ReturnType, typename... Args> +struct MakeFunction<ReturnType(Args...) const> { + using type = Function<ReturnType(Args...), true>; +}; +} // namespace impl + +/* using override */ using impl::constCastFunction; + +template <typename FunctionType> +using Function = typename impl::MakeFunction<FunctionType>::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<T>) == size(Core<U>). // See Core::convert for details. - folly::Function< - void(Try<T>&&), - folly::FunctionMoveCtor::MAY_THROW, - 8 * sizeof(void*)> - callback_; + folly::Function<void(Try<T>&&)> callback_; // place result_ next to increase the likelihood that the value will be // contained entirely in one cache line folly::Optional<Try<T>> 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 <folly/Memory.h> #include <gtest/gtest.h> -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<decltype(callable_noexcept)>::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<decltype(callable_throw)>::value); - - // callable_noexcept can be stored in the Function object - Function<int(int), FunctionMoveCtor::NO_THROW> func(callable_noexcept); - EXPECT_EQ(43, func(42)); - EXPECT_FALSE(func.hasAllocatedMemory()); - EXPECT_TRUE(std::is_nothrow_move_constructible<decltype(func)>::value); - - // callable_throw cannot be stored in the Function object, - // because Function guarantees noexcept-movability, but - // callable_throw may throw when moved - Function<int(int), FunctionMoveCtor::NO_THROW> 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<decltype(func_safe_move)>::value); - - // callable_throw can be stored in the Function object when - // the NoExceptMovable template parameter is set to NO - Function<int(int), FunctionMoveCtor::MAY_THROW> func_movethrows( - callable_throw); - EXPECT_EQ(52, func_movethrows(42)); - EXPECT_FALSE(func_movethrows.hasAllocatedMemory()); - EXPECT_FALSE( - std::is_nothrow_move_constructible<decltype(func_movethrows)>::value); -} +} // namespace // TEST ===================================================================== // InvokeFunctor & InvokeReference -template <FunctionMoveCtor NEM, size_t S> -void invoke_functor_test() { +TEST(Function, InvokeFunctor) { Functor<int, 100> func; + static_assert( + sizeof(func) > sizeof(Function<int(size_t)>), + "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<int(size_t) const, NEM, sizeof(func)* S / 2> 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<int(size_t) const> 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<FunctionMoveCtor::MAY_THROW, 0>(); -} -TEST(Function, InvokeFunctor_N0) { - invoke_functor_test<FunctionMoveCtor::NO_THROW, 0>(); -} -TEST(Function, InvokeFunctor_T1) { - invoke_functor_test<FunctionMoveCtor::MAY_THROW, 1>(); -} -TEST(Function, InvokeFunctor_N1) { - invoke_functor_test<FunctionMoveCtor::NO_THROW, 1>(); -} -TEST(Function, InvokeFunctor_T2) { - invoke_functor_test<FunctionMoveCtor::MAY_THROW, 2>(); -} -TEST(Function, InvokeFunctor_N2) { - invoke_functor_test<FunctionMoveCtor::NO_THROW, 2>(); -} -TEST(Function, InvokeFunctor_T3) { - invoke_functor_test<FunctionMoveCtor::MAY_THROW, 3>(); -} -TEST(Function, InvokeFunctor_N3) { - invoke_functor_test<FunctionMoveCtor::NO_THROW, 3>(); -} -template <FunctionMoveCtor NEM> -void invoke_reference_test() { +TEST(Function, InvokeReference) { Functor<int, 10> func; func(5, 123); - // Have Functions for getter and setter, both referencing the - // same funtor - Function<int(size_t) const, NEM, 0> getter = std::ref(func); - Function<int(size_t, int), NEM, 0> setter = std::ref(func); + // Have Functions for getter and setter, both referencing the same funtor + Function<int(size_t) const> getter = std::ref(func); + Function<int(size_t, int)> 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<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, InvokeReference_N) { - invoke_reference_test<FunctionMoveCtor::NO_THROW>(); -} // TEST ===================================================================== // Emptiness -template <FunctionMoveCtor NEM> -void emptiness_test() { - Function<int(int), NEM> f; +TEST(Function, Emptiness_T) { + Function<int(int)> f; EXPECT_EQ(f, nullptr); EXPECT_EQ(nullptr, f); EXPECT_FALSE(f); EXPECT_THROW(f(98), std::bad_function_call); - Function<int(int), NEM> g([](int x) { return x + 1; }); + Function<int(int)> g([](int x) { return x + 1; }); EXPECT_NE(g, nullptr); EXPECT_NE(nullptr, g); EXPECT_TRUE(g); EXPECT_EQ(100, g(99)); - Function<int(int), NEM> h(&func_int_int_add_25); + Function<int(int)> 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<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, Emptiness_N) { - emptiness_test<FunctionMoveCtor::NO_THROW>(); -} - -// TEST ===================================================================== -// Types - -TEST(Function, Types) { - EXPECT_TRUE(( - !std::is_base_of<std::unary_function<int, int>, Function<int()>>::value)); - EXPECT_TRUE( - (!std::is_base_of<std::binary_function<int, int, int>, Function<int()>>:: - value)); - EXPECT_TRUE((std::is_same<Function<int()>::ResultType, int>::value)); - - EXPECT_TRUE(( - std::is_base_of<std::unary_function<int, double>, Function<double(int)>>:: - value)); - EXPECT_TRUE((!std::is_base_of< - std::binary_function<int, int, double>, - Function<double(int)>>::value)); - EXPECT_TRUE((std::is_same<Function<double(int)>::ResultType, double>::value)); - EXPECT_TRUE( - (std::is_same<Function<double(int)>::result_type, double>::value)); - EXPECT_TRUE((std::is_same<Function<double(int)>::argument_type, int>::value)); - - EXPECT_TRUE((!std::is_base_of< - std::unary_function<int, double>, - Function<double(int, char)>>::value)); - EXPECT_TRUE((std::is_base_of< - std::binary_function<int, char, double>, - Function<double(int, char)>>::value)); - EXPECT_TRUE( - (std::is_same<Function<double(int, char)>::ResultType, double>::value)); - EXPECT_TRUE( - (std::is_same<Function<double(int, char)>::result_type, double>::value)); - EXPECT_TRUE( - (std::is_same<Function<double(int, char)>::first_argument_type, int>:: - value)); - EXPECT_TRUE( - (std::is_same<Function<double(int, char)>::second_argument_type, char>:: - value)); -} - // TEST ===================================================================== // Swap -template <FunctionMoveCtor NEM1, FunctionMoveCtor NEM2, bool UseSwapMethod> +template <bool UseSwapMethod> void swap_test() { - Function<int(int), NEM1> mf1(func_int_int_add_25); - Function<int(int), NEM2> mf2(func_int_int_add_111); + Function<int(int)> mf1(func_int_int_add_25); + Function<int(int)> 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<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::MAY_THROW, true>(); -} -TEST(Function, SwapMethod_TN) { - swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::NO_THROW, true>(); -} -TEST(Function, SwapMethod_NT) { - swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::MAY_THROW, true>(); +TEST(Function, SwapMethod) { + swap_test<true>(); } -TEST(Function, SwapMethod_NN) { - swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::NO_THROW, true>(); -} -TEST(Function, SwapFunction_TT) { - swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::MAY_THROW, false>(); -} -TEST(Function, SwapFunction_TN) { - swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::NO_THROW, false>(); -} -TEST(Function, SwapFunction_NT) { - swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::MAY_THROW, false>(); -} -TEST(Function, SwapFunction_NN) { - swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::NO_THROW, false>(); +TEST(Function, SwapFunction) { + swap_test<false>(); } // TEST ===================================================================== // Bind -template <FunctionMoveCtor NEM> -void bind_test() { - Function<float(float, float), NEM> fnc = floatMult; +TEST(Function, Bind) { + Function<float(float, float)> 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<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, Bind_N) { - bind_test<FunctionMoveCtor::NO_THROW>(); -} // TEST ===================================================================== // NonCopyableLambda -template <FunctionMoveCtor NEM, size_t S> -void non_copyable_lambda_test() { +TEST(Function, NonCopyableLambda) { auto unique_ptr_int = folly::make_unique<int>(900); EXPECT_EQ(900, *unique_ptr_int); @@ -376,184 +199,11 @@ void non_copyable_lambda_test() { EXPECT_EQ(901, functor()); - Function<int(void), NEM, sizeof(functor)* S / 2> func = std::move(functor); - EXPECT_EQ( - func.hasAllocatedMemory(), - S < 2 || (NEM == FunctionMoveCtor::NO_THROW && - !std::is_nothrow_move_constructible<decltype(functor)>::value)); + Function<int(void)> func = std::move(functor); + EXPECT_TRUE(func.hasAllocatedMemory()); EXPECT_EQ(902, func()); } -TEST(Function, NonCopyableLambda_T0) { - non_copyable_lambda_test<FunctionMoveCtor::MAY_THROW, 0>(); -} -TEST(Function, NonCopyableLambda_N0) { - non_copyable_lambda_test<FunctionMoveCtor::NO_THROW, 0>(); -} -TEST(Function, NonCopyableLambda_T1) { - non_copyable_lambda_test<FunctionMoveCtor::MAY_THROW, 1>(); -} -TEST(Function, NonCopyableLambda_N1) { - non_copyable_lambda_test<FunctionMoveCtor::NO_THROW, 1>(); -} -TEST(Function, NonCopyableLambda_T2) { - non_copyable_lambda_test<FunctionMoveCtor::MAY_THROW, 2>(); -} -TEST(Function, NonCopyableLambda_N2) { - non_copyable_lambda_test<FunctionMoveCtor::NO_THROW, 2>(); -} -TEST(Function, NonCopyableLambda_T3) { - non_copyable_lambda_test<FunctionMoveCtor::MAY_THROW, 3>(); -} -TEST(Function, NonCopyableLambda_N3) { - non_copyable_lambda_test<FunctionMoveCtor::NO_THROW, 3>(); -} - -// TEST ===================================================================== -// Downsize - -template <FunctionMoveCtor NEM> -void downsize_test() { - Functor<int, 10> 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<int(size_t, int), NEM, sizeof(functor)* 2> 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<int(size_t, int), NEM, sizeof(functor)> 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<int(size_t, int), NEM, 0> 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<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, Downsize_N) { - downsize_test<FunctionMoveCtor::NO_THROW>(); -} - -// TEST ===================================================================== -// Refcount - -template <FunctionMoveCtor NEM> -void refcount_test() { - Functor<int, 100> functor; - functor(3, 999); - auto shared_int = std::make_shared<int>(100); - - EXPECT_EQ(100, *shared_int); - EXPECT_EQ(1, shared_int.use_count()); - - Function<int(void), NEM> 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<int(void), NEM, sizeof(functor) / 2> 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<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, Refcount_N) { - refcount_test<FunctionMoveCtor::NO_THROW>(); -} - -// TEST ===================================================================== -// Target - -template <FunctionMoveCtor NEM> -void target_test() { - std::function<int(int)> func = [](int x) { return x + 25; }; - EXPECT_EQ(125, func(100)); - - Function<int(int), NEM> ufunc = std::move(func); - EXPECT_THROW(func(0), std::bad_function_call); - EXPECT_EQ(225, ufunc(200)); - - EXPECT_EQ(typeid(std::function<int(int)>), ufunc.target_type()); - - EXPECT_FALSE(ufunc.template target<int>()); - EXPECT_FALSE(ufunc.template target<std::function<void(void)>>()); - - std::function<int(int)>& ufunc_target = - *ufunc.template target<std::function<int(int)>>(); - - EXPECT_EQ(325, ufunc_target(300)); -} - -TEST(Function, Target_T) { - target_test<FunctionMoveCtor::MAY_THROW>(); -} -TEST(Function, Target_N) { - target_test<FunctionMoveCtor::NO_THROW>(); -} // TEST ===================================================================== // OverloadedFunctor @@ -885,149 +535,6 @@ TEST(Function, ParameterCopyMoveCount) { EXPECT_LE(cmt.copyCount(), 0); } -// TEST ===================================================================== -// CopyMoveThrows - -enum ExceptionType { COPY, MOVE }; - -template <ExceptionType ET> -class CopyMoveException : public std::runtime_error { - public: - using std::runtime_error::runtime_error; -}; - -template <bool CopyThrows, bool MoveThrows> -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<COPY>("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<MOVE>("CopyMoveThrowsCallable move"); - } - return *this; - } - - void operator()() const {} -}; - -TEST(Function, CopyMoveThrowsCallable) { - EXPECT_TRUE((std::is_nothrow_move_constructible< - CopyMoveThrowsCallable<false, false>>::value)); - EXPECT_TRUE((std::is_nothrow_move_constructible< - CopyMoveThrowsCallable<true, false>>::value)); - EXPECT_FALSE((std::is_nothrow_move_constructible< - CopyMoveThrowsCallable<false, true>>::value)); - EXPECT_FALSE((std::is_nothrow_move_constructible< - CopyMoveThrowsCallable<true, true>>::value)); - - EXPECT_TRUE((std::is_nothrow_copy_constructible< - CopyMoveThrowsCallable<false, false>>::value)); - EXPECT_FALSE((std::is_nothrow_copy_constructible< - CopyMoveThrowsCallable<true, false>>::value)); - EXPECT_TRUE((std::is_nothrow_copy_constructible< - CopyMoveThrowsCallable<false, true>>::value)); - EXPECT_FALSE((std::is_nothrow_copy_constructible< - CopyMoveThrowsCallable<true, true>>::value)); -} - -template <FunctionMoveCtor NEM, bool CopyThrows, bool MoveThrows> -void copy_and_move_throws_test() { - CopyMoveThrowsCallable<CopyThrows, MoveThrows> c; - Function<void(void), NEM> uf; - - if (CopyThrows) { - EXPECT_THROW((uf = c), CopyMoveException<COPY>); - } else { - EXPECT_NO_THROW((uf = c)); - } - - if (MoveThrows) { - EXPECT_THROW((uf = std::move(c)), CopyMoveException<MOVE>); - } else { - EXPECT_NO_THROW((uf = std::move(c))); - } - - c.allowMoveOperations = 1; - uf = std::move(c); - if (NEM == FunctionMoveCtor::MAY_THROW && MoveThrows) { - Function<void(void), NEM> uf2; - EXPECT_THROW((uf2 = std::move(uf)), CopyMoveException<MOVE>); - } else { - Function<void(void), NEM> uf2; - EXPECT_NO_THROW((uf2 = std::move(uf))); - } - - c.allowMoveOperations = 0; - c.allowCopyOperations = 1; - uf = c; - if (NEM == FunctionMoveCtor::MAY_THROW && MoveThrows) { - Function<void(void), NEM> uf2; - EXPECT_THROW((uf2 = std::move(uf)), CopyMoveException<MOVE>); - } else { - Function<void(void), NEM> uf2; - EXPECT_NO_THROW((uf2 = std::move(uf))); - } -} - -TEST(Function, CopyAndMoveThrows_TNN) { - copy_and_move_throws_test<FunctionMoveCtor::MAY_THROW, false, false>(); -} - -TEST(Function, CopyAndMoveThrows_NNN) { - copy_and_move_throws_test<FunctionMoveCtor::NO_THROW, false, false>(); -} - -TEST(Function, CopyAndMoveThrows_TTN) { - copy_and_move_throws_test<FunctionMoveCtor::MAY_THROW, true, false>(); -} - -TEST(Function, CopyAndMoveThrows_NTN) { - copy_and_move_throws_test<FunctionMoveCtor::NO_THROW, true, false>(); -} - -TEST(Function, CopyAndMoveThrows_TNT) { - copy_and_move_throws_test<FunctionMoveCtor::MAY_THROW, false, true>(); -} - -TEST(Function, CopyAndMoveThrows_NNT) { - copy_and_move_throws_test<FunctionMoveCtor::NO_THROW, false, true>(); -} - -TEST(Function, CopyAndMoveThrows_TTT) { - copy_and_move_throws_test<FunctionMoveCtor::MAY_THROW, true, true>(); -} - -TEST(Function, CopyAndMoveThrows_NTT) { - copy_and_move_throws_test<FunctionMoveCtor::NO_THROW, true, true>(); -} - // 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<void()> f = [&] { ++i; }; @@ -1302,3 +812,40 @@ TEST(Function, asStdFunction_args_const) { sf(42, 42); EXPECT_EQ(1, i); } + +TEST(Function, NoAllocatedMemoryAfterMove) { + Functor<int, 100> foo; + + Function<int(size_t)> func = foo; + EXPECT_TRUE(func.hasAllocatedMemory()); + + Function<int(size_t)> func2 = std::move(func); + EXPECT_TRUE(func2.hasAllocatedMemory()); + EXPECT_FALSE(func.hasAllocatedMemory()); +} + +TEST(Function, ConstCastEmbedded) { + int x = 0; + auto functor = [&x]() { ++x; }; + + Function<void() const> func(functor); + EXPECT_FALSE(func.hasAllocatedMemory()); + + Function<void()> func2(std::move(func)); + EXPECT_FALSE(func2.hasAllocatedMemory()); +} + +TEST(Function, EmptyAfterConstCast) { + Function<int(size_t)> func; + EXPECT_FALSE(func); + + Function<int(size_t) const> func2 = constCastFunction(std::move(func)); + EXPECT_FALSE(func2); +} + +TEST(Function, SelfMoveAssign) { + Function<int()> f = [] { return 0; }; + Function<int()>& g = f; + f = std::move(g); + EXPECT_TRUE(f); +}