Reimplement folly::Function to improve compile times.
authorEric Niebler <eniebler@fb.com>
Wed, 27 Apr 2016 17:02:00 +0000 (10:02 -0700)
committerFacebook Github Bot 2 <facebook-github-bot-2-bot@fb.com>
Wed, 27 Apr 2016 17:05:23 +0000 (10:05 -0700)
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

folly/Function-inl.h [deleted file]
folly/Function-pre.h [deleted file]
folly/Function.h
folly/Makefile.am
folly/futures/detail/Core.h
folly/test/FunctionTest.cpp

diff --git a/folly/Function-inl.h b/folly/Function-inl.h
deleted file mode 100644 (file)
index 7c34f92..0000000
+++ /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 (file)
index 5e91fbb..0000000
+++ /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
index 399efd54c17c2298f8296d30ba4ce432ad645c6c..c5f4a4834aad636197599c077a414880e939f53e 100644 (file)
@@ -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)
  */
 
 /**
  * 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;
  * 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;
+}
index 5eb27680e05cf1d0bac35758a122b3280061ab5c..f82d4898586a0f8dc6d0f5c051c6bb0d31d80b96 100644 (file)
@@ -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 \
index a41059009d48cf87d8b600e530141ad2a2bcfd98..b2fb18d82baa399c07d76d753c5c439499f53da7 100644 (file)
@@ -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_;
index 24a54d0dd7f3bd07cfb85452e8d6ada51e6601ff..7c5159c9ae0873f28ac1b8e5dd209fefbbb435a6 100644 (file)
@@ -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);
+}