#ifndef ENTT_SIGNAL_DELEGATE_HPP #define ENTT_SIGNAL_DELEGATE_HPP #include #include #include #include #include #include "../config/config.h" #include "../core/type_traits.hpp" #include "fwd.hpp" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); template constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); template constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); template constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); template constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); template using function_pointer_t = decltype(function_pointer(std::declval()...)); template [[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { return std::index_sequence_for{}; } } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @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) noexcept { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); if constexpr(std::is_invocable_r_v>...>) { return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); } else { constexpr auto offset = sizeof...(Args) - sizeof...(Index); return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); } }; } template [[nodiscard]] auto wrap(Type &, std::index_sequence) 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)); if constexpr(std::is_invocable_r_v>...>) { return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); } else { constexpr auto offset = sizeof...(Args) - sizeof...(Index); return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); } }; } template [[nodiscard]] auto wrap(Type *, std::index_sequence) 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)); if constexpr(std::is_invocable_r_v>...>) { return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); } else { constexpr auto offset = sizeof...(Args) - sizeof...(Index); 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() noexcept : instance{nullptr}, fn{nullptr} {} /** * @brief Constructs a delegate with a given object or payload, if any. * @tparam Candidate Function or member to connect to the delegate. * @tparam Type Type of class or type of payload, if any. * @param value_or_instance Optional valid object that fits the purpose. */ template delegate(connect_arg_t, Type &&...value_or_instance) 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) 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() noexcept { instance = 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) noexcept { instance = &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) noexcept { instance = 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) noexcept { ENTT_ASSERT(function != nullptr, "Uninitialized function pointer"); instance = payload; fn = function; } /** * @brief Resets a delegate. * * After a reset, a delegate cannot be invoked anymore. */ void reset() noexcept { instance = nullptr; fn = nullptr; } /** * @brief Returns a pointer to the stored callable function target, if any. * @return An opaque pointer to the stored callable function target. */ [[nodiscard]] function_type *target() const noexcept { return fn; } /** * @brief Returns the instance or the payload linked to a delegate, if any. * @return An opaque pointer to the underlying data. */ [[nodiscard]] const void *data() const noexcept { return instance; } /** * @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(instance, 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 noexcept { // no need to also test instance 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 noexcept { return fn == other.fn && instance == other.instance; } private: const void *instance; function_type *fn; }; /** * @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) 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; } // namespace entt #endif