diff --git a/zircon/system/ulib/fit/include/lib/fit/function.h b/zircon/system/ulib/fit/include/lib/fit/function.h index 57f117bdadd51af85fd04dc7fe2d72f1c582afb5..fe00ba454fdc8f53594b4a84e786454a1725d4bf 100644 --- a/zircon/system/ulib/fit/include/lib/fit/function.h +++ b/zircon/system/ulib/fit/include/lib/fit/function.h @@ -5,7 +5,11 @@ #ifndef LIB_FIT_FUNCTION_H_ #define LIB_FIT_FUNCTION_H_ +#include <memory> +#include <type_traits> + #include "function_internal.h" +#include "nullable.h" namespace fit { @@ -13,10 +17,6 @@ template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> class function_impl; -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -class callback_impl; - // The default size allowance for storing a target inline within a function // object, in bytes. This default allows for inline storage of targets // as big as two pointers, such as an object pointer and a pointer to a member @@ -25,15 +25,9 @@ constexpr size_t default_inline_target_size = sizeof(void*) * 2; // A |fit::function| is a move-only polymorphic function wrapper. // -// If you need a class with similar characteristics that also ensures -// "run-once" semantics (such as callbacks shared with timeouts, or for -// service requests with redundant, failover, or fallback service providers), -// see |fit::callback|. -// -// |fit::function<T>| behaves like |std::function<T>| except that it is -// move-only instead of copyable, so it can hold targets that cannot be copied, -// such as mutable lambdas, and immutable lambdas that capture move-only -// objects. +// |fit::function<T>| behaves like |std::function<T>| except that it is move-only +// instead of copyable so it can hold targets which cannot be copied, such as +// mutable lambdas. // // Targets of up to |inline_target_size| bytes in size (rounded up for memory // alignment) are stored inline within the function object without incurring @@ -51,206 +45,110 @@ constexpr size_t default_inline_target_size = sizeof(void*) * 2; // fit within a function without requiring heap allocation. // Defaults to |default_inline_target_size|. // -// Class members are documented in |fit::function_impl|, below. +// Class members are documented in |fit::function_impl|. // // EXAMPLES // // - https://fuchsia.googlesource.com/fuchsia/+/master/zircon/system/utest/fit/examples/function_example1.cpp // - https://fuchsia.googlesource.com/fuchsia/+/master/zircon/system/utest/fit/examples/function_example2.cpp // -template <typename T, - size_t inline_target_size = default_inline_target_size> -using function = function_impl<inline_target_size, - /*require_inline=*/false, T>; +template <typename T, size_t inline_target_size = default_inline_target_size> +using function = function_impl<inline_target_size, false, T>; -// A move-only callable object wrapper that forces callables to be stored inline +// A move-only callable object wrapper which forces callables to be stored inline // and never performs heap allocation. // -// Behaves just like |fit::function<T, inline_target_size>| except that -// attempting to store a target larger than |inline_target_size| will fail to -// compile. -template <typename T, - size_t inline_target_size = default_inline_target_size> -using inline_function = function_impl<inline_target_size, - /*require_inline=*/true, T>; +// Behaves just like |fit::function<T, inline_target_size>| except that attempting +// to store a target larger than |inline_target_size| will fail to compile. +template <typename T, size_t inline_target_size = default_inline_target_size> +using inline_function = function_impl<inline_target_size, true, T>; // Synonym for a function which takes no arguments and produces no result. using closure = function<void()>; -// A |fit::callback| is a move-only polymorphic function wrapper that also -// ensures "run-once" semantics (such as callbacks shared with timeouts, or for -// service requests with redundant, failover, or fallback service providers). -// A |fit::callback| releases it's resources after the first call, and can be -// inspected before calling, so a potential caller can know if it should call -// the function, or skip the call because the target was already called. -// -// If you need a move-only function class with typical function characteristics, -// that permits multiple invocations of the same function, see |fit::function|. -// -// |fit::callback<T>| behaves like |std::function<T>| except: -// -// 1. It is move-only instead of copyable, so it can hold targets that cannot -// be copied, such as mutable lambdas, and immutable lambdas that capture -// move-only objects. -// 2. On the first call to invoke a |fit::callback|, the target function held -// by the |fit::callback| cannot be called again. -// -// When a |fit::callback| is invoked for the first time, the target function is -// released and destructed, along with any resources owned by that function -// (typically the objects captured by a lambda). -// -// A |fit::callback| in the "already called" state has the same state as a -// |fit::callback| that has been assigned to |nullptr|. It can be compared to -// |nullptr| (via "==" or "!=", and its "operator bool()" returns false, which -// provides a convenient way to gate whether or not the |fit::callback| should -// be called. (Note that invoking an empty |fit::callback| or |fit::function| -// will cause a program abort!) -// -// As an example, sharing |fit::callback| between both a service and a timeout -// might look something like this: -// -// void service_with_timeout(fit::callback<void(bool)> cb, uint timeout_ms) { -// service_request([cb = std::move(cb.share())]() { if (cb) cb(false); }); -// on_timeout(timeout_ms, [cb = std::move(cb)]() { if (cb) cb(true); }); -// } -// -// Since |fit::callback| objects are move-only, and not copyable, duplicate -// references to the same |fit::callback| can be obtained via share(), as shown -// in the example above. This method converts the |fit::callback| into a -// reference-counted version of the |fit::callback| and returns a copy of the -// reference as another |fit::callback| with the same target function. -// -// What is notable about |fit::callback<T>.share()| is that invoking any shared -// copy will "nullify" all shared copies, as shown in the example. -// -// Note that |fit::callback| is NOT thread-safe by default. If multi-threaded -// support is required, you would need to implement your own mutex, or similar -// guard, before checking and calling a |fit::callback|. -// -// Targets of up to |inline_target_size| bytes in size (rounded up for memory -// alignment) are stored inline within the callback object without incurring -// any heap allocation. Larger callable objects will be moved to the heap as -// required. -// -// See also |fit::inline_callback<T, size>| for more control over allocation -// behavior. -// -// SYNOPSIS -// -// |T| is the callback's signature. e.g. void(int, std::string). -// -// |inline_target_size| is the minimum size of target that is guaranteed to -// fit within a callback without requiring heap allocation. -// Defaults to |default_inline_target_size|. -// -// Class members are documented in |fit::callback_impl|, below. -// -template <typename T, - size_t inline_target_size = default_inline_target_size> -using callback = callback_impl<inline_target_size, /*require_inline=*/false, T>; - -// A move-only, run-once, callable object wrapper that forces callables to be -// stored inline and never performs heap allocation. -// -// Behaves just like |fit::callback<T, inline_target_size>| except that -// attempting to store a target larger than |inline_target_size| will fail to -// compile. -template <typename T, - size_t inline_target_size = default_inline_target_size> -using inline_callback = callback_impl<inline_target_size, - /*require_inline=*/true, T>; - +// Function implementation details. +// See |fit::function| documentation for more information. template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> -class function_impl<inline_target_size, require_inline, Result(Args...)> - final : private ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)> { - - using base = ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)>; - - // function_base requires private access during share() - friend class ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)>; - - // supports target() for shared functions - friend const void* ::fit::internal::get_target_type_id<>( - const function_impl<inline_target_size, require_inline, - Result(Args...)>&); +class function_impl<inline_target_size, require_inline, Result(Args...)> final { + using ops_type = const ::fit::internal::target_ops<Result, Args...>*; + using storage_type = typename std::aligned_storage< + (inline_target_size >= sizeof(void*) + ? inline_target_size + : sizeof(void*))>::type; // avoid including <algorithm> just for max + template <typename Callable> + using target_type = ::fit::internal::target< + Callable, + (sizeof(Callable) <= sizeof(storage_type)), + Result, Args...>; + using null_target_type = target_type<decltype(nullptr)>; public: // The function's result type. - using typename base::result_type; + using result_type = Result; - // Initializes an empty (null) function. Attempting to call an empty - // function will abort the program. - function_impl() = default; + // // Creates a function with an empty target. + function_impl() { + initialize_null_target(); + } - // Creates a function with an empty target (same outcome as the default - // constructor). - function_impl(decltype(nullptr)) - : base(nullptr) {} + // Creates a function with an empty target. + function_impl(decltype(nullptr)) { + initialize_null_target(); + } // Creates a function bound to the specified function pointer. // If target == nullptr, assigns an empty target. - function_impl(Result (*target)(Args...)) - : base(target) {} + function_impl(Result (*target)(Args...)) { + initialize_target(target); + } // Creates a function bound to the specified callable object. // If target == nullptr, assigns an empty target. // - // For functors, we need to capture the raw type but also restrict on the - // existence of an appropriate operator () to resolve overloads and implicit - // casts properly. - // - // Note that specializations of this template method that take fit::callback - // objects as the target Callable are deleted (see below). + // For functors, we need to capture the raw type but also restrict on the existence of an + // appropriate operator () to resolve overloads and implicit casts properly. template <typename Callable, typename = std::enable_if_t< std::is_convertible< decltype(std::declval<Callable&>()( std::declval<Args>()...)), result_type>::value>> - function_impl(Callable target) - : base(std::move(target)) {} - - // Deletes the specializations of function_impl(Callable) that would allow - // a |fit::function| to be constructed from a |fit::callback|. This prevents - // unexpected behavior of a |fit::function| that would otherwise fail after - // one call. To explicitly allow this, simply wrap the |fit::callback| in a - // pass-through lambda before passing it to the |fit::function|. - template <size_t other_inline_target_size, bool other_require_inline> - function_impl(::fit::callback_impl<other_inline_target_size, - other_require_inline, - Result(Args...)>) = delete; + function_impl(Callable target) { + initialize_target(std::move(target)); + } // Creates a function with a target moved from another function, // leaving the other function with an empty target. - function_impl(function_impl&& other) - : base(static_cast<base&&>(other)) {} + function_impl(function_impl&& other) { + move_target_from(std::move(other)); + } // Destroys the function, releasing its target. - ~function_impl() = default; + ~function_impl() { + destroy_target(); + } - // Assigns the function to an empty target. Attempting to invoke the - // function will abort the program. + // Returns true if the function has a non-empty target. + explicit operator bool() const { + return ops_ != &null_target_type::ops; + } + + // Invokes the function's target. + // Aborts if the function's target is empty. + Result operator()(Args... args) const { + return ops_->invoke(&bits_, std::forward<Args>(args)...); + } + + // Assigns an empty target. function_impl& operator=(decltype(nullptr)) { - base::assign(nullptr); + destroy_target(); + initialize_null_target(); return *this; } - // Assigns the function to the specified callable object. If target == - // nullptr, assigns an empty target. - // - // For functors, we need to capture the raw type but also restrict on the - // existence of an appropriate operator () to resolve overloads and implicit - // casts properly. - // - // Note that specializations of this template method that take fit::callback - // objects as the target Callable are deleted (see below). + // Assigns the function's target. + // If target == nullptr, assigns an empty target. template <typename Callable, typename = std::enable_if_t< std::is_convertible< @@ -258,47 +156,51 @@ public: std::declval<Args>()...)), result_type>::value>> function_impl& operator=(Callable target) { - base::assign(std::move(target)); + destroy_target(); + initialize_target(std::move(target)); return *this; } - // Deletes the specializations of operator=(Callable) that would allow - // a |fit::function| to be assigned from a |fit::callback|. This - // prevents unexpected behavior of a |fit::function| that would otherwise - // fail after one call. To explicitly allow this, simply wrap the - // |fit::callback| in a pass-through lambda before assigning it to the - // |fit::function|. - template <size_t other_inline_target_size, bool other_require_inline> - function_impl& operator=(::fit::callback_impl<other_inline_target_size, - other_require_inline, - Result(Args...)>) = delete; - - // Move assignment + // Assigns the function with a target moved from another function, + // leaving the other function with an empty target. function_impl& operator=(function_impl&& other) { if (&other == this) return *this; - base::assign(static_cast<base&&>(other)); + destroy_target(); + move_target_from(std::move(other)); return *this; } // Swaps the functions' targets. void swap(function_impl& other) { - base::swap(other); + if (&other == this) + return; + ops_type temp_ops = ops_; + storage_type temp_bits; + ops_->move(&bits_, &temp_bits); + + ops_ = other.ops_; + other.ops_->move(&other.bits_, &bits_); + + other.ops_ = temp_ops; + temp_ops->move(&temp_bits, &other.bits_); } // Returns a pointer to the function's target. - using base::target; - - // Returns true if the function has a non-empty target. - using base::operator bool; + template <typename Callable> + Callable* target() { + check_target_type<Callable>(); + return static_cast<Callable*>(ops_->get(&bits_)); + } - // Invokes the function's target. - // Aborts if the function's target is empty. - Result operator()(Args... args) const { - return base::invoke(std::forward<Args>(args)...); + // Returns a pointer to the function's target. + template <typename Callable> + const Callable* target() const { + check_target_type<Callable>(); + return static_cast<Callable*>(ops_->get(&bits_)); } - // Returns a new function object that invokes the same target. + // Returns a new function object which invokes the same target. // The target itself is not copied; it is moved to the heap and its // lifetime is extended until all references have been released. // @@ -306,233 +208,101 @@ public: // because it may incur a heap allocation which is contrary to // the stated purpose of |fit::inline_function<>|. function_impl share() { - function_impl copy; - base::template share_with<function_impl>(copy); - return copy; + static_assert(!require_inline, "Inline functions cannot be shared."); + // TODO(jeffbrown): Replace shared_ptr with a better ref-count mechanism. + // TODO(jeffbrown): This definition breaks the client's ability to use + // |target()| because the target's type has changed. We could fix this + // by defining a new target type (and vtable) for shared targets + // although it would be nice to avoid memory overhead and code expansion + // when sharing is not used. + struct ref { + std::shared_ptr<function_impl> target; + Result operator()(Args... args) { + return (*target)(std::forward<Args>(args)...); + } + }; + if (ops_ != &target_type<ref>::ops) { + if (ops_ == &null_target_type::ops) { + return nullptr; + } + auto target = ref{std::make_shared<function_impl>(std::move(*this))}; + *this = std::move(target); + } + return function_impl(*static_cast<ref*>(ops_->get(&bits_))); } -}; - -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -void swap(function_impl<inline_target_size, require_inline, - Result, Args...>& a, - function_impl<inline_target_size, require_inline, - Result, Args...>& b) { - a.swap(b); -} - -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator==( - const function_impl<inline_target_size, require_inline, - Result, Args...>& f, - decltype(nullptr)) { - return !f; -} -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator==( - decltype(nullptr), - const function_impl<inline_target_size, require_inline, - Result, Args...>& f) { - return !f; -} -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator!=( - const function_impl<inline_target_size, require_inline, - Result, Args...>& f, - decltype(nullptr)) { - return !!f; -} -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator!=( - decltype(nullptr), - const function_impl<inline_target_size, require_inline, - Result, Args...>& f) { - return !!f; -} -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -class callback_impl<inline_target_size, require_inline, - Result(Args...)> - final : private ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)> { - - using base = ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)>; - - // function_base requires private access during share() - friend class ::fit::internal::function_base<inline_target_size, - require_inline, - Result(Args...)>; - - // supports target() for shared functions - friend const void* ::fit::internal::get_target_type_id<>( - const callback_impl<inline_target_size, require_inline, - Result(Args...)>&); + function_impl(const function_impl& other) = delete; + function_impl& operator=(const function_impl& other) = delete; -public: - // The callback function's result type. - using typename base::result_type; - - // Initializes an empty (null) callback. Attempting to call an empty - // callback will abort the program. - callback_impl() = default; - - // Creates a callback with an empty target (same outcome as the default - // constructor). - callback_impl(decltype(nullptr)) - : base(nullptr) {} - - // Creates a callback bound to the specified function pointer. - // If target == nullptr, assigns an empty target. - callback_impl(Result (*target)(Args...)) - : base(target) {} - - // Creates a callback bound to the specified callable object. - // If target == nullptr, assigns an empty target. - // - // For functors, we need to capture the raw type but also restrict on the - // existence of an appropriate operator () to resolve overloads and implicit - // casts properly. - template <typename Callable, - typename = std::enable_if_t< - std::is_convertible< - decltype(std::declval<Callable&>()( - std::declval<Args>()...)), - result_type>::value>> - callback_impl(Callable target) - : base(std::move(target)) {} - - // Creates a callback with a target moved from another callback, - // leaving the other callback with an empty target. - callback_impl(callback_impl&& other) - : base(static_cast<base&&>(other)) {} - - // Destroys the callback, releasing its target. - ~callback_impl() = default; - - // Assigns the callback to an empty target. Attempting to invoke the - // callback will abort the program. - callback_impl& operator=(decltype(nullptr)) { - base::assign(nullptr); - return *this; +private: + // assumes target is uninitialized + void initialize_null_target() { + ops_ = &null_target_type::ops; } - // Assigns the callback to the specified callable object. If target == - // nullptr, assigns an empty target. - // - // For functors, we need to capture the raw type but also restrict on the - // existence of an appropriate operator () to resolve overloads and implicit - // casts properly. - template <typename Callable, - typename = std::enable_if_t< - std::is_convertible< - decltype(std::declval<Callable&>()( - std::declval<Args>()...)), - result_type>::value>> - callback_impl& operator=(Callable target) { - base::assign(std::move(target)); - return *this; + // assumes target is uninitialized + template <typename Callable> + void initialize_target(Callable target) { + static_assert(!require_inline || sizeof(Callable) <= inline_target_size, + "Callable too large to store inline as requested."); + if (is_null(target)) { + initialize_null_target(); + } else { + ops_ = &target_type<Callable>::ops; + target_type<Callable>::initialize(&bits_, std::move(target)); + } } - // Move assignment - callback_impl& operator=(callback_impl&& other) { - if (&other == this) - return *this; - base::assign(static_cast<base&&>(other)); - return *this; + // leaves target uninitialized + void destroy_target() { + ops_->destroy(&bits_); } - // Swaps the callbacks' targets. - void swap(callback_impl& other) { - base::swap(other); + // leaves other target initialized to null + void move_target_from(function_impl&& other) { + ops_ = other.ops_; + other.ops_->move(&other.bits_, &bits_); + other.initialize_null_target(); } - // Returns a pointer to the callback's target. - using base::target; - - // Returns true if the callback has a non-empty target. - using base::operator bool; - - // Invokes the callback's target. - // Aborts if the callback's target is empty. - // |fit::callback| must be non-const to invoke. Before the target function - // is actually called, the fit::callback will be set to the default empty - // state (== nullptr, and operator bool() will subsequently return |false|). - // The target function will then be released after the function is called. - // If the callback was shared, any remaining copies will also be cleared. - Result operator()(Args... args) { - auto temp = std::move(*this); - return temp.invoke(std::forward<Args>(args)...); + template <typename Callable> + void check_target_type() const { + if (ops_ != &target_type<Callable>::ops) + abort(); } - // Returns a new callback object that invokes the same target. - // The target itself is not copied; it is moved to the heap and its - // lifetime is extended until all references have been released. - // For |fit::callback| (unlike fit::function), the first invocation of the - // callback will release all references to the target. All callbacks - // derived from the same original callback (via share()) will be cleared, - // as if set to |nullptr|, and "operator bool()" will return false. - // - // Note: This method is not supported on |fit::inline_function<>| - // because it may incur a heap allocation which is contrary to - // the stated purpose of |fit::inline_function<>|. - callback_impl share() { - callback_impl copy; - base::template share_with<callback_impl>(copy); - return copy; - } -}; + ops_type ops_; + mutable storage_type bits_; +}; // namespace fit -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -void swap(callback_impl<inline_target_size, require_inline, - Result, Args...>& a, - callback_impl<inline_target_size, require_inline, - Result, Args...>& b) { +template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> +void swap(function_impl<inline_target_size, require_inline, Result, Args...>& a, + function_impl<inline_target_size, require_inline, Result, Args...>& b) { a.swap(b); } -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator==( - const callback_impl<inline_target_size, require_inline, - Result, Args...>& f, - decltype(nullptr)) { +template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> +bool operator==(const function_impl<inline_target_size, require_inline, Result, Args...>& f, + decltype(nullptr)) { return !f; } -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator==( - decltype(nullptr), - const callback_impl<inline_target_size, require_inline, - Result, Args...>& f) { +template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> +bool operator==(decltype(nullptr), + const function_impl<inline_target_size, require_inline, Result, Args...>& f) { return !f; } -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator!=( - const callback_impl<inline_target_size, require_inline, - Result, Args...>& f, - decltype(nullptr)) { +template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> +bool operator!=(const function_impl<inline_target_size, require_inline, Result, Args...>& f, + decltype(nullptr)) { return !!f; } -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -bool operator!=( - decltype(nullptr), - const callback_impl<inline_target_size, require_inline, - Result, Args...>& f) { +template <size_t inline_target_size, bool require_inline, typename Result, typename... Args> +bool operator!=(decltype(nullptr), + const function_impl<inline_target_size, require_inline, Result, Args...>& f) { return !!f; } -// Returns a Callable object that invokes a member function of an object. +// Returns a Callable object which invokes a member function of an object. template <typename R, typename T, typename... Args> auto bind_member(T* instance, R (T::*fn)(Args...)) { return [instance, fn](Args... args) { diff --git a/zircon/system/ulib/fit/include/lib/fit/function_internal.h b/zircon/system/ulib/fit/include/lib/fit/function_internal.h index 6c78819b83f30c93a382f0da4a28330af9458977..ddcefef7e3bfa3cf795a61e2d7d9a60c71dce776 100644 --- a/zircon/system/ulib/fit/include/lib/fit/function_internal.h +++ b/zircon/system/ulib/fit/include/lib/fit/function_internal.h @@ -8,10 +8,6 @@ #include <stddef.h> #include <stdlib.h> -#include <memory> - -#include "nullable.h" - #include <new> #include <type_traits> #include <utility> @@ -21,30 +17,17 @@ namespace internal { template <typename Result, typename... Args> struct target_ops final { - const void* (*target_type_id)(void* bits, const void* ops); void* (*get)(void* bits); Result (*invoke)(void* bits, Args... args); void (*move)(void* from_bits, void* to_bits); void (*destroy)(void* bits); }; -template <typename Callable, - bool is_inline, bool is_shared, - typename Result, typename... Args> +template <typename Callable, bool is_inline, typename Result, typename... Args> struct target; -inline const void* unshared_target_type_id(void* bits, const void* ops) { - return ops; -} - -// vtable for nullptr (empty target function) - template <typename Result, typename... Args> -struct target<decltype(nullptr), - /*is_inline=*/true, /*is_shared=*/false, - Result, Args...> - final { - +struct target<decltype(nullptr), true, Result, Args...> final { static Result invoke(void* bits, Args... args) { abort(); } @@ -59,24 +42,14 @@ inline void null_target_move(void* from_bits, void* to_bits) {} inline void null_target_destroy(void* bits) {} template <typename Result, typename... Args> -constexpr target_ops<Result, Args...> target<decltype(nullptr), - /*is_inline=*/true, - /*is_shared=*/false, - Result, Args...>::ops = { - &unshared_target_type_id, +constexpr target_ops<Result, Args...> target<decltype(nullptr), true, Result, Args...>::ops = { &null_target_get, &target::invoke, &null_target_move, &null_target_destroy}; -// vtable for inline target function - template <typename Callable, typename Result, typename... Args> -struct target<Callable, - /*is_inline=*/true, /*is_shared=*/false, - Result, Args...> - final { - +struct target<Callable, true, Result, Args...> final { static void initialize(void* bits, Callable&& target) { new (bits) Callable(std::move(target)); } @@ -102,24 +75,14 @@ inline void* inline_target_get(void* bits) { } template <typename Callable, typename Result, typename... Args> -constexpr target_ops<Result, Args...> target<Callable, - /*is_inline=*/true, - /*is_shared=*/false, - Result, Args...>::ops = { - &unshared_target_type_id, +constexpr target_ops<Result, Args...> target<Callable, true, Result, Args...>::ops = { &inline_target_get, &target::invoke, &target::move, &target::destroy}; -// vtable for pointer to target function - template <typename Callable, typename Result, typename... Args> -struct target<Callable, - /*is_inline=*/false, /*is_shared=*/false, - Result, Args...> - final { - +struct target<Callable, false, Result, Args...> final { static void initialize(void* bits, Callable&& target) { auto ptr = static_cast<Callable**>(bits); *ptr = new Callable(std::move(target)); @@ -146,334 +109,13 @@ inline void* heap_target_get(void* bits) { } template <typename Callable, typename Result, typename... Args> -constexpr target_ops<Result, Args...> target<Callable, - /*is_inline=*/false, - /*is_shared=*/false, - Result, Args...>::ops = { - &unshared_target_type_id, +constexpr target_ops<Result, Args...> target<Callable, false, Result, Args...>::ops = { &heap_target_get, &target::invoke, &target::move, &target::destroy}; -// vtable for fit::function std::shared_ptr to target function - -template <typename SharedFunction> -const void* get_target_type_id(const SharedFunction& function_or_callback) { - return function_or_callback.target_type_id(); -} - -// For this vtable, -// Callable by definition will be either a fit::function or fit::callback -template <typename SharedFunction, typename Result, typename... Args> -struct target<SharedFunction, - /*is_inline=*/false, /*is_shared=*/true, - Result, Args...> - final { - - static void initialize(void* bits, SharedFunction target) { - new (bits) std::shared_ptr<SharedFunction>( - std::move(std::make_shared<SharedFunction>(std::move(target)))); - } - static void copy_shared_ptr(void* from_bits, void* to_bits) { - auto& from_shared_ptr = - *static_cast<std::shared_ptr<SharedFunction>*>(from_bits); - new (to_bits) std::shared_ptr<SharedFunction>(from_shared_ptr); - } - static const void* target_type_id(void* bits, const void* ops) { - auto& function_or_callback = - **static_cast<std::shared_ptr<SharedFunction>*>(bits); - return ::fit::internal::get_target_type_id(function_or_callback); - } - static void* get(void* bits) { - auto& function_or_callback = - **static_cast<std::shared_ptr<SharedFunction>*>(bits); - return function_or_callback.template target<SharedFunction>( - /*check=*/false); // void* will fail the check - } - static Result invoke(void* bits, Args... args) { - auto& function_or_callback = - **static_cast<std::shared_ptr<SharedFunction>*>(bits); - return function_or_callback(std::forward<Args>(args)...); - } - static void move(void* from_bits, void* to_bits) { - auto from_shared_ptr = std::move( - *static_cast<std::shared_ptr<SharedFunction>*>(from_bits)); - new (to_bits) std::shared_ptr<SharedFunction>( - std::move(from_shared_ptr)); - } - static void destroy(void* bits) { - static_cast<std::shared_ptr<SharedFunction>*>(bits)->reset(); - } - - static const target_ops<Result, Args...> ops; -}; - -template <typename SharedFunction, typename Result, typename... Args> -constexpr target_ops<Result, Args...> target<SharedFunction, - /*is_inline=*/false, - /*is_shared=*/true, - Result, Args...>::ops = { - &target::target_type_id, - &target::get, - &target::invoke, - &target::move, - &target::destroy}; - -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -class function_base; - -// Function implementation details. -// See |fit::function| and |fit::callback| documentation for more information. -template <size_t inline_target_size, bool require_inline, - typename Result, typename... Args> -class function_base<inline_target_size, require_inline, - Result(Args...)> { - - using ops_type = const target_ops<Result, Args...>*; - using storage_type = typename std::aligned_storage< - (inline_target_size >= sizeof(void*) - ? inline_target_size - : sizeof(void*))>::type; // avoid including <algorithm> for max - template <typename Callable> - using target_type = target< - Callable, - (sizeof(Callable) <= sizeof(storage_type)), - /*is_shared=*/false, - Result, Args...>; - template <typename SharedFunction> - using shared_target_type = target< - SharedFunction, - /*is_inline=*/false, - /*is_shared=*/true, - Result, Args...>; - using null_target_type = target_type<decltype(nullptr)>; - -protected: - using result_type = Result; - - function_base() { - initialize_null_target(); - } - - function_base(decltype(nullptr)) { - initialize_null_target(); - } - - function_base(Result (*target)(Args...)) { - initialize_target(target); - } - - template <typename Callable, - typename = std::enable_if_t< - std::is_convertible< - decltype(std::declval<Callable&>()( - std::declval<Args>()...)), - result_type>::value>> - function_base(Callable target) { - initialize_target(std::move(target)); - } - - function_base(function_base&& other) { - move_target_from(std::move(other)); - } - - ~function_base() { - destroy_target(); - } - - // Returns true if the function has a non-empty target. - explicit operator bool() const { - return ops_->get(&bits_) != nullptr; - } - - // Returns a pointer to the function's target. - // If |check| is true (the default), the function _may_ abort if the - // caller tries to assign the target to a varible of the wrong type. (This - // check is currently skipped for share()d objects.) - // Note the shared pointer vtable must set |check| to false to assign the - // target to |void*|. - template <typename Callable> - Callable* target(bool check = true) { - if (check) - check_target_type<Callable>(); - return static_cast<Callable*>(ops_->get(&bits_)); - } - - // Returns a pointer to the function's target (const version). - // If |check| is true (the default), the function _may_ abort if the - // caller tries to assign the target to a varible of the wrong type. (This - // check is currently skipped for share()d objects.) - // Note the shared pointer vtable must set |check| to false to assign the - // target to |void*|. - template <typename Callable> - const Callable* target(bool check = true) const { - if (check) - check_target_type<Callable>(); - return static_cast<Callable*>(ops_->get(&bits_)); - } - - // Used by the derived "impl" classes to implement share(). - // - // The caller creates a new object of the same type as itself, and passes in - // the empty object. This function first checks if |this| is already shared, - // and if not, creates a new version of itself containing a - // |std::shared_ptr| to its original self, and updates |ops_| to the vtable - // for the shared version. - // - // Then it copies its |shared_ptr| to the |bits_| of the given |copy|, - // and assigns the same shared pointer vtable to the copy's |ops_|. - // - // The target itself is not copied; it is moved to the heap and its - // lifetime is extended until all references have been released. - // - // Note: This method is not supported on |fit::inline_function<>| - // because it may incur a heap allocation which is contrary to - // the stated purpose of |fit::inline_function<>|. - template <typename SharedFunction> - void share_with(SharedFunction& copy) { - static_assert(!require_inline, "Inline functions cannot be shared."); - if (ops_->get(&bits_) != nullptr) { - if (ops_ != &shared_target_type<SharedFunction>::ops) { - convert_to_shared_target<SharedFunction>(); - } - copy_shared_target_to(copy); - } - } - - // Used by derived "impl" classes to implement operator()(). - // Invokes the function's target. - // Note that fit::callback will release the target immediately after - // invoke() (also affecting any share()d copies). - // Aborts if the function's target is empty. - Result invoke(Args... args) const { - return ops_->invoke(&bits_, std::forward<Args>(args)...); - } - - // Used by derived "impl" classes to implement operator=(). - // Assigns an empty target. - void assign(decltype(nullptr)) { - destroy_target(); - initialize_null_target(); - } - - // Used by derived "impl" classes to implement operator=(). - // Assigns the function's target. - // If target == nullptr, assigns an empty target. - template <typename Callable, - typename = std::enable_if_t< - std::is_convertible< - decltype(std::declval<Callable&>()( - std::declval<Args>()...)), - result_type>::value>> - void assign(Callable target) { - destroy_target(); - initialize_target(std::move(target)); - } - - // Used by derived "impl" classes to implement operator=(). - // Assigns the function with a target moved from another function, - // leaving the other function with an empty target. - void assign(function_base&& other) { - destroy_target(); - move_target_from(std::move(other)); - } - - void swap(function_base& other) { - if (&other == this) - return; - ops_type temp_ops = ops_; - storage_type temp_bits; - ops_->move(&bits_, &temp_bits); - - ops_ = other.ops_; - other.ops_->move(&other.bits_, &bits_); - - other.ops_ = temp_ops; - temp_ops->move(&temp_bits, &other.bits_); - } - - // returns an opaque ID unique to the |Callable| type of the target. - // Used by check_target_type. - const void* target_type_id() const { - return ops_->target_type_id(&bits_, ops_); - } - - // Deleted copy constructor and assign. |function_base| implementations are - // move-only. - function_base(const function_base& other) = delete; - function_base& operator=(const function_base& other) = delete; - -private: - // Implements the move operation, used by move construction and move - // assignment. Leaves other target initialized to null. - void move_target_from(function_base&& other) { - ops_ = other.ops_; - other.ops_->move(&other.bits_, &bits_); - other.initialize_null_target(); - } - - // fit::function and fit::callback are not directly copyable, but share() - // will create shared references to the original object. This method - // implements the copy operation for the |std::shared_ptr| wrapper. - template <typename SharedFunction> - void copy_shared_target_to(SharedFunction& copy) { - copy.destroy_target(); - assert(ops_ == &shared_target_type<SharedFunction>::ops); - shared_target_type<SharedFunction>::copy_shared_ptr( - &bits_, ©.bits_); - copy.ops_ = ops_; - } - - // assumes target is uninitialized - void initialize_null_target() { - ops_ = &null_target_type::ops; - } - - // assumes target is uninitialized - template <typename Callable> - void initialize_target(Callable target) { - static_assert(!require_inline || sizeof(Callable) <= inline_target_size, - "Callable too large to store inline as requested."); - if (is_null(target)) { - initialize_null_target(); - } else { - ops_ = &target_type<Callable>::ops; - target_type<Callable>::initialize(&bits_, std::move(target)); - } - } - - // assumes target is uninitialized - template <typename SharedFunction> - void convert_to_shared_target() { - shared_target_type<SharedFunction>::initialize( - &bits_, std::move(*static_cast<SharedFunction*>(this))); - ops_ = &shared_target_type<SharedFunction>::ops; - } - - // leaves target uninitialized - void destroy_target() { - ops_->destroy(&bits_); - } - - // Called by target() if |check| is true. - // Checks the template parameter, usually inferred from the context of - // the call to target(), and aborts the program if it can determine that - // the Callable type is not compatible with the function's Result and Args. - template <typename Callable> - void check_target_type() const { - if (target_type<Callable>::ops.target_type_id( - nullptr, &target_type<Callable>::ops) != target_type_id()) - abort(); - } - - ops_type ops_; - mutable storage_type bits_; -}; - } // namespace internal - } // namespace fit #endif // LIB_FIT_FUNCTION_INTERNAL_H_ diff --git a/zircon/system/utest/fit/function_tests.cpp b/zircon/system/utest/fit/function_tests.cpp index 84f2373c031be31ca678dd650419e2035353cc7f..957197706474a4b3c8346120e879a9870bde2adb 100644 --- a/zircon/system/utest/fit/function_tests.cpp +++ b/zircon/system/utest/fit/function_tests.cpp @@ -604,9 +604,7 @@ bool sharing() { int finlinevalue = 1; int finlinedestroy = 0; fit::function<Closure> finline = - [&finlinevalue, d = DestructionObserver(&finlinedestroy)] { - finlinevalue++; - }; + [&finlinevalue, d = DestructionObserver(&finlinedestroy)] { finlinevalue++; }; fit::function<Closure> finlineshare1 = finline.share(); fit::function<Closure> finlineshare2 = finline.share(); fit::function<Closure> finlineshare3 = finlineshare1.share(); @@ -639,9 +637,7 @@ bool sharing() { int fheapvalue = 1; int fheapdestroy = 0; fit::function<Closure> fheap = - [&fheapvalue, big = Big(), d = DestructionObserver(&fheapdestroy)] { - fheapvalue++; - }; + [&fheapvalue, big = Big(), d = DestructionObserver(&fheapdestroy)] { fheapvalue++; }; fit::function<Closure> fheapshare1 = fheap.share(); fit::function<Closure> fheapshare2 = fheap.share(); fit::function<Closure> fheapshare3 = fheapshare1.share(); @@ -671,26 +667,6 @@ bool sharing() { fheapshare1 = nullptr; EXPECT_EQ(1, fheapdestroy); - // target access now available after share() - using ClosureFunction = fit::function<Closure, HugeCallableSize>; - ClosureFunction fslot = SlotMachine{42}; - fslot(); - SlotMachine* fslottarget = fslot.template target<SlotMachine>(); - EXPECT_EQ(43, fslottarget->value); - - auto shared_fslot = fslot.share(); - shared_fslot(); - fslottarget = shared_fslot.template target<SlotMachine>(); - EXPECT_EQ(44, fslottarget->value); - fslot(); - EXPECT_EQ(45, fslottarget->value); - fslot = nullptr; - EXPECT_NULL(fslot.template target<decltype(nullptr)>()); - shared_fslot(); - EXPECT_EQ(46, fslottarget->value); - shared_fslot = nullptr; - EXPECT_NULL(shared_fslot.template target<decltype(nullptr)>()); - // These statements do not compile because inline functions cannot be shared #if 0 fit::inline_function<Closure> fbad; @@ -733,195 +709,12 @@ bool bind_member() { fit::bind_member(&obj, &Obj::Call)(); EXPECT_EQ(23, fit::bind_member(&obj, &Obj::AddOne)(22)); EXPECT_EQ(6, fit::bind_member(&obj, &Obj::Sum)(1, 2, 3)); - move_only_value = fit::bind_member(&obj, &Obj::AddAndReturn)( - std::move(move_only_value)); + move_only_value = fit::bind_member(&obj, &Obj::AddAndReturn)(std::move(move_only_value)); EXPECT_EQ(5, *move_only_value); EXPECT_EQ(3, obj.calls); END_TEST; } - -// We don't have std::shared in Zircon yet. -#ifndef FIT_NO_STD_FOR_ZIRCON_USERSPACE -bool callback_once() { - BEGIN_TEST; - - fit::callback<Closure> cbnull; - fit::callback<Closure> cbnullshare1 = cbnull.share(); - fit::callback<Closure> cbnullshare2 = cbnull.share(); - fit::callback<Closure> cbnullshare3 = cbnullshare1.share(); - EXPECT_FALSE(!!cbnull); - EXPECT_FALSE(!!cbnullshare1); - EXPECT_FALSE(!!cbnullshare2); - EXPECT_FALSE(!!cbnullshare3); - - int cbinlinevalue = 1; - int cbinlinedestroy = 0; - fit::callback<Closure> cbinline = - [&cbinlinevalue, d = DestructionObserver(&cbinlinedestroy)] { - cbinlinevalue++; - }; - EXPECT_TRUE(!!cbinline); - EXPECT_FALSE(cbinline == nullptr); - EXPECT_EQ(1, cbinlinevalue); - EXPECT_EQ(0, cbinlinedestroy); - cbinline(); // releases resources even if never shared - EXPECT_FALSE(!!cbinline); - EXPECT_TRUE(cbinline == nullptr); - EXPECT_EQ(2, cbinlinevalue); - EXPECT_EQ(1, cbinlinedestroy); - - cbinlinevalue = 1; - cbinlinedestroy = 0; - cbinline = - [&cbinlinevalue, d = DestructionObserver(&cbinlinedestroy)] { - cbinlinevalue++; - }; - fit::callback<Closure> cbinlineshare1 = cbinline.share(); - fit::callback<Closure> cbinlineshare2 = cbinline.share(); - fit::callback<Closure> cbinlineshare3 = cbinlineshare1.share(); - EXPECT_TRUE(!!cbinline); - EXPECT_TRUE(!!cbinlineshare1); - EXPECT_TRUE(!!cbinlineshare2); - EXPECT_TRUE(!!cbinlineshare3); - EXPECT_EQ(1, cbinlinevalue); - EXPECT_EQ(0, cbinlinedestroy); - cbinline(); - EXPECT_EQ(2, cbinlinevalue); - EXPECT_EQ(1, cbinlinedestroy); - EXPECT_FALSE(!!cbinline); - EXPECT_TRUE(cbinline == nullptr); - // cbinline(); // should abort - EXPECT_FALSE(!!cbinlineshare1); - EXPECT_TRUE(cbinlineshare1 == nullptr); - // cbinlineshare1(); // should abort - EXPECT_FALSE(!!cbinlineshare2); - // cbinlineshare2(); // should abort - EXPECT_FALSE(!!cbinlineshare3); - // cbinlineshare3(); // should abort - EXPECT_EQ(1, cbinlinedestroy); - cbinlineshare3 = nullptr; - EXPECT_EQ(1, cbinlinedestroy); - cbinline = nullptr; - EXPECT_EQ(1, cbinlinedestroy); - - int cbheapvalue = 1; - int cbheapdestroy = 0; - fit::callback<Closure> cbheap = - [&cbheapvalue, big = Big(), d = DestructionObserver(&cbheapdestroy)] { - cbheapvalue++; - }; - EXPECT_TRUE(!!cbheap); - EXPECT_FALSE(cbheap == nullptr); - EXPECT_EQ(1, cbheapvalue); - EXPECT_EQ(0, cbheapdestroy); - cbheap(); // releases resources even if never shared - EXPECT_FALSE(!!cbheap); - EXPECT_TRUE(cbheap == nullptr); - EXPECT_EQ(2, cbheapvalue); - EXPECT_EQ(1, cbheapdestroy); - - cbheapvalue = 1; - cbheapdestroy = 0; - cbheap = - [&cbheapvalue, big = Big(), d = DestructionObserver(&cbheapdestroy)] { - cbheapvalue++; - }; - fit::callback<Closure> cbheapshare1 = cbheap.share(); - fit::callback<Closure> cbheapshare2 = cbheap.share(); - fit::callback<Closure> cbheapshare3 = cbheapshare1.share(); - EXPECT_TRUE(!!cbheap); - EXPECT_TRUE(!!cbheapshare1); - EXPECT_TRUE(!!cbheapshare2); - EXPECT_TRUE(!!cbheapshare3); - EXPECT_EQ(1, cbheapvalue); - EXPECT_EQ(0, cbheapdestroy); - cbheap(); - EXPECT_EQ(2, cbheapvalue); - EXPECT_EQ(1, cbheapdestroy); - EXPECT_FALSE(!!cbheap); - EXPECT_TRUE(cbheap == nullptr); - // cbheap(); // should abort - EXPECT_FALSE(!!cbheapshare1); - EXPECT_TRUE(cbheapshare1 == nullptr); - // cbheapshare1(); // should abort - EXPECT_FALSE(!!cbheapshare2); - // cbheapshare2(); // should abort - EXPECT_FALSE(!!cbheapshare3); - // cbheapshare3(); // should abort - EXPECT_EQ(1, cbheapdestroy); - cbheapshare3 = nullptr; - EXPECT_EQ(1, cbheapdestroy); - cbheap = nullptr; - EXPECT_EQ(1, cbheapdestroy); - - // Verify new design, splitting out fit::callback, still supports - // assignment of move-only "Callables" (that is, lambdas made move-only - // because they capture a move-only object, like a fit::function, for - // example!) - fit::function<void()> fn_to_wrap = []() {}; - fit::function<void()> fn_from_lambda; - fn_from_lambda = [fn = fn_to_wrap.share()]() mutable { - fn(); - }; - - // Same test for fit::callback - fit::callback<void()> cb_to_wrap = []() {}; - fit::callback<void()> cb_from_lambda; - cb_from_lambda = [cb = std::move(cb_to_wrap)]() mutable { - cb(); - }; - - // |fit::function| objects can be constructed from or assigned from - // a |fit::callback|, if the result and arguments are compatible. - fit::function<Closure> fn = []() {}; - fit::callback<Closure> cb = []() {}; - fit::callback<Closure> cb_assign; - cb_assign = std::move(fn); - fit::callback<Closure> cb_construct = std::move(fn); - fit::callback<Closure> cb_share = fn.share(); - - static_assert(!std::is_convertible< - fit::function<void()>*, - fit::callback<void()>*>::value, - ""); - static_assert(!std::is_constructible< - fit::function<void()>, - fit::callback<void()>>::value, - ""); - static_assert(!std::is_assignable< - fit::function<void()>, - fit::callback<void()>>::value, - ""); - static_assert(!std::is_constructible< - fit::function<void()>, - decltype(cb.share())>::value, - ""); -#if 0 - // These statements do not compile because inline callbacks cannot be shared - fit::inline_callback<Closure> cbbad; - cbbad.share(); - - { - // Attempts to copy, move, or share a callback into a fit::function<> - // should not compile. This is verified by static_assert above, and - // was verified interactively using the compiler. - fit::callback<Closure> cb = []() {}; - fit::function<Closure> fn = []() {}; - fit::function<Closure> fn_assign; - fn_assign = cb; // BAD - fn_assign = std::move(cb); // BAD - fit::function<Closure> fn_construct = cb; // BAD - fit::function<Closure> fn_construct2 = std::move(cb); // BAD - fit::function<Closure> fn_share = cb.share(); // BAD - } - -#endif - - END_TEST; -} -#endif // FIT_NO_STD_FOR_ZIRCON_USERSPACE - } // namespace namespace test_conversions { @@ -956,9 +749,6 @@ static_assert(!std::is_convertible<void, fit::function<Closure>>::value, ""); static_assert(!std::is_convertible<void, fit::function<BinaryOp>>::value, ""); static_assert(!std::is_assignable<void, fit::function<Closure>>::value, ""); static_assert(!std::is_assignable<void, fit::function<BinaryOp>>::value, ""); - -static_assert(std::is_same<fit::function<BinaryOp>::result_type, int>::value, ""); -static_assert(std::is_same<fit::callback<BinaryOp>::result_type, int>::value, ""); } // namespace test_conversions BEGIN_TEST_CASE(function_tests) @@ -970,14 +760,13 @@ RUN_TEST((closure<fit::function<Closure, HugeCallableSize>>)) RUN_TEST((binary_op<fit::function<BinaryOp, HugeCallableSize>>)) RUN_TEST((closure<fit::inline_function<Closure, HugeCallableSize>>)) RUN_TEST((binary_op<fit::inline_function<BinaryOp, HugeCallableSize>>)) -RUN_TEST(sized_function_size_bounds) -RUN_TEST(inline_function_size_bounds) -RUN_TEST(move_only_argument_and_result) -RUN_TEST(implicit_construction) -RUN_TEST(overload_resolution) +RUN_TEST(sized_function_size_bounds); +RUN_TEST(inline_function_size_bounds); +RUN_TEST(move_only_argument_and_result); +RUN_TEST(implicit_construction); +RUN_TEST(overload_resolution); #ifndef FIT_NO_STD_FOR_ZIRCON_USERSPACE RUN_TEST(sharing) -RUN_TEST(callback_once) #endif RUN_TEST(bind_member); END_TEST_CASE(function_tests) diff --git a/zircon/system/utest/fit/traits_tests.cpp b/zircon/system/utest/fit/traits_tests.cpp index 80973ce4049b64a5b748479a00b8051ca1e0f34e..267836596786de937b80bb978fc68a29045416b5 100644 --- a/zircon/system/utest/fit/traits_tests.cpp +++ b/zircon/system/utest/fit/traits_tests.cpp @@ -108,10 +108,7 @@ static_assert(fit::is_callable<int (*)(float, bool)>::value, ""); static_assert(fit::is_callable<int (member_function_pointer_traits::Object::*)(float, bool)>::value, ""); static_assert(fit::is_callable<decltype(lambda_traits::lambda)>::value, ""); static_assert(fit::is_callable<mutable_functor_traits::MutableFunctor>::value, ""); - static_assert(fit::is_callable<fit::function<int(float, bool)>>::value, ""); -static_assert(fit::is_callable<fit::callback<int(float, bool)>>::value, ""); - static_assert(fit::is_callable<std::function<int(float, bool)>>::value, ""); } // namespace test_callables