Commit 08a67734 authored by Sven Over's avatar Sven Over Committed by Facebook Github Bot 9

Introducing folly::Function

Summary:std::function is copy-constructible and requires that the callable that it wraps
is copy-constructible as well, which is a constraint that is often inconvenient.
In most cases when using a std::function we don't make use of its
copy-constructibility.

This diff introduces a templated type called folly::Function that is very
similar to a std::function, except it is not copy-constructible and doesn't
require the callable to be either.

Like std::function, Function is a templated type with template parameters
for return type and argument types of the callable, but not the callable's
specific type. It can store function pointers, static member function pointers,
std::function objects, std::reference_wrapper objects and arbitrary callable
types (functors) with matching return and argument types.

Much like std::function, Function will store small callables in-place, so
that no additional memory allocation is necessary. For larger callables,
Function will allocate memory on the heap.

Function has two more template parameters: firstly, an enum parameter of
type folly::FunctionMoveCtor, which defaults to NO_THROW and determines
whether no-except-movability should be guaranteed. If set to NO_THROW,
callables that are not no-except-movable will be stored on the heap, even
if they would fit into the storage area within Function.

Secondly, a size_t parameter (EmbedFunctorSize), which determines the size of
the internal callable storage. If you know the specific type of the callable you
want to store, you can set EmbedFunctorSize to sizeof(CallableType).

The original motivation of this diff was to allow to pass lambdas to
folly::Future::then that are not copy-constructible because they capture
non-copyable types, such as a promise or a unique pointer.

Another diff will shortly follow that changes folly::Future to use
folly::Function instead of std::function for callbacks, thus allowing to
pass non-copyable lambdas to folly::Future::then.

Reviewed By: fugalh

Differential Revision: D2844587

