X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FFunction.h;h=263b0af07d8ac940b186038dd9bbd5c9e4b436df;hb=ba690cad3c4138fce4e55b319237bb9e01ca030f;hp=f5926ac343edc9a1967bb02cdd83bab50693b6fb;hpb=ab389fe4f77be8d4e25796d2bb6e8f491018f17e;p=folly.git diff --git a/folly/Function.h b/folly/Function.h index f5926ac3..263b0af0 100644 --- a/folly/Function.h +++ b/folly/Function.h @@ -1,7 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. - * - * @author Eric Niebler (eniebler@fb.com), Sven Over (over@fb.com) + * Copyright 2017-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +12,9 @@ * 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. - * + */ +/* + * @author Eric Niebler (eniebler@fb.com), Sven Over (over@fb.com) * Acknowledgements: Giuseppe Ottaviano (ott@fb.com) */ @@ -224,17 +224,17 @@ #include #include +#include +#include namespace folly { -namespace impl { -template +template class Function; template -Function constCastFunction( - Function&&) noexcept; -} // impl +Function constCastFunction( + Function&&) noexcept; namespace detail { namespace function { @@ -243,22 +243,28 @@ enum class Op { MOVE, NUKE, FULL, HEAP }; union Data { void* big; - typename std::aligned_storage<6 * sizeof(void*)>::type small; + std::aligned_storage<6 * sizeof(void*)>::type tiny; }; -template -using ConstIf = typename std::conditional::type; - template ::type> -using IsSmall = std::integral_constant< - bool, - (sizeof(FunT) <= sizeof(Data::small) && - // Same as is_nothrow_move_constructible, but w/ no template instantiation. - noexcept(FunT(std::declval())) - )>; +using IsSmall = Conjunction< + std::integral_constant, + std::is_nothrow_move_constructible>; using SmallTag = std::true_type; using HeapTag = std::false_type; +template +struct NotFunction : std::true_type {}; +template +struct NotFunction> : std::false_type {}; + +template ::type> +using DecayIfConstructible = typename std::enable_if< + Conjunction, std::is_constructible>::value, + FunT>::type; + +struct CoerceTag {}; + template bool isNullPtrFn(T* p) { return p == nullptr; @@ -268,21 +274,158 @@ std::false_type isNullPtrFn(T&&) { return {}; } -template -ReturnType uninitCall(Data&, Args&&...) { - throw std::bad_function_call(); -} inline bool uninitNoop(Op, Data*, Data*) { return false; } +template +struct FunctionTraits; + +template +struct FunctionTraits { + using Call = ReturnType (*)(Data&, Args&&...); + using IsConst = std::false_type; + using ConstSignature = ReturnType(Args...) const; + using NonConstSignature = ReturnType(Args...); + using OtherSignature = ConstSignature; + + template ::type> + using ResultOf = decltype( + static_cast(std::declval()(std::declval()...))); + + template + static ReturnType callSmall(Data& p, Args&&... args) { + return static_cast((*static_cast( + static_cast(&p.tiny)))(static_cast(args)...)); + } + + template + static ReturnType callBig(Data& p, Args&&... args) { + return static_cast( + (*static_cast(p.big))(static_cast(args)...)); + } + + static ReturnType uninitCall(Data&, Args&&...) { + throw std::bad_function_call(); + } + + ReturnType operator()(Args... args) { + auto& fn = *static_cast*>(this); + return fn.call_(fn.data_, static_cast(args)...); + } + + class SharedProxy { + std::shared_ptr> sp_; + + public: + explicit SharedProxy(Function&& func) + : sp_(std::make_shared>( + std::move(func))) {} + ReturnType operator()(Args&&... args) const { + return (*sp_)(static_cast(args)...); + } + }; +}; + +template +struct FunctionTraits { + using Call = ReturnType (*)(Data&, Args&&...); + using IsConst = std::true_type; + using ConstSignature = ReturnType(Args...) const; + using NonConstSignature = ReturnType(Args...); + using OtherSignature = NonConstSignature; + + template ::type> + using ResultOf = decltype(static_cast( + std::declval()(std::declval()...))); + + template + static ReturnType callSmall(Data& p, Args&&... args) { + return static_cast((*static_cast( + static_cast(&p.tiny)))(static_cast(args)...)); + } + + template + static ReturnType callBig(Data& p, Args&&... args) { + return static_cast( + (*static_cast(p.big))(static_cast(args)...)); + } + + static ReturnType uninitCall(Data&, Args&&...) { + throw std::bad_function_call(); + } + + ReturnType operator()(Args... args) const { + auto& fn = *static_cast*>(this); + return fn.call_(fn.data_, static_cast(args)...); + } + + class SharedProxy { + std::shared_ptr> sp_; + + public: + explicit SharedProxy(Function&& func) + : sp_(std::make_shared>( + std::move(func))) {} + ReturnType operator()(Args&&... args) const { + return (*sp_)(static_cast(args)...); + } + }; +}; + +template +bool execSmall(Op o, Data* src, Data* dst) { + switch (o) { + case Op::MOVE: + ::new (static_cast(&dst->tiny)) + Fun(std::move(*static_cast(static_cast(&src->tiny)))); + FOLLY_FALLTHROUGH; + case Op::NUKE: + static_cast(static_cast(&src->tiny))->~Fun(); + break; + case Op::FULL: + return true; + case Op::HEAP: + break; + } + return false; +} + +template +bool execBig(Op o, Data* src, Data* dst) { + switch (o) { + case Op::MOVE: + dst->big = src->big; + src->big = nullptr; + break; + case Op::NUKE: + delete static_cast(src->big); + break; + case Op::FULL: + case Op::HEAP: + break; + } + return true; +} + +// Invoke helper +template +inline auto invoke(F&& f, Args&&... args) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +template +inline auto invoke(M(C::*d), Args&&... args) + -> decltype(std::mem_fn(d)(std::forward(args)...)) { + return std::mem_fn(d)(std::forward(args)...); +} + } // namespace function } // namespace detail -namespace impl { - -template -class Function final { +template +class Function final : private detail::function::FunctionTraits { // These utility types are defined outside of the template to reduce // the number of instantiations, and then imported in the class // namespace for convenience. @@ -290,98 +433,57 @@ class Function final { using Op = detail::function::Op; using SmallTag = detail::function::SmallTag; using HeapTag = detail::function::HeapTag; - using Call = ReturnType (*)(Data&, Args&&...); + using CoerceTag = detail::function::CoerceTag; + + using Traits = detail::function::FunctionTraits; + using Call = typename Traits::Call; using Exec = bool (*)(Op, Data*, Data*); - template - using ConstIf = detail::function::ConstIf; template using IsSmall = detail::function::IsSmall; - /** - * @Function is const-safe: - * - @call_ takes @Data as non-const param to avoid code/data duplication. - * - @data_ can only be mutated if @constCastFunction is used. - */ + // The `data_` member is mutable to allow `constCastFunction` to work without + // invoking undefined behavior. Const-correctness is only violated when + // `FunctionType` is a const function type (e.g., `int() const`) and `*this` + // is the result of calling `constCastFunction`. mutable Data data_; - Call call_{&detail::function::uninitCall}; + Call call_{&Traits::uninitCall}; Exec exec_{&detail::function::uninitNoop}; - friend Function constCastFunction<>( - Function&&) noexcept; - friend class Function; - - template - struct OpsSmall { - using FunT = typename std::decay::type; - static ReturnType call(Data& p, Args&&... args) { - return static_cast((*static_cast*>( - static_cast(&p.small)))(static_cast(args)...)); - } - static bool exec(Op o, Data* src, Data* dst) { - switch (o) { - case Op::MOVE: - ::new (static_cast(&dst->small)) FunT( - std::move(*static_cast(static_cast(&src->small)))); - FOLLY_FALLTHROUGH; - case Op::NUKE: - static_cast(static_cast(&src->small))->~FunT(); - break; - case Op::FULL: - return true; - case Op::HEAP: - break; - } - return false; - } - }; + friend Traits; + friend Function folly::constCastFunction<>( + Function&&) noexcept; + friend class Function; template Function(Fun&& fun, SmallTag) noexcept { - using Ops = OpsSmall; + using FunT = typename std::decay::type; if (!detail::function::isNullPtrFn(fun)) { - ::new (static_cast(&data_.small)) - typename Ops::FunT(static_cast(fun)); - exec_ = &Ops::exec; - call_ = &Ops::call; + ::new (static_cast(&data_.tiny)) FunT(static_cast(fun)); + call_ = &Traits::template callSmall; + exec_ = &detail::function::execSmall; } } - template - struct OpsHeap { - using FunT = typename std::decay::type; - static ReturnType call(Data& p, Args&&... args) { - return static_cast( - (*static_cast*>(p.big))(static_cast(args)...)); - } - static bool exec(Op o, Data* src, Data* dst) { - switch (o) { - case Op::MOVE: - dst->big = src->big; - src->big = nullptr; - break; - case Op::NUKE: - delete static_cast(src->big); - break; - case Op::FULL: - case Op::HEAP: - break; - } - return true; - } - }; - template Function(Fun&& fun, HeapTag) { - using Ops = OpsHeap; - data_.big = new typename Ops::FunT(static_cast(fun)); - call_ = &Ops::call; - exec_ = &Ops::exec; + using FunT = typename std::decay::type; + data_.big = new FunT(static_cast(fun)); + call_ = &Traits::template callBig; + exec_ = &detail::function::execBig; } - template ::type> - using ResultOf = decltype(static_cast( - std::declval&>()(std::declval()...))); + template + Function(Function&& that, CoerceTag) + : Function(static_cast&&>(that), HeapTag{}) {} + + Function( + Function&& that, + CoerceTag) noexcept { + that.exec_(Op::MOVE, &that.data_, &data_); + std::swap(call_, that.call_); + std::swap(exec_, that.exec_); + } public: /** @@ -390,11 +492,6 @@ class Function final { Function() = default; // not copyable - // NOTE: Deleting the non-const copy constructor is unusual but necessary to - // prevent copies from non-const `Function` object from selecting the - // perfect forwarding implicit converting constructor below - // (i.e., `template Function(Fun&&)`). - Function(Function&) = delete; Function(const Function&) = delete; /** @@ -412,36 +509,47 @@ class Function final { /* implicit */ Function(std::nullptr_t) noexcept {} /** - * 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. + * Constructs a new `Function` from any callable object that is _not_ a + * `folly::Function`. 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 an object convertible to `R` when called + * with `Args...`). * * \note `typename = ResultOf` prevents this overload from being * selected by overload resolution when `fun` is not a compatible function. + * + * \note The noexcept requires some explanation. IsSmall is true when the + * decayed type fits within the internal buffer and is noexcept-movable. But + * this ctor might copy, not move. What we need here, if this ctor does a + * copy, is that this ctor be noexcept when the copy is noexcept. That is not + * checked in IsSmall, and shouldn't be, because once the Function is + * constructed, the contained object is never copied. This check is for this + * ctor only, in the case that this ctor does a copy. */ - template > - /* implicit */ Function(Fun&& fun) noexcept(IsSmall::value) + template < + typename Fun, + typename FunT = detail::function::DecayIfConstructible, + typename = typename Traits::template ResultOf> + /* implicit */ Function(Fun&& fun) noexcept( + IsSmall::value && noexcept(FunT(std::declval()))) : Function(static_cast(fun), IsSmall{}) {} /** - * For moving a `Function` into a `Function`. + * For move-constructing from a `folly::Function`. + * 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 < - bool OtherConst, - typename std::enable_if::type = 0> - Function(Function&& that) noexcept { - that.exec_(Op::MOVE, &that.data_, &data_); - std::swap(call_, that.call_); - std::swap(exec_, that.exec_); - } + typename Signature, + typename = typename Traits::template ResultOf>> + Function(Function&& that) noexcept( + noexcept(Function(std::move(that), CoerceTag{}))) + : Function(std::move(that), CoerceTag{}) {} /** * If `ptr` is null, constructs an empty `Function`. Otherwise, @@ -463,22 +571,27 @@ class Function final { exec_(Op::NUKE, &data_, nullptr); } - Function& operator=(Function&) = delete; Function& operator=(const Function&) = delete; /** * Move assignment operator + * + * \note Leaves `that` in a valid but unspecified state. If `&that == this` + * then `*this` is left in a valid but unspecified state. */ 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)); - } + // 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. + // In the case of self-move (this == &that), this leaves the object in + // a default-constructed state. First the object is destroyed, then we + // pass the destroyed object to the move constructor. The first thing the + // move constructor does is default-construct the object. That object is + // "moved" into itself, which is a no-op for a default-constructed Function. + this->~Function(); + ::new (this) Function(std::move(that)); return *this; } @@ -489,7 +602,7 @@ class Function final { * \note `typename = ResultOf` prevents this overload from being * selected by overload resolution when `fun` is not a compatible function. */ - template > + template ()))> Function& operator=(Fun&& fun) noexcept( noexcept(/* implicit */ Function(std::declval()))) { // Doing this in place is more efficient when we can do so safely. @@ -505,6 +618,17 @@ class Function final { return *this; } + /** + * For assigning from a `Function`. + */ + template < + typename Signature, + typename = typename Traits::template ResultOf>> + Function& operator=(Function&& that) noexcept( + noexcept(Function(std::move(that)))) { + return (*this = Function(std::move(that))); + } + /** * Clears this `Function`. */ @@ -526,31 +650,8 @@ class Function final { /** * Call the wrapped callable object with the specified arguments. - * If this `Function` object is a const `folly::Function` object, - * this overload shall not participate in overload resolution. */ - template < - // `True` makes `operator()` a template so we can SFINAE on `Const`, - // which is non-deduced here. - bool True = true, - typename std::enable_if::type = 0> - ReturnType operator()(Args... args) { - return call_(data_, static_cast(args)...); - } - - /** - * Call the wrapped callable object with the specified arguments. - * If this `Function` object is not a const `folly::Function` object, - * this overload shall not participate in overload resolution. - */ - template < - // `True` makes `operator()` a template so we can SFINAE on `Const`, - // which is non-deduced here. - bool True = true, - typename std::enable_if::type = 0> - ReturnType operator()(Args... args) const { - return call_(data_, static_cast(args)...); - } + using Traits::operator(); /** * Exchanges the callable objects of `*this` and `that`. @@ -577,85 +678,149 @@ class Function final { return exec_(Op::HEAP, nullptr, nullptr); } + using typename Traits::SharedProxy; + + /** + * Move this `Function` into a copyable callable object, of which all copies + * share the state. + */ + SharedProxy asSharedProxy() && { + return SharedProxy{std::move(*this)}; + } + /** * Construct a `std::function` by moving in the contents of this `Function`. * Note that the returned `std::function` will share its state (i.e. captured * data) across all copies you make of it, so be very careful when copying. */ - std::function asStdFunction() && { - struct Impl { - std::shared_ptr sp_; - ReturnType operator()(Args&&... args) const { - return (*sp_)(static_cast(args)...); - } - }; - return Impl{std::make_shared(std::move(*this))}; + std::function asStdFunction() && { + return std::move(*this).asSharedProxy(); } }; -template -void swap( - Function& lhs, - Function& rhs) noexcept { +template +void swap(Function& lhs, Function& rhs) noexcept { lhs.swap(rhs); } -template -bool operator==(const Function& fn, std::nullptr_t) { +template +bool operator==(const Function& fn, std::nullptr_t) { return !fn; } -template -bool operator==(std::nullptr_t, const Function& fn) { +template +bool operator==(std::nullptr_t, const Function& fn) { return !fn; } -template -bool operator!=(const Function& fn, std::nullptr_t) { +template +bool operator!=(const Function& fn, std::nullptr_t) { return !(fn == nullptr); } -template -bool operator!=(std::nullptr_t, const Function& fn) { +template +bool operator!=(std::nullptr_t, const Function& fn) { return !(nullptr == fn); } /** - * NOTE: See detailed note about @constCastFunction at the top of the file. + * NOTE: See detailed note about `constCastFunction` at the top of the file. * This is potentially dangerous and requires the equivalent of a `const_cast`. */ template -Function constCastFunction( - Function&& that) noexcept { - Function fn{}; - that.exec_(detail::function::Op::MOVE, &that.data_, &fn.data_); - std::swap(fn.call_, that.call_); - std::swap(fn.exec_, that.exec_); - return fn; +Function constCastFunction( + Function&& that) noexcept { + return Function{std::move(that), + detail::function::CoerceTag{}}; } -template -Function constCastFunction( - Function&& that) noexcept { +template +Function constCastFunction( + Function&& that) noexcept { return std::move(that); } +/** + * @class FunctionRef + * + * @brief A reference wrapper for callable objects + * + * FunctionRef is similar to std::reference_wrapper, but the template parameter + * is the function signature type rather than the type of the referenced object. + * A folly::FunctionRef is cheap to construct as it contains only a pointer to + * the referenced callable and a pointer to a function which invokes the + * callable. + * + * The user of FunctionRef must be aware of the reference semantics: storing a + * copy of a FunctionRef is potentially dangerous and should be avoided unless + * the referenced object definitely outlives the FunctionRef object. Thus any + * function that accepts a FunctionRef parameter should only use it to invoke + * the referenced function and not store a copy of it. Knowing that FunctionRef + * itself has reference semantics, it is generally okay to use it to reference + * lambdas that capture by reference. + */ + template -struct MakeFunction {}; +class FunctionRef; template -struct MakeFunction { - using type = Function; -}; +class FunctionRef final { + using Call = ReturnType (*)(void*, Args&&...); -template -struct MakeFunction { - using type = Function; -}; -} // namespace impl + void* object_{nullptr}; + Call call_{&FunctionRef::uninitCall}; + + static ReturnType uninitCall(void*, Args&&...) { + throw std::bad_function_call(); + } -/* using override */ using impl::constCastFunction; + template + static ReturnType call(void* object, Args&&... args) { + return static_cast(detail::function::invoke( + *static_cast(object), static_cast(args)...)); + } -template -using Function = typename impl::MakeFunction::type; -} + public: + /** + * Default constructor. Constructs an empty FunctionRef. + * + * Invoking it will throw std::bad_function_call. + */ + FunctionRef() = default; + + /** + * Construct a FunctionRef from a reference to a callable object. + */ + template < + typename Fun, + typename std::enable_if< + !std::is_same::type>::value, + int>::type = 0> + /* implicit */ FunctionRef(Fun&& fun) noexcept { + using ReferencedType = typename std::remove_reference::type; + + static_assert( + std::is_convertible< + typename std::result_of::type, + ReturnType>::value, + "FunctionRef cannot be constructed from object with " + "incompatible function signature"); + + // `Fun` may be a const type, in which case we have to do a const_cast + // to store the address in a `void*`. This is safe because the `void*` + // will be cast back to `Fun*` (which is a const pointer whenever `Fun` + // is a const type) inside `FunctionRef::call` + object_ = const_cast(static_cast(std::addressof(fun))); + call_ = &FunctionRef::call; + } + + ReturnType operator()(Args... args) const { + return call_(object_, static_cast(args)...); + } + + explicit operator bool() const { + return object_; + } +}; + +} // namespace folly