#ifndef ENTT_SIGNAL_DELEGATE_HPP #define ENTT_SIGNAL_DELEGATE_HPP #include #include #include #include #include #include "../core/type_traits.hpp" #include "../config/config.h" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template auto function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...); template auto function_pointer(Ret(*)(Type, Args...), Other &&) -> Ret(*)(Args...); template auto function_pointer(Ret(Class:: *)(Args...), Other &&...) -> Ret(*)(Args...); template auto function_pointer(Ret(Class:: *)(Args...) const, Other &&...) -> Ret(*)(Args...); template auto function_pointer(Type Class:: *, Other &&...) -> Type(*)(); template using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); template [[nodiscard]] constexpr auto index_sequence_for(Ret(*)(Args...)) { return std::index_sequence_for{}; } } /** * Internal details not to be documented. * @endcond */ /*! @brief Used to wrap a function or a member of a specified type. */ template struct connect_arg_t {}; /*! @brief Constant of type connect_arg_t used to disambiguate calls. */ template inline constexpr connect_arg_t connect_arg{}; /** * @brief Basic delegate implementation. * * Primary template isn't defined on purpose. All the specializations give a * compile-time error unless the template parameter is a function type. */ template class delegate; /** * @brief Utility class to use to send around functions and members. * * Unmanaged delegate for function pointers and members. Users of this class are * in charge of disconnecting instances before deleting them. * * A delegate can be used as a general purpose invoker without memory overhead * for free functions possibly with payloads and bound or unbound members. * * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. */ template class delegate { template [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); }; } template [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); }; } template [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); }; } public: /*! @brief Function type of the contained target. */ using function_type = Ret(const void *, Args...); /*! @brief Function type of the delegate. */ using type = Ret(Args...); /*! @brief Return type of the delegate. */ using result_type = Ret; /*! @brief Default constructor. */ delegate() ENTT_NOEXCEPT : fn{nullptr}, data{nullptr} {} /** * @brief Constructs a delegate and connects a free function or an unbound * member. * @tparam Candidate Function or member to connect to the delegate. */ template delegate(connect_arg_t) ENTT_NOEXCEPT { connect(); } /** * @brief Constructs a delegate and connects a free function with payload or * a bound member. * @tparam Candidate Function or member to connect to the delegate. * @tparam Type Type of class or type of payload. * @param value_or_instance A valid object that fits the purpose. */ template delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { connect(std::forward(value_or_instance)); } /** * @brief Constructs a delegate and connects an user defined function with * optional payload. * @param function Function to connect to the delegate. * @param payload User defined arbitrary data. */ delegate(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT { connect(function, payload); } /** * @brief Connects a free function or an unbound member to a delegate. * @tparam Candidate Function or member to connect to the delegate. */ template void connect() ENTT_NOEXCEPT { data = nullptr; if constexpr(std::is_invocable_r_v) { fn = [](const void *, Args... args) -> Ret { return Ret(std::invoke(Candidate, std::forward(args)...)); }; } else if constexpr(std::is_member_pointer_v) { fn = wrap(internal::index_sequence_for>>(internal::function_pointer_t{})); } else { fn = wrap(internal::index_sequence_for(internal::function_pointer_t{})); } } /** * @brief Connects a free function with payload or a bound member to a * delegate. * * The delegate isn't responsible for the connected object or the payload. * Users must always guarantee that the lifetime of the instance overcomes * the one of the delegate.
* When used to connect a free function with payload, its signature must be * such that the instance is the first argument before the ones used to * define the delegate itself. * * @tparam Candidate Function or member to connect to the delegate. * @tparam Type Type of class or type of payload. * @param value_or_instance A valid reference that fits the purpose. */ template void connect(Type &value_or_instance) ENTT_NOEXCEPT { data = &value_or_instance; if constexpr(std::is_invocable_r_v) { fn = [](const void *payload, Args... args) -> Ret { Type *curr = static_cast(const_cast *>(payload)); return Ret(std::invoke(Candidate, *curr, std::forward(args)...)); }; } else { fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); } } /** * @brief Connects a free function with payload or a bound member to a * delegate. * * @sa connect(Type &) * * @tparam Candidate Function or member to connect to the delegate. * @tparam Type Type of class or type of payload. * @param value_or_instance A valid pointer that fits the purpose. */ template void connect(Type *value_or_instance) ENTT_NOEXCEPT { data = value_or_instance; if constexpr(std::is_invocable_r_v) { fn = [](const void *payload, Args... args) -> Ret { Type *curr = static_cast(const_cast *>(payload)); return Ret(std::invoke(Candidate, curr, std::forward(args)...)); }; } else { fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); } } /** * @brief Connects an user defined function with optional payload to a * delegate. * * The delegate isn't responsible for the connected object or the payload. * Users must always guarantee that the lifetime of an instance overcomes * the one of the delegate.
* The payload is returned as the first argument to the target function in * all cases. * * @param function Function to connect to the delegate. * @param payload User defined arbitrary data. */ void connect(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT { fn = function; data = payload; } /** * @brief Resets a delegate. * * After a reset, a delegate cannot be invoked anymore. */ void reset() ENTT_NOEXCEPT { fn = nullptr; data = nullptr; } /** * @brief Returns the instance or the payload linked to a delegate, if any. * @return An opaque pointer to the underlying data. */ [[nodiscard]] const void * instance() const ENTT_NOEXCEPT { return data; } /** * @brief Triggers a delegate. * * The delegate invokes the underlying function and returns the result. * * @warning * Attempting to trigger an invalid delegate results in undefined * behavior. * * @param args Arguments to use to invoke the underlying function. * @return The value returned by the underlying function. */ Ret operator()(Args... args) const { ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); return fn(data, std::forward(args)...); } /** * @brief Checks whether a delegate actually stores a listener. * @return False if the delegate is empty, true otherwise. */ [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { // no need to test also data return !(fn == nullptr); } /** * @brief Compares the contents of two delegates. * @param other Delegate with which to compare. * @return False if the two contents differ, true otherwise. */ [[nodiscard]] bool operator==(const delegate &other) const ENTT_NOEXCEPT { return fn == other.fn && data == other.data; } private: function_type *fn; const void *data; }; /** * @brief Compares the contents of two delegates. * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. * @param lhs A valid delegate object. * @param rhs A valid delegate object. * @return True if the two contents differ, false otherwise. */ template [[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } /** * @brief Deduction guide. * @tparam Candidate Function or member to connect to the delegate. */ template delegate(connect_arg_t) -> delegate>>; /** * @brief Deduction guide. * @tparam Candidate Function or member to connect to the delegate. * @tparam Type Type of class or type of payload. */ template delegate(connect_arg_t, Type &&) -> delegate>>; /** * @brief Deduction guide. * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. */ template delegate(Ret(*)(const void *, Args...), const void * = nullptr) -> delegate; } #endif