fb-gh-sync-id: 3bee2af75ef8a4eca4409aaa679cc13762cae0d0
shipit-source-id: 3bee2af75ef8a4eca4409aaa679cc13762cae0d0
parent 328b22e8
/*
* 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) noexcept(
OtherNTM == FunctionMoveCtor::NO_THROW &&
EmbedFunctorSize >= OtherEmbedFunctorSize) {
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!");
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 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 =
std::is_convertible<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 =
std::is_convertible<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 is a Function
template <typename T>
struct IsFunction : public std::false_type {};
template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
struct IsFunction<::folly::Function<FunctionType, NTM, EmbedFunctorSize>>
: public std::true_type {};
// Helper template to check if a functor can be called with arguments of type
// Args..., if it returns a type convertible to R, and also is not a
// Function.
// Function objects can constructed or assigned from types for which
// IsCallableHelper is true_type.
template <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>::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 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 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;
};
} // namespace function
} // namespace detail
} // 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.
*/
/**
* @class Function
*
* @brief A polymorphic function wrapper that is not copyable and does not
* require the wrapped function to be copy constructible.
*
* `folly::Function` is a polymorphic function wrapper, similar to
* `std::function`. The template parameters of the `folly::Function` define
* the parameter signature of the wrapped callable, but not the specific
* type of the embedded callable. E.g. a `folly::Function<int(int)>`
* can wrap callables that return an `int` when passed an `int`. This can be a
* function pointer or any class object implementing one or both of
* int operator(int);
* int operator(int) const;
* If both are defined, the non-const one takes precedence.
*
* Unlike `std::function`, a `folly::Function` can wrap objects that are not
* copy constructible. As a consequence of this, `folly::Function` itself
* is not copyable, either.
*
* Another difference is that, unlike `std::function`, `folly::Function` treats
* const-ness of methods correctly. While a `std::function` allows to wrap
* an object that only implements a non-const `operator()` and invoke
* a const-reference of the `std::function`, `folly::Function` requires you to
* declare a function type as const in order to be able to execute it on a
* const-reference.
*
* For example:
* class Foo {
* public:
* void operator()() {
* // mutates the Foo object
* }
* };
*
* class Bar {
* std::function<void(void)> foo_; // wraps a Foo object
* public:
* void mutateFoo() const
* {
* foo_();
* }
* };
* Even though `mutateFoo` is a const-method, so it can only reference `foo_`
* as const, it is able to call the non-const `operator()` of the Foo
* object that is embedded in the foo_ function.
*
* `folly::Function` will not allow you to do that. You will have to decide
* whether you need to invoke your wrapped callable from a const reference
* (like in the example above), in which case it will only wrap a
* `operator() const`. If your functor does not implement that,
* compilation will fail. If you do not require to be able to invoke the
* wrapped function in a const context, you can wrap any functor that
* implements either or both of const and non-const `operator()`.
*
* The first (and usually only specified) template parameter of
* `folly::Function`, the `FunctionType`, can be const-qualified. Be aware
* that the const is part of the function signature. It does not mean that
* the function type is a const type.
*
* using FunctionType = R(Args...);
* using ConstFunctionType = R(Args...) const;
*
* In this example, `FunctionType` and `ConstFunctionType` are different
* types. `ConstFunctionType` is not the same as `const FunctionType`.
* As a matter of fact, trying to use the latter should emit a compiler
* warning or error, because it has no defined meaning.
*
* // This will not compile:
* folly::Function<void(void) const> func = Foo();
* // because Foo does not have a member function of the form:
* // void operator()() const;
*
* // This will compile just fine:
* folly::Function<void(void)> func = Foo();
* // and it will wrap the existing member function:
* // void operator()();
*
* When should a const function type be used? As a matter of fact, you will
* probably not need to use const function types very often. See the following
* example:
*
* class Bar {
* folly::Function<void()> func_;
* folly::Function<void() const> constFunc_;
*
* void someMethod() {
* // Can call func_.
* func_();
* // Can call constFunc_.
* constFunc_();
* }
*
* void someConstMethod() const {
* // Can call constFunc_.
* constFunc_();
* // However, cannot call func_ because a non-const method cannot
* // be called from a const one.
* }
* };
*
* As you can see, whether the `folly::Function`'s function type should
* be declared const or not is identical to whether a corresponding method
* would be declared const or not.
*
* You only require a `folly::Function` to hold a const function type, if you
* intend to invoke it from within a const context. This is to ensure that
* you cannot mutate its inner state when calling in a const context.
*
* This is how the const/non-const choice relates to lambda functions:
*
* // Non-mutable lambdas: can be stored in a non-const...
* folly::Function<void(int)> print_number =
* [] (int number) { std::cout << number << std::endl; };
*
* // ...as well as in a const folly::Function
* folly::Function<void(int) const> print_number_const =
* [] (int number) { std::cout << number << std::endl; };
*
* // Mutable lambda: can only be stored in a non-const folly::Function:
* int number = 0;
* folly::Function<void()> print_number =
* [number] () mutable { std::cout << ++number << std::endl; };
* // Trying to store the above mutable lambda in a
* // `folly::Function<void() const>` would lead to a compiler error:
* // error: no viable conversion from '(lambda at ...)' to
* // 'folly::Function<void () const>'
*
* Casting between const and non-const `folly::Function`s:
* conversion from const to non-const signatures happens implicitly. Any
* function that takes a `folly::Function<R(Args...)>` can be passed
* a `folly::Function<R(Args...) const>` without explicit conversion.
* This is safe, because casting from const to non-const only entails giving
* up the ability to invoke the function from a const context.
* Casting from a non-const to a const signature is potentially dangerous,
* as it means that a function that may change its inner state when invoked
* is made possible to call from a const context. Therefore this cast does
* not happen implicitly. The function `folly::constCastfolly::Function` can
* be used to perform the cast.
*
* // Mutable lambda: can only be stored in a non-const folly::Function:
* int number = 0;
* folly::Function<void()> print_number =
* [number] () mutable { std::cout << ++number << std::endl; };
*
* // const-cast to a const folly::Function:
* folly::Function<void() const> print_number_const =
* constCastfolly::Function(std::move(print_number));
*
* When to use const function types?
* Generally, only when you need them. When you use a `folly::Function` as a
* member of a struct or class, only use a const function signature when you
* need to invoke the function from const context.
* When passing a `folly::Function` to a function, the function should accept
* a non-const `folly::Function` whenever possible, i.e. when it does not
* need to pass on or store a const `folly::Function`. This is the least
* possible constraint: you can always pass a const `folly::Function` when
* the function accepts a non-const one.
*
* How does the const behaviour compare to `std::function`?
* `std::function` can wrap object with non-const invokation behaviour but
* exposes them as const. The equivalent behaviour can be achieved with
* `folly::Function` like so:
*
* std::function<void(void)> stdfunc = someCallable;
*
* folly::Function<void(void) const> uniqfunc = constCastfolly::Function(
* folly::Function<void(void)>(someCallable)
* );
*
* You need to wrap the callable first in a non-const `folly::Function` to
* select a non-const invoke operator (or the const one if no non-const one is
* present), and then move it into a const `folly::Function` using
* `constCastfolly::Function`.
* The name of `constCastfolly::Function` should warn you that something
* potentially dangerous is happening. As a matter of fact, using
* `std::function` always involves this potentially dangerous aspect, which
* is why it is not considered fully const-safe or even const-correct.
* However, in most of the cases you will not need the dangerous aspect at all.
* Either you do not require invokation of the function from a const context,
* in which case you do not need to use `constCastfolly::Function` and just
* use the inner `folly::Function` in the example above, i.e. just use a
* non-const `folly::Function`. Or, you may need invokation from const, but
* the callable you are wrapping does not mutate its state (e.g. it is a class
* object and implements `operator() const`, or it is a normal,
* non-mutable lambda), in which case you can wrap the callable in a const
* `folly::Function` directly, without using `constCastfolly::Function`.
* Only if you require invokation from a const context of a callable that
* may mutate itself when invoked you have to go through the above procedure.
* However, in that case what you do is potentially dangerous and requires
* the equivalent of a `const_cast`, hence you need to call
* `constCastfolly::Function`.
*
* `folly::Function` also has two additional template paremeters:
* * `NTM`: if set to `folly::FunctionMoveCtor::NO_THROW`, the
* `folly::Function` object is guaranteed to be nothrow move constructible.
* The downside is that any function object that itself is
* not nothrow move constructible cannot be stored in-place in the
* `folly::Function` object and will be stored on the heap instead.
* * `EmbedFunctorSize`: a number of bytes that will be reserved in the
* `folly::Function` object to store callable objects in-place. If you
* wrap a callable object bigger than this in a `folly::Function` object,
* it will be stored on the heap and the `folly::Function` object will keep
* a `std::unique_ptr` to it.
*/
#pragma once
#include <functional>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <folly/ScopeGuard.h>
#include <folly/portability/Constexpr.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)>)>
class Function;
} // folly
// boring predeclarations and details
#include "Function-pre.h"
namespace folly {
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;
};
public:
// not copyable
Function(Function const&) = delete;
Function& operator=(Function const&) = delete;
/**
* Default constructor. Constructs an empty Function.
*/
Function() noexcept {
initializeEmptyExecutor();
}
~Function() {
destroyExecutor();
static_assert(
kStorageSize == sizeof(*this),
"There is something wrong with the size of Function");
}
// construct/assign from Function
/**
* Move constructor
*/
Function(Function&& other) noexcept(hasNoExceptMoveCtor());
/**
* Move assignment operator
*/
Function& operator=(Function&& rhs) noexcept(hasNoExceptMoveCtor());
/**
* Constructs a `Function` by moving from one with different template
* parameters with regards to const-ness, no-except-movability and internal
* storage size.
*/
template <
typename OtherFunctionType,
FunctionMoveCtor OtherNTM,
size_t OtherEmbedFunctorSize>
Function(Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&& other)
noexcept(
OtherNTM == FunctionMoveCtor::NO_THROW &&
EmbedFunctorSize >= OtherEmbedFunctorSize);
/**
* Moves a `Function` with different template parameters with regards
* to const-ness, no-except-movability and internal storage size into this
* one.
*/
template <
typename RhsFunctionType,
FunctionMoveCtor RhsNTM,
size_t RhsEmbedFunctorSize>
Function& operator=(Function<RhsFunctionType, RhsNTM, RhsEmbedFunctorSize>&&
rhs) noexcept(RhsNTM == FunctionMoveCtor::NO_THROW);
/**
* Constructs an empty `Function`.
*/
/* implicit */ Function(std::nullptr_t) noexcept : Function() {}
/**
* Clears this `Function`.
*/
Function& operator=(std::nullptr_t) noexcept {
destroyExecutor();
initializeEmptyExecutor();
return *this;
}
/**
* Constructs a new `Function` from any callable object. This
* handles function pointers, pointers to static member functions,
* `std::reference_wrapper` objects, `std::function` objects, and arbitrary
* objects that implement `operator()` if the parameter signature
* matches (i.e. it returns R when called with Args...).
* For a `Function` with a const function type, the object must be
* callable from a const-reference, i.e. implement `operator() const`.
* For a `Function` with a non-const function type, the object will
* be called from a non-const reference, which means that it will execute
* a non-const `operator()` if it is defined, and falls back to
* `operator() const` otherwise
*/
template <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));
}
/**
* Assigns a callable object to this `Function`.
*/
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;
}
/**
* Exchanges the callable objects of `*this` and `other`. `other` can be
* a Function with different settings with regard to
* no-except-movability and internal storage size, but must match
* `*this` with regards to return type and argument types.
*/
template <FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize>
void
swap(Function<FunctionType, OtherNTM, OtherEmbedFunctorSize>& o) noexcept(
hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW);
/**
* Returns `true` if this `Function` contains a callable, i.e. is
* non-empty.
*/
explicit operator bool() const noexcept;
/**
* Returns `true` if this `Function` stores the callable on the
* heap. If `false` is returned, there has been no additional memory
* allocation and the callable is stored inside the `Function`
* object itself.
*/
bool hasAllocatedMemory() const noexcept;
/**
* Returns the `type_info` (as returned by `typeid`) of the callable stored
* in this `Function`. Returns `typeid(void)` if empty.
*/
std::type_info const& target_type() const noexcept;
/**
* Returns a pointer to the stored callable if its type matches `T`, and
* `nullptr` otherwise.
*/
template <typename T>
T* target() noexcept;
/**
* Returns a const-pointer to the stored callable if its type matches `T`,
* and `nullptr` otherwise.
*/
template <typename T>
const T* target() const noexcept;
/**
* Move out this `Function` into one with a const function type.
*
* This is a potentially dangerous operation, equivalent to a `const_cast`.
* This converts a `Function` with a non-const function type, i.e.
* one that can only be called when in the form of a non-const reference,
* into one that can be called in a const context. Use at your own risk!
*/
Function<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 <typename F>
void createExecutor(F&& f) noexcept(
noexcept(typename std::decay<F>::type(std::forward<F>(f))));
void destroyExecutor() noexcept;
struct MinStorageSize;
typename std::aligned_storage<MinStorageSize::value>::type data_;
static constexpr size_t kStorageSize = sizeof(data_);
};
// 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, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
inline bool operator==(
std::nullptr_t,
Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept {
return !f;
}
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, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
inline bool operator!=(
std::nullptr_t,
Function<FunctionType, NTM, EmbedFunctorSize> const& f) noexcept {
return !!f;
}
/**
* Cast a `Function` into one with a const function type.
*
* This is a potentially dangerous operation, equivalent to a `const_cast`.
* This converts a `Function` with a non-const function type, i.e.
* one that can only be called when in the form of a non-const reference,
* into one that can be called in a const context. Use at your own risk!
*/
template <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();
}
} // folly
namespace std {
template <typename FunctionType, bool NOM1, bool NOM2, size_t S1, size_t S2>
void swap(
::folly::Function<FunctionType, NOM1, S1>& lhs,
::folly::Function<FunctionType, NOM2, S2>& rhs) {
lhs.swap(rhs);
}
} // std
#include "Function-inl.h"
......@@ -329,6 +329,9 @@ nobase_follyinclude_HEADERS = \
TimeoutQueue.h \
Traits.h \
Unicode.h \
Function.h \
Function-inl.h \
Function-pre.h \
Uri.h \
Uri-inl.h \
Varint.h \
......
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdarg>
#include <folly/Function.h>
#include <folly/Memory.h>
#include <gtest/gtest.h>
using folly::FunctionMoveCtor;
using folly::Function;
namespace {
int func_int_int_add_25(int x) {
return x + 25;
}
int func_int_int_add_111(int x) {
return x + 111;
}
int func_int_return_987() {
return 987;
}
float floatMult(float a, float b) {
return a * b;
}
template <class T, size_t S>
struct Functor {
std::array<T, S> data = {{0}};
// Two operator() with different argument types.
// The InvokeReference tests use both
T const& operator()(size_t index) const {
return data[index];
}
T operator()(size_t index, T const& value) {
T oldvalue = data[index];
data[index] = value;
return oldvalue;
}
};
// TEST =====================================================================
// NoExceptMovable
struct MoveMayThrow {
bool doThrow{false};
MoveMayThrow() = default;
MoveMayThrow(MoveMayThrow const&) = default;
MoveMayThrow& operator=(MoveMayThrow const&) = default;
MoveMayThrow(MoveMayThrow&&) noexcept(false) {
if (doThrow) {
throw std::runtime_error("MoveMayThrow(MoveMayThrow&&)");
}
}
MoveMayThrow& operator=(MoveMayThrow&&) noexcept(false) {
if (doThrow) {
throw std::runtime_error("MoveMayThrow::operator=(MoveMayThrow&&)");
}
return *this;
}
};
}
TEST(Function, NoExceptMovable) {
// callable_noexcept is noexcept-movable
auto callable_noexcept = [](int x) { return x + 1; };
EXPECT_TRUE(
std::is_nothrow_move_constructible<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(func(42), 43);
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(func_safe_move(42), 52);
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(func_movethrows(42), 52);
EXPECT_FALSE(func_movethrows.hasAllocatedMemory());
EXPECT_FALSE(
std::is_nothrow_move_constructible<decltype(func_movethrows)>::value);
}
// TEST =====================================================================
// InvokeFunctor & InvokeReference
template <FunctionMoveCtor NEM, size_t S>
void invoke_functor_test() {
Functor<int, 100> func;
func(5, 123);
// Try Functions with differently sized storage areas
// S=0: request storage for functors of size 0. The storage size
// will be actually larger, because there is a lower limit which
// still allows to store at least pointers to functors on the heap.
// S=1: request minimum storage size of 0.5x the sizeof(func)
// S=2: request sizeof(func)
// S=3: request 1.5*sizeof(func)
Function<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(getter(5), 123);
}
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() {
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);
EXPECT_EQ(getter(5), 123);
EXPECT_EQ(setter(5, 456), 123);
EXPECT_EQ(setter(5, 567), 456);
EXPECT_EQ(getter(5), 567);
}
TEST(Function, InvokeReference_T) {
invoke_reference_test<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;
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; });
EXPECT_NE(g, nullptr);
EXPECT_NE(nullptr, g);
EXPECT_TRUE(g);
EXPECT_EQ(g(99), 100);
Function<int(int), NEM> h(&func_int_int_add_25);
EXPECT_NE(h, nullptr);
EXPECT_NE(nullptr, h);
EXPECT_TRUE(h);
EXPECT_EQ(h(100), 125);
h = {};
EXPECT_EQ(h, nullptr);
EXPECT_EQ(nullptr, h);
EXPECT_FALSE(h);
EXPECT_THROW(h(101), std::bad_function_call);
}
TEST(Function, Emptiness_T) {
emptiness_test<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>
void swap_test() {
Function<int(int), NEM1> mf1(func_int_int_add_25);
Function<int(int), NEM2> mf2(func_int_int_add_111);
EXPECT_EQ(mf1(100), 125);
EXPECT_EQ(mf2(100), 211);
mf1.swap(mf2);
EXPECT_EQ(mf2(100), 125);
EXPECT_EQ(mf1(100), 211);
Function<int(int)> mf3(nullptr);
EXPECT_EQ(mf3, nullptr);
mf1.swap(mf3);
EXPECT_EQ(mf3(100), 211);
EXPECT_EQ(mf1, nullptr);
Function<int(int)> mf4([](int x) { return x + 222; });
EXPECT_EQ(mf4(100), 322);
mf4.swap(mf3);
EXPECT_EQ(mf4(100), 211);
EXPECT_EQ(mf3(100), 322);
mf3.swap(mf1);
EXPECT_EQ(mf3, nullptr);
EXPECT_EQ(mf1(100), 322);
}
TEST(Function, Swap_TT) {
swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::MAY_THROW>();
}
TEST(Function, Swap_TN) {
swap_test<FunctionMoveCtor::MAY_THROW, FunctionMoveCtor::NO_THROW>();
}
TEST(Function, Swap_NT) {
swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::MAY_THROW>();
}
TEST(Function, Swap_NN) {
swap_test<FunctionMoveCtor::NO_THROW, FunctionMoveCtor::NO_THROW>();
}
// TEST =====================================================================
// Bind
template <FunctionMoveCtor NEM>
void bind_test() {
Function<float(float, float), NEM> fnc = floatMult;
auto task = std::bind(std::move(fnc), 2.f, 4.f);
EXPECT_THROW(fnc(0, 0), std::bad_function_call);
EXPECT_EQ(task(), 8);
auto task2(std::move(task));
EXPECT_THROW(task(), std::bad_function_call);
EXPECT_EQ(task2(), 8);
}
TEST(Function, Bind_T) {
bind_test<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() {
auto unique_ptr_int = folly::make_unique<int>(900);
EXPECT_EQ(*unique_ptr_int, 900);
char fooData[64] = {0};
EXPECT_EQ(fooData[0], 0); // suppress gcc warning about fooData not being used
auto functor = std::bind(
[fooData](std::unique_ptr<int>& up) mutable { return ++*up; },
std::move(unique_ptr_int));
EXPECT_EQ(functor(), 901);
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));
EXPECT_EQ(func(), 902);
}
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(functor(3), 123);
// 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(func2x(3, 200), 123);
EXPECT_EQ(func2x(3, 201), 200);
// 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(func1x(3, 202), 201);
EXPECT_EQ(func1x(3, 203), 202);
// Function with minimal callable storage area (functor does
// not fit and will be moved to memory on the heap)
Function<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(func0x(3, 204), 203);
EXPECT_EQ(func0x(3, 205), 204);
// bonus test: move to Function with opposite NoExceptMovable
// setting
Function<
int(size_t, int),
NEM == FunctionMoveCtor::NO_THROW ? FunctionMoveCtor::MAY_THROW
: FunctionMoveCtor::NO_THROW,
0>
funcnot = std::move(func0x);
EXPECT_THROW(func0x(0, 0), std::bad_function_call);
EXPECT_FALSE(func0x);
EXPECT_TRUE(funcnot.hasAllocatedMemory());
EXPECT_EQ(funcnot(3, 206), 205);
EXPECT_EQ(funcnot(3, 207), 206);
}
TEST(Function, Downsize_T) {
downsize_test<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(*shared_int, 100);
EXPECT_EQ(shared_int.use_count(), 1);
Function<int(void), NEM> func1 = [shared_int]() { return ++*shared_int; };
EXPECT_EQ(shared_int.use_count(), 2);
EXPECT_EQ(func1(), 101);
EXPECT_EQ(*shared_int, 101);
// func2: made to not fit functor.
Function<int(void), NEM, sizeof(functor) / 2> func2 = std::move(func1);
EXPECT_THROW(func1(), std::bad_function_call);
EXPECT_EQ(shared_int.use_count(), 2);
EXPECT_FALSE(func1);
EXPECT_EQ(func2(), 102);
EXPECT_EQ(*shared_int, 102);
func2 = [shared_int]() { return ++*shared_int; };
EXPECT_EQ(shared_int.use_count(), 2);
EXPECT_EQ(func2(), 103);
EXPECT_EQ(*shared_int, 103);
// We set func2 to a lambda that captures 'functor', which forces it on
// the heap
func2 = [functor]() { return functor(3); };
EXPECT_TRUE(func2.hasAllocatedMemory());
EXPECT_EQ(func2(), 999);
EXPECT_EQ(shared_int.use_count(), 1);
EXPECT_EQ(*shared_int, 103);
func2 = [shared_int]() { return ++*shared_int; };
EXPECT_EQ(shared_int.use_count(), 2);
EXPECT_EQ(func2(), 104);
EXPECT_EQ(*shared_int, 104);
// We set func2 to function pointer, which always fits into the
// Function object and is no-except-movable
func2 = &func_int_return_987;
EXPECT_FALSE(func2.hasAllocatedMemory());
EXPECT_EQ(func2(), 987);
EXPECT_EQ(shared_int.use_count(), 1);
EXPECT_EQ(*shared_int, 104);
}
TEST(Function, Refcount_T) {
refcount_test<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(func(100), 125);
Function<int(int), NEM> ufunc = std::move(func);
EXPECT_THROW(func(0), std::bad_function_call);
EXPECT_EQ(ufunc(200), 225);
EXPECT_EQ(ufunc.target_type(), typeid(std::function<int(int)>));
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(ufunc_target(300), 325);
}
TEST(Function, Target_T) {
target_test<FunctionMoveCtor::MAY_THROW>();
}
TEST(Function, Target_N) {
target_test<FunctionMoveCtor::NO_THROW>();
}
// TEST =====================================================================
// OverloadedFunctor
TEST(Function, OverloadedFunctor) {
struct OverloadedFunctor {
// variant 1
int operator()(int x) {
return 100 + 1 * x;
}
// variant 2 (const-overload of v1)
int operator()(int x) const {
return 100 + 2 * x;
}
// variant 3
int operator()(int x, int) {
return 100 + 3 * x;
}
// variant 4 (const-overload of v3)
int operator()(int x, int) const {
return 100 + 4 * x;
}
// variant 5 (non-const, has no const-overload)
int operator()(int x, char const*) {
return 100 + 5 * x;
}
// variant 6 (const only)
int operator()(int x, std::vector<int> const&) const {
return 100 + 6 * x;
}
};
OverloadedFunctor of;
Function<int(int)> variant1 = of;
EXPECT_EQ(variant1(15), 100 + 1 * 15);
Function<int(int) const> variant2 = of;
EXPECT_EQ(variant2(16), 100 + 2 * 16);
Function<int(int, int)> variant3 = of;
EXPECT_EQ(variant3(17, 0), 100 + 3 * 17);
Function<int(int, int) const> variant4 = of;
EXPECT_EQ(variant4(18, 0), 100 + 4 * 18);
Function<int(int, char const*)> variant5 = of;
EXPECT_EQ(variant5(19, "foo"), 100 + 5 * 19);
Function<int(int, std::vector<int> const&)> variant6 = of;
EXPECT_EQ(variant6(20, {}), 100 + 6 * 20);
EXPECT_EQ(variant6(20, {1, 2, 3}), 100 + 6 * 20);
Function<int(int, std::vector<int> const&) const> variant6const = of;
EXPECT_EQ(variant6const(21, {}), 100 + 6 * 21);
// Cast const-functions to non-const and the other way around: if the functor
// has both const and non-const operator()s for a given parameter signature,
// constructing a Function must select one of them, depending on
// whether the function type template parameter is const-qualified or not.
// When the const-ness is later changed (by moving the
// Function<R(Args...)const> into a Function<R(Args...)> or by
// calling the folly::constCastFunction which moves it into a
// Function<R(Args...)const>), the Function must still execute
// the initially selected function.
auto variant1_const = folly::constCastFunction(std::move(variant1));
EXPECT_THROW(variant1(0), std::bad_function_call);
EXPECT_EQ(variant1_const(22), 100 + 1 * 22);
Function<int(int)> variant2_nonconst = std::move(variant2);
EXPECT_THROW(variant2(0), std::bad_function_call);
EXPECT_EQ(variant2_nonconst(23), 100 + 2 * 23);
auto variant3_const = folly::constCastFunction(std::move(variant3));
EXPECT_THROW(variant3(0, 0), std::bad_function_call);
EXPECT_EQ(variant3_const(24, 0), 100 + 3 * 24);
Function<int(int, int)> variant4_nonconst = std::move(variant4);
EXPECT_THROW(variant4(0, 0), std::bad_function_call);
EXPECT_EQ(variant4_nonconst(25, 0), 100 + 4 * 25);
auto variant5_const = folly::constCastFunction(std::move(variant5));
EXPECT_THROW(variant5(0, ""), std::bad_function_call);
EXPECT_EQ(variant5_const(26, "foo"), 100 + 5 * 26);
auto variant6_const = folly::constCastFunction(std::move(variant6));
EXPECT_THROW(variant6(0, {}), std::bad_function_call);
EXPECT_EQ(variant6_const(27, {}), 100 + 6 * 27);
Function<int(int, std::vector<int> const&)> variant6const_nonconst =
std::move(variant6const);
EXPECT_THROW(variant6const(0, {}), std::bad_function_call);
EXPECT_EQ(variant6const_nonconst(28, {}), 100 + 6 * 28);
}
// TEST =====================================================================
// Lambda
TEST(Function, Lambda) {
// Non-mutable lambdas: can be stored in a non-const...
Function<int(int)> func = [](int x) { return 1000 + x; };
EXPECT_EQ(func(1), 1001);
// ...as well as in a const Function
Function<int(int) const> func_const = [](int x) { return 2000 + x; };
EXPECT_EQ(func_const(1), 2001);
// Mutable lambda: can only be stored in a const Function:
int number = 3000;
Function<int()> func_mutable = [number]() mutable { return ++number; };
EXPECT_EQ(func_mutable(), 3001);
EXPECT_EQ(func_mutable(), 3002);
// test after const-casting
Function<int(int) const> func_made_const =
folly::constCastFunction(std::move(func));
EXPECT_EQ(func_made_const(2), 1002);
EXPECT_THROW(func(0), std::bad_function_call);
Function<int(int)> func_const_made_nonconst = std::move(func_const);
EXPECT_EQ(func_const_made_nonconst(2), 2002);
EXPECT_THROW(func_const(0), std::bad_function_call);
Function<int() const> func_mutable_made_const =
folly::constCastFunction(std::move(func_mutable));
EXPECT_EQ(func_mutable_made_const(), 3003);
EXPECT_EQ(func_mutable_made_const(), 3004);
EXPECT_THROW(func_mutable(), std::bad_function_call);
}
// TEST =====================================================================
// DataMember & MemberFunction
struct MemberFunc {
int x;
int getX() const {
return x;
}
void setX(int xx) {
x = xx;
}
};
TEST(Function, DataMember) {
MemberFunc mf;
MemberFunc const& cmf = mf;
mf.x = 123;
Function<int(MemberFunc const*)> data_getter1 = &MemberFunc::x;
EXPECT_EQ(data_getter1(&cmf), 123);
Function<int(MemberFunc*)> data_getter2 = &MemberFunc::x;
EXPECT_EQ(data_getter2(&mf), 123);
Function<int(MemberFunc const&)> data_getter3 = &MemberFunc::x;
EXPECT_EQ(data_getter3(cmf), 123);
Function<int(MemberFunc&)> data_getter4 = &MemberFunc::x;
EXPECT_EQ(data_getter4(mf), 123);
}
TEST(Function, MemberFunction) {
MemberFunc mf;
MemberFunc const& cmf = mf;
mf.x = 123;
Function<int(MemberFunc const*)> getter1 = &MemberFunc::getX;
EXPECT_EQ(getter1(&cmf), 123);
Function<int(MemberFunc*)> getter2 = &MemberFunc::getX;
EXPECT_EQ(getter2(&mf), 123);
Function<int(MemberFunc const&)> getter3 = &MemberFunc::getX;
EXPECT_EQ(getter3(cmf), 123);
Function<int(MemberFunc&)> getter4 = &MemberFunc::getX;
EXPECT_EQ(getter4(mf), 123);
Function<void(MemberFunc*, int)> setter1 = &MemberFunc::setX;
setter1(&mf, 234);
EXPECT_EQ(mf.x, 234);
Function<void(MemberFunc&, int)> setter2 = &MemberFunc::setX;
setter2(mf, 345);
EXPECT_EQ(mf.x, 345);
}
// TEST =====================================================================
// CaptureCopyMoveCount & ParameterCopyMoveCount
class CopyMoveTracker {
public:
struct ConstructorTag {};
CopyMoveTracker() = delete;
explicit CopyMoveTracker(ConstructorTag)
: data_(std::make_shared<std::pair<size_t, size_t>>(0, 0)) {}
CopyMoveTracker(CopyMoveTracker const& o) noexcept : data_(o.data_) {
++data_->first;
}
CopyMoveTracker& operator=(CopyMoveTracker const& o) noexcept {
data_ = o.data_;
++data_->first;
return *this;
}
CopyMoveTracker(CopyMoveTracker&& o) noexcept : data_(o.data_) {
++data_->second;
}
CopyMoveTracker& operator=(CopyMoveTracker&& o) noexcept {
data_ = o.data_;
++data_->second;
return *this;
}
size_t copyCount() const {
return data_->first;
}
size_t moveCount() const {
return data_->second;
}
size_t refCount() const {
return data_.use_count();
}
void resetCounters() {
data_->first = data_->second = 0;
}
private:
// copy, move
std::shared_ptr<std::pair<size_t, size_t>> data_;
};
TEST(Function, CaptureCopyMoveCount) {
// This test checks that no unnecessary copies/moves are made.
CopyMoveTracker cmt(CopyMoveTracker::ConstructorTag{});
EXPECT_EQ(cmt.copyCount(), 0);
EXPECT_EQ(cmt.moveCount(), 0);
EXPECT_EQ(cmt.refCount(), 1);
// Move into lambda, move lambda into Function
auto lambda1 = [cmt = std::move(cmt)]() {
return cmt.moveCount();
};
Function<size_t(void)> uf1 = std::move(lambda1);
// Max copies: 0. Max copy+moves: 2.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2);
EXPECT_LE(cmt.copyCount(), 0);
cmt.resetCounters();
// Move into lambda, copy lambda into Function
auto lambda2 = [cmt = std::move(cmt)]() {
return cmt.moveCount();
};
Function<size_t(void)> uf2 = lambda2;
// Max copies: 1. Max copy+moves: 2.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2);
EXPECT_LE(cmt.copyCount(), 1);
// Invoking Function must not make copies/moves of the callable
cmt.resetCounters();
uf1();
uf2();
EXPECT_EQ(cmt.copyCount(), 0);
EXPECT_EQ(cmt.moveCount(), 0);
}
TEST(Function, ParameterCopyMoveCount) {
// This test checks that no unnecessary copies/moves are made.
CopyMoveTracker cmt(CopyMoveTracker::ConstructorTag{});
EXPECT_EQ(cmt.copyCount(), 0);
EXPECT_EQ(cmt.moveCount(), 0);
EXPECT_EQ(cmt.refCount(), 1);
// pass by value
Function<size_t(CopyMoveTracker)> uf1 = [](CopyMoveTracker c) {
return c.moveCount();
};
cmt.resetCounters();
uf1(cmt);
// Max copies: 1. Max copy+moves: 2.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2);
EXPECT_LE(cmt.copyCount(), 1);
cmt.resetCounters();
uf1(std::move(cmt));
// Max copies: 1. Max copy+moves: 2.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 2);
EXPECT_LE(cmt.copyCount(), 0);
// pass by reference
Function<size_t(CopyMoveTracker&)> uf2 = [](CopyMoveTracker& c) {
return c.moveCount();
};
cmt.resetCounters();
uf2(cmt);
// Max copies: 0. Max copy+moves: 0.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0);
EXPECT_LE(cmt.copyCount(), 0);
// pass by const reference
Function<size_t(CopyMoveTracker const&)> uf3 = [](CopyMoveTracker const& c) {
return c.moveCount();
};
cmt.resetCounters();
uf3(cmt);
// Max copies: 0. Max copy+moves: 0.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0);
EXPECT_LE(cmt.copyCount(), 0);
// pass by rvalue reference
Function<size_t(CopyMoveTracker &&)> uf4 = [](CopyMoveTracker&& c) {
return c.moveCount();
};
cmt.resetCounters();
uf4(std::move(cmt));
// Max copies: 0. Max copy+moves: 0.
EXPECT_LE(cmt.moveCount() + cmt.copyCount(), 0);
EXPECT_LE(cmt.copyCount(), 0);
}
// TEST =====================================================================
// CopyMoveThrows
enum ExceptionType { COPY, MOVE };
template <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
struct VariadicTemplateSum {
int operator()() const {
return 0;
}
template <class... Args>
int operator()(int x, Args... args) const {
return x + (*this)(args...);
}
};
TEST(Function, VariadicTemplate) {
Function<int(int)> uf1 = VariadicTemplateSum();
Function<int(int, int)> uf2 = VariadicTemplateSum();
Function<int(int, int, int)> uf3 = VariadicTemplateSum();
EXPECT_EQ(uf1(66), 66);
EXPECT_EQ(uf2(55, 44), 99);
EXPECT_EQ(uf3(33, 22, 11), 66);
}
struct VariadicArgumentsSum {
int operator()(int count, ...) const {
int result = 0;
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
result += va_arg(args, int);
}
va_end(args);
return result;
}
};
TEST(Function, VariadicArguments) {
Function<int(int)> uf1 = VariadicArgumentsSum();
Function<int(int, int)> uf2 = VariadicArgumentsSum();
Function<int(int, int, int)> uf3 = VariadicArgumentsSum();
EXPECT_EQ(uf1(0), 0);
EXPECT_EQ(uf2(1, 66), 66);
EXPECT_EQ(uf3(2, 55, 44), 99);
}
// TEST =====================================================================
// SafeCaptureByReference
// A function can use Function const& as a parameter to signal that it
// is safe to pass a lambda that captures local variables by reference.
// It is safe because we know the function called can only invoke the
// Function until it returns. It can't store a copy of the Function
// (because it's not copyable), and it can't move the Function somewhere
// else (because it gets only a const&).
template <typename T>
void for_each(
T const& range,
Function<void(typename T::value_type const&) const> const& func) {
for (auto const& elem : range) {
func(elem);
}
}
TEST(Function, SafeCaptureByReference) {
std::vector<int> const vec = {20, 30, 40, 2, 3, 4, 200, 300, 400};
int sum = 0;
// for_each's second parameter is of type Function<...> const&.
// Hence we know we can safely pass it a lambda that references local
// variables. There is no way the reference to x will be stored anywhere.
for_each<std::vector<int>>(vec, [&sum](int x) { sum += x; });
// gcc versions before 4.9 cannot deduce the type T in the above call
// to for_each. Modern compiler versions can compile the following line:
// for_each(vec, [&sum](int x) { sum += x; });
EXPECT_EQ(sum, 999);
}
......@@ -249,4 +249,8 @@ futures_test_SOURCES = \
futures_test_LDADD = libfollytestmain.la
TESTS += futures_test
function_test_SOURCES = FunctionTest.cpp
function_test_LDADD = libfollytestmain.la
TESTS += function_test
check_PROGRAMS += $(TESTS)
......@@ -36,6 +36,13 @@ void BM_std_function_invoke_impl(int iters,
}
}
void BM_Function_invoke_impl(int iters,
const folly::Function<void() const>& fn) {
for (int n = 0; n < iters; ++n) {
fn();
}
}
void BM_mem_fn_invoke_impl(int iters,
TestClass* tc,
void (TestClass::*memfn)()) {
......
......@@ -18,11 +18,15 @@
#include <functional>
#include <folly/Function.h>
class TestClass;
class VirtualClass;
void BM_fn_ptr_invoke_impl(int iters, void (*fn)());
void BM_std_function_invoke_impl(int iters, const std::function<void()>& fn);
void BM_Function_invoke_impl(int iters,
const folly::Function<void() const>& fn);
void BM_mem_fn_invoke_impl(int iters,
TestClass* tc,
void (TestClass::*memfn)());
......
......@@ -45,6 +45,11 @@ BENCHMARK(std_function_invoke, iters) {
BM_std_function_invoke_impl(iters, doNothing);
}
// Invoking a function through a folly::Function object
BENCHMARK(Function_invoke, iters) {
BM_Function_invoke_impl(iters, doNothing);
}
// Invoking a member function through a member function pointer
BENCHMARK(mem_fn_invoke, iters) {
TestClass tc;
......@@ -111,6 +116,15 @@ BENCHMARK(std_function_create_invoke, iters) {
}
}
// Creating a folly::Function object from a function pointer, and
// invoking it
BENCHMARK(Function_create_invoke, iters) {
for (size_t n = 0; n < iters; ++n) {
folly::Function<void()> fn = doNothing;
fn();
}
}
// Creating a pointer-to-member and invoking it
BENCHMARK(mem_fn_create_invoke, iters) {
TestClass tc;
......@@ -155,6 +169,14 @@ BENCHMARK(scope_guard_std_function_rvalue, iters) {
}
}
// Using ScopeGuard to invoke a folly::Function,
// but create the ScopeGuard with an rvalue to a folly::Function
BENCHMARK(scope_guard_Function_rvalue, iters) {
for (size_t n = 0; n < iters; ++n) {
ScopeGuard g = makeGuard(folly::Function<void()>(doNothing));
}
}
// Using ScopeGuard to invoke a function pointer
BENCHMARK(scope_guard_fn_ptr, iters) {
for (size_t n = 0; n < iters; ++n) {
......
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