Commit 5022d546 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot 2

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
parent 9383e1ca
/*
* 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
/*
* 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
/* /*
* Copyright 2016 Facebook, Inc. * 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
...@@ -12,6 +14,8 @@ ...@@ -12,6 +14,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
* Acknowledgements: Giuseppe Ottaviano (ott@fb.com)
*/ */
/** /**
...@@ -73,10 +77,9 @@ ...@@ -73,10 +77,9 @@
* wrapped function in a const context, you can wrap any functor that * wrapped function in a const context, you can wrap any functor that
* implements either or both of const and non-const `operator()`. * implements either or both of const and non-const `operator()`.
* *
* The first (and usually only specified) template parameter of * The template parameter of `folly::Function`, the `FunctionType`, can be
* `folly::Function`, the `FunctionType`, can be const-qualified. Be aware * const-qualified. Be aware that the const is part of the function signature.
* that the const is part of the function signature. It does not mean that * It does not mean that the function type is a const type.
* the function type is a const type.
* *
* using FunctionType = R(Args...); * using FunctionType = R(Args...);
* using ConstFunctionType = R(Args...) const; * using ConstFunctionType = R(Args...) const;
...@@ -210,153 +213,198 @@ ...@@ -210,153 +213,198 @@
* However, in that case what you do is potentially dangerous and requires * However, in that case what you do is potentially dangerous and requires
* the equivalent of a `const_cast`, hence you need to call * the equivalent of a `const_cast`, hence you need to call
* `constCastFunction`. * `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 #pragma once
#include <functional> #include <functional>
#include <memory>
#include <new>
#include <type_traits> #include <type_traits>
#include <typeinfo>
#include <utility> #include <utility>
#include <folly/ScopeGuard.h> #include <folly/CppAttributes.h>
#include <folly/portability/Constexpr.h>
namespace folly { namespace folly {
enum class FunctionMoveCtor { NO_THROW, MAY_THROW }; namespace impl {
template <typename FunctionType, bool Const = false>
template <
typename FunctionType,
FunctionMoveCtor NTM = FunctionMoveCtor::NO_THROW,
size_t EmbedFunctorSize = (NTM == FunctionMoveCtor::NO_THROW)
? sizeof(void (*)(void))
: sizeof(std::function<void(void)>)>
class Function; class Function;
} // folly template <typename ReturnType, typename... Args>
Function<ReturnType(Args...), true> constCastFunction(
Function<ReturnType(Args...), false>&&) noexcept;
}
// boring predeclarations and details namespace detail {
#include "Function-pre.h" namespace function {
namespace folly { enum class Op { MOVE, NUKE, FULL, HEAP };
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> union Data {
class Function final void* big;
: public detail::function::FunctionTypeTraits<FunctionType>:: typename std::aligned_storage<6 * sizeof(void*)>::type small;
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;
};
public: struct Tag {};
// not copyable
Function(Function const&) = delete; template <bool If, typename T>
Function& operator=(Function const&) = delete; 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 {};
}
/** template <typename ReturnType, typename... Args>
* Default constructor. Constructs an empty Function. ReturnType uninitCall(Data&, Args&&...) {
*/ throw std::bad_function_call();
Function() noexcept { }
initializeEmptyExecutor(); inline bool uninitNoop(Op, Data*, Data*) {
} return false;
}
} // namespace function
} // namespace detail
~Function() { namespace impl {
destroyExecutor();
static_assert( template <typename ReturnType, typename... Args, bool Const>
kStorageSize == sizeof(*this), class Function<ReturnType(Args...), Const> final {
"There is something wrong with the size of Function"); 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 template <typename T>
/** using ConstIf = detail::function::ConstIf<Const, T>;
* Move constructor template <typename Fun>
*/ using IsSmall = detail::function::IsSmall<Fun>;
Function(Function&& other) noexcept(hasNoExceptMoveCtor());
/**
* Move assignment operator
*/
Function& operator=(Function&& rhs) noexcept(hasNoExceptMoveCtor());
/** Data data_;
* Construct a std::function by moving in the contents of this `Function`. Call call_;
* Note that the returned std::function will share its state (i.e. captured Exec exec_;
* data) across all copies you make of it, so be very careful when copying.
*/ friend Function<ReturnType(Args...), true> constCastFunction<>(
std::function<typename Traits::NonConstFunctionType> asStdFunction() && { Function<ReturnType(Args...), false>&&) noexcept;
return detail::function::InvokeFromSharedPtr<Function>( friend class Function<ReturnType(Args...), !Const>;
std::make_shared<Function>(std::move(*this)));
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 * Default constructor. Constructs an empty Function.
* parameters with regards to const-ness, no-except-movability and internal
* storage size.
*/ */
template < Function() noexcept
typename OtherFunctionType, : call_(&detail::function::uninitCall<ReturnType, Args...>),
FunctionMoveCtor OtherNTM, exec_(&detail::function::uninitNoop) {}
size_t OtherEmbedFunctorSize>
Function( // not copyable
Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other, // NOTE: Deleting the non-const copy constructor is unusual but necessary to
typename std::enable_if<std::is_same< // prevent copies from non-const `Function` object from selecting the
typename Traits::NonConstFunctionType, // perfect forwarding implicit converting constructor below
typename detail::function::FunctionTypeTraits< // (i.e., `template <typename Fun> Function(Fun&&)`).
OtherFunctionType>::NonConstFunctionType>::value>::type* = Function(Function&) = delete;
0) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW && Function(const Function&) = delete;
EmbedFunctorSize >= OtherEmbedFunctorSize);
/** /**
* Moves a `Function` with different template parameters with regards * Move constructor
* to const-ness, no-except-movability and internal storage size into this
* one.
*/ */
template < Function(Function&& that) noexcept : Function() {
typename RhsFunctionType, that.exec_(Op::MOVE, &that.data_, &data_);
FunctionMoveCtor RhsNTM, std::swap(call_, that.call_);
size_t RhsEmbedFunctorSize> std::swap(exec_, that.exec_);
Function& operator=(Function<RhsFunctionType, RhsNTM, RhsEmbedFunctorSize>&& }
rhs) noexcept(RhsNTM == FunctionMoveCtor::NO_THROW);
/** /**
* Constructs an empty `Function`. * Constructs an empty `Function`.
*/ */
/* implicit */ Function(std::nullptr_t) noexcept : 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 * Constructs a new `Function` from any callable object. This
* handles function pointers, pointers to static member functions, * handles function pointers, pointers to static member functions,
...@@ -368,199 +416,238 @@ class Function final ...@@ -368,199 +416,238 @@ class Function final
* For a `Function` with a non-const function type, the object will * 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 * 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 * 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> template <class Fun, typename = ResultOf<Fun>>
/* implicit */ Function( /* implicit */ Function(Fun&& fun) noexcept(
F&& f, noexcept(Function(std::declval<Fun>(), Tag{})))
typename std::enable_if< : Function(static_cast<Fun&&>(fun), Tag{}) {}
detail::function::IsCallable<F, FunctionType>::value>::type* =
0) noexcept(noexcept(typename std::decay<F>::
type(std::forward<F>(f)))) {
createExecutor(std::forward<F>(f));
}
/** /**
* Assigns a callable object to this `Function`. * For moving a `Function<X(Ys..) const>` into a `Function<X(Ys...)>`.
*/ */
template <typename F> template <
typename std::enable_if< bool OtherConst,
detail::function::IsCallable<F, FunctionType>::value, typename std::enable_if<!Const && OtherConst, int>::type = 0>
Function&>::type Function(Function<ReturnType(Args...), OtherConst>&& that) noexcept
operator=(F&& f) noexcept( : Function() {
noexcept(typename std::decay<F>::type(std::forward<F>(f)))) { that.exec_(Op::MOVE, &that.data_, &data_);
destroyExecutor(); std::swap(call_, that.call_);
SCOPE_FAIL { std::swap(exec_, that.exec_);
initializeEmptyExecutor();
};
createExecutor(std::forward<F>(f));
return *this;
} }
/** /**
* Exchanges the callable objects of `*this` and `other`. `other` can be * If `ptr` is null, constructs an empty `Function`. Otherwise,
* a Function with different settings with regard to * this constructor is equivalent to `Function(std::mem_fn(ptr))`.
* no-except-movability and internal storage size, but must match
* `*this` with regards to return type and argument types.
*/ */
template <FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize> template <
void typename Member,
swap(Function<FunctionType, OtherNTM, OtherEmbedFunctorSize>& o) noexcept( typename Class,
hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW); // 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 * Move assignment operator
* non-empty.
*/ */
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 * Assigns a callable object to this `Function`. If the operation fails,
* heap. If `false` is returned, there has been no additional memory * `*this` is left unmodified.
* allocation and the callable is stored inside the `Function` *
* object itself. * \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 * Clears this `Function`.
* in this `Function`. Returns `typeid(void)` if empty.
*/ */
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 * If `ptr` is null, clears this `Function`. Otherwise, this assignment
* `nullptr` otherwise. * operator is equivalent to `*this = std::mem_fn(ptr)`.
*/ */
template <typename T> template <typename Member, typename Class>
T* target() noexcept; 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`, * Call the wrapped callable object with the specified arguments.
* and `nullptr` otherwise. * If this `Function` object is a const `folly::Function` object,
* this overload shall not participate in overload resolution.
*/ */
template <typename T> template <
const T* target() const noexcept; // `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. * Call the wrapped callable object with the specified arguments.
* * If this `Function` object is not a const `folly::Function` object,
* This is a potentially dangerous operation, equivalent to a `const_cast`. * this overload shall not participate in overload resolution.
* This converts a `Function` with a non-const function type, i.e.
* one that can only be called when in the form of a non-const reference,
* into one that can be called in a const context. Use at your own risk!
*/ */
Function<typename Traits::ConstFunctionType, NTM, EmbedFunctorSize> template <
castToConstFunction() && noexcept(hasNoExceptMoveCtor()); // `True` makes `operator()` a template so we can SFINAE on `Const`,
// which is non-deduced here.
using SignatureType = FunctionType; bool True = true,
using ResultType = typename Traits::ResultType; typename std::enable_if<True && Const, int>::type = 0>
using ArgsTuple = typename Traits::ArgsTuple; ReturnType operator()(Args... args) const {
return call_(const_cast<Data&>(data_), static_cast<Args&&>(args)...);
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 <typename F> /**
void createExecutor(F&& f) noexcept( * Exchanges the callable objects of `*this` and `that`.
noexcept(typename std::decay<F>::type(std::forward<F>(f)))); */
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, bool Const>
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> void swap(
inline bool operator==( Function<FunctionType, Const>& lhs,
Function<FunctionType, NTM, EmbedFunctorSize> const& f, Function<FunctionType, Const>& rhs) noexcept {
std::nullptr_t) noexcept { lhs.swap(rhs);
return !f;
} }
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> template <typename FunctionType, bool Const>
inline bool operator==( bool operator==(const Function<FunctionType, Const>& fn, std::nullptr_t) {
std::nullptr_t, return !fn;
Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept {
return !f;
} }
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> template <typename FunctionType, bool Const>
inline bool operator!=( bool operator==(std::nullptr_t, const Function<FunctionType, Const>& fn) {
Function<FunctionType, NTM, EmbedFunctorSize> const& f, return !fn;
std::nullptr_t) noexcept {
return !!f;
} }
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize> template <typename FunctionType, bool Const>
inline bool operator!=( bool operator!=(const Function<FunctionType, Const>& fn, std::nullptr_t) {
std::nullptr_t, return !(fn == nullptr);
Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept {
return !!f;
} }
/** template <typename FunctionType, bool Const>
* Cast a `Function` into one with a const function type. bool operator!=(std::nullptr_t, const Function<FunctionType, Const>& fn) {
* return !(nullptr == fn);
* 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, FunctionMoveCtor NOM, size_t S> template <typename ReturnType, typename... Args>
void swap( Function<ReturnType(Args...), true> constCastFunction(
Function<FunctionType, NOM, S>& lhs, Function<ReturnType(Args...), false>&& that) noexcept {
Function<FunctionType, NOM, S>& rhs) noexcept(noexcept(lhs.swap(rhs))) { Function<ReturnType(Args...), true> fn{};
lhs.swap(rhs); 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 < template <typename FunctionType>
typename FunctionType, Function<FunctionType, true> constCastFunction(
FunctionMoveCtor NOM1, Function<FunctionType, true>&& that) noexcept {
FunctionMoveCtor NOM2, return std::move(that);
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);
} }
} // 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;
}
...@@ -341,8 +341,6 @@ nobase_follyinclude_HEADERS = \ ...@@ -341,8 +341,6 @@ nobase_follyinclude_HEADERS = \
Traits.h \ Traits.h \
Unicode.h \ Unicode.h \
Function.h \ Function.h \
Function-inl.h \
Function-pre.h \
Uri.h \ Uri.h \
Uri-inl.h \ Uri-inl.h \
Varint.h \ Varint.h \
......
...@@ -374,11 +374,7 @@ class Core { ...@@ -374,11 +374,7 @@ class Core {
// sizeof(Core<T>) == size(Core<U>). // sizeof(Core<T>) == size(Core<U>).
// See Core::convert for details. // See Core::convert for details.
folly::Function< folly::Function<void(Try<T>&&)> callback_;
void(Try<T>&&),
folly::FunctionMoveCtor::MAY_THROW,
8 * sizeof(void*)>
callback_;
// place result_ next to increase the likelihood that the value will be // place result_ next to increase the likelihood that the value will be
// contained entirely in one cache line // contained entirely in one cache line
folly::Optional<Try<T>> result_; folly::Optional<Try<T>> result_;
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
#include <folly/Memory.h> #include <folly/Memory.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
using folly::FunctionMoveCtor;
using folly::Function; using folly::Function;
namespace { namespace {
...@@ -31,9 +30,6 @@ int func_int_int_add_25(int x) { ...@@ -31,9 +30,6 @@ int func_int_int_add_25(int x) {
int func_int_int_add_111(int x) { int func_int_int_add_111(int x) {
return x + 111; return x + 111;
} }
int func_int_return_987() {
return 987;
}
float floatMult(float a, float b) { float floatMult(float a, float b) {
return a * b; return a * b;
} }
...@@ -53,157 +49,57 @@ struct Functor { ...@@ -53,157 +49,57 @@ struct Functor {
return oldvalue; return oldvalue;
} }
}; };
} // namespace
// 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);
}
// TEST ===================================================================== // TEST =====================================================================
// InvokeFunctor & InvokeReference // InvokeFunctor & InvokeReference
template <FunctionMoveCtor NEM, size_t S> TEST(Function, InvokeFunctor) {
void invoke_functor_test() {
Functor<int, 100> func; Functor<int, 100> func;
static_assert(
sizeof(func) > sizeof(Function<int(size_t)>),
"sizeof(Function) is much larger than expected");
func(5, 123); func(5, 123);
// Try Functions with differently sized storage areas Function<int(size_t) const> getter = std::move(func);
// S=0: request storage for functors of size 0. The storage size
// will be actually larger, because there is a lower limit which // Function will allocate memory on the heap to store the functor object
// still allows to store at least pointers to functors on the heap. EXPECT_TRUE(getter.hasAllocatedMemory());
// 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);
EXPECT_EQ(123, getter(5)); 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> TEST(Function, InvokeReference) {
void invoke_reference_test() {
Functor<int, 10> func; Functor<int, 10> func;
func(5, 123); func(5, 123);
// Have Functions for getter and setter, both referencing the // Have Functions for getter and setter, both referencing the same funtor
// same funtor Function<int(size_t) const> getter = std::ref(func);
Function<int(size_t) const, NEM, 0> getter = std::ref(func); Function<int(size_t, int)> setter = std::ref(func);
Function<int(size_t, int), NEM, 0> setter = std::ref(func);
EXPECT_EQ(123, getter(5)); EXPECT_EQ(123, getter(5));
EXPECT_EQ(123, setter(5, 456)); EXPECT_EQ(123, setter(5, 456));
EXPECT_EQ(456, setter(5, 567)); EXPECT_EQ(456, setter(5, 567));
EXPECT_EQ(567, getter(5)); 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 ===================================================================== // TEST =====================================================================
// Emptiness // Emptiness
template <FunctionMoveCtor NEM> TEST(Function, Emptiness_T) {
void emptiness_test() { Function<int(int)> f;
Function<int(int), NEM> f;
EXPECT_EQ(f, nullptr); EXPECT_EQ(f, nullptr);
EXPECT_EQ(nullptr, f); EXPECT_EQ(nullptr, f);
EXPECT_FALSE(f); EXPECT_FALSE(f);
EXPECT_THROW(f(98), std::bad_function_call); 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(g, nullptr);
EXPECT_NE(nullptr, g); EXPECT_NE(nullptr, g);
EXPECT_TRUE(g); EXPECT_TRUE(g);
EXPECT_EQ(100, g(99)); 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(h, nullptr);
EXPECT_NE(nullptr, h); EXPECT_NE(nullptr, h);
EXPECT_TRUE(h); EXPECT_TRUE(h);
...@@ -216,60 +112,13 @@ void emptiness_test() { ...@@ -216,60 +112,13 @@ void emptiness_test() {
EXPECT_THROW(h(101), std::bad_function_call); 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 ===================================================================== // TEST =====================================================================
// Swap // Swap
template <FunctionMoveCtor NEM1, FunctionMoveCtor NEM2, bool UseSwapMethod> template <bool UseSwapMethod>
void swap_test() { void swap_test() {
Function<int(int), NEM1> mf1(func_int_int_add_25); Function<int(int)> mf1(func_int_int_add_25);
Function<int(int), NEM2> mf2(func_int_int_add_111); Function<int(int)> mf2(func_int_int_add_111);
EXPECT_EQ(125, mf1(100)); EXPECT_EQ(125, mf1(100));
EXPECT_EQ(211, mf2(100)); EXPECT_EQ(211, mf2(100));
...@@ -314,37 +163,18 @@ void swap_test() { ...@@ -314,37 +163,18 @@ void swap_test() {
EXPECT_EQ(nullptr, mf3); EXPECT_EQ(nullptr, mf3);
EXPECT_EQ(322, mf1(100)); EXPECT_EQ(322, mf1(100));
} }
TEST(Function, SwapMethod_TT) { TEST(Function, SwapMethod) {
swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::MAY_THROW, true>(); swap_test<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_NN) { TEST(Function, SwapFunction) {
swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::NO_THROW, true>(); swap_test<false>();
}
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 ===================================================================== // TEST =====================================================================
// Bind // Bind
template <FunctionMoveCtor NEM> TEST(Function, Bind) {
void bind_test() { Function<float(float, float)> fnc = floatMult;
Function<float(float, float), NEM> fnc = floatMult;
auto task = std::bind(std::move(fnc), 2.f, 4.f); auto task = std::bind(std::move(fnc), 2.f, 4.f);
EXPECT_THROW(fnc(0, 0), std::bad_function_call); EXPECT_THROW(fnc(0, 0), std::bad_function_call);
EXPECT_EQ(8, task()); EXPECT_EQ(8, task());
...@@ -352,18 +182,11 @@ void bind_test() { ...@@ -352,18 +182,11 @@ void bind_test() {
EXPECT_THROW(task(), std::bad_function_call); EXPECT_THROW(task(), std::bad_function_call);
EXPECT_EQ(8, task2()); EXPECT_EQ(8, task2());
} }
TEST(Function, Bind_T) {
bind_test<FunctionMoveCtor::MAY_THROW>();
}
TEST(Function, Bind_N) {
bind_test<FunctionMoveCtor::NO_THROW>();
}
// TEST ===================================================================== // TEST =====================================================================
// NonCopyableLambda // NonCopyableLambda
template <FunctionMoveCtor NEM, size_t S> TEST(Function, NonCopyableLambda) {
void non_copyable_lambda_test() {
auto unique_ptr_int = folly::make_unique<int>(900); auto unique_ptr_int = folly::make_unique<int>(900);
EXPECT_EQ(900, *unique_ptr_int); EXPECT_EQ(900, *unique_ptr_int);
...@@ -376,184 +199,11 @@ void non_copyable_lambda_test() { ...@@ -376,184 +199,11 @@ void non_copyable_lambda_test() {
EXPECT_EQ(901, functor()); EXPECT_EQ(901, functor());
Function<int(void), NEM, sizeof(functor)* S / 2> func = std::move(functor); Function<int(void)> func = std::move(functor);
EXPECT_EQ( EXPECT_TRUE(func.hasAllocatedMemory());
func.hasAllocatedMemory(),
S < 2 || (NEM == FunctionMoveCtor::NO_THROW &&
!std::is_nothrow_move_constructible<decltype(functor)>::value));
EXPECT_EQ(902, func()); 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 ===================================================================== // TEST =====================================================================
// OverloadedFunctor // OverloadedFunctor
...@@ -885,149 +535,6 @@ TEST(Function, ParameterCopyMoveCount) { ...@@ -885,149 +535,6 @@ TEST(Function, ParameterCopyMoveCount) {
EXPECT_LE(cmt.copyCount(), 0); 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 ===================================================================== // TEST =====================================================================
// VariadicTemplate & VariadicArguments // VariadicTemplate & VariadicArguments
...@@ -1231,6 +738,9 @@ TEST(Function, ConvertReturnType) { ...@@ -1231,6 +738,9 @@ TEST(Function, ConvertReturnType) {
EXPECT_EQ(66, cf9().x); EXPECT_EQ(66, cf9().x);
} }
// TEST =====================================================================
// asStdFunction_*
TEST(Function, asStdFunction_void) { TEST(Function, asStdFunction_void) {
int i = 0; int i = 0;
folly::Function<void()> f = [&] { ++i; }; folly::Function<void()> f = [&] { ++i; };
...@@ -1302,3 +812,40 @@ TEST(Function, asStdFunction_args_const) { ...@@ -1302,3 +812,40 @@ TEST(Function, asStdFunction_args_const) {
sf(42, 42); sf(42, 42);
EXPECT_EQ(1, i); 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);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment