Browse Source

signal: review

Michele Caini 7 years ago
parent
commit
fe3f6aa22b

+ 21 - 15
docs/signal.md

@@ -90,12 +90,9 @@ signal.sink().connect<&listener::bar>(&instance);
 // disconnects a free function
 signal.sink().disconnect<&foo>();
 
-// disconnect a specific member function of an instance ...
+// disconnect a member function of an instance
 signal.sink().disconnect<&listener::bar>(&instance);
 
-// ... or an instance as a whole
-signal.sink().disconnect(&instance);
-
 // discards all the listeners at once
 signal.sink().disconnect();
 ```
@@ -187,10 +184,22 @@ my_struct instance;
 delegate.connect<&my_struct::f>(&instance);
 ```
 
-It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member
-function to clear it.<br/>
-To know if a delegate is empty, it can be used explicitly in every conditional
-statement:
+The delegate class accepts also data members if required. In this case, the
+function type of the delegate is such that the parameter list is empty and the
+value of the data member is at least convertible to the return type.<br/>
+Finally, it can work with invokable objects in general (lambdas or functors), as
+long as they are trivially destructible and their sizes fit the one of `void *`.
+As an example, a lambda that captures a pointer or an integer value can be used
+with a delegate:
+
+```cpp
+delegate.connect([value = int_var](int i) { return value * i; });
+```
+
+Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
+exists a `reset` member function to use to clear a delegate.<br/>
+To know if it's empty instead, the delegate can be used explicitly in every
+conditional statement:
 
 ```cpp
 if(delegate) {
@@ -268,11 +277,8 @@ In order to register an instance of a class to a dispatcher, its type must
 expose one or more member functions the arguments of which are such that
 `const E &` can be converted to them for each type of event `E`, no matter what
 the return value is.<br/>
-To ease the development, member functions that are named `receive` are
-automatically detected and have not to be explicitly specified when registered.
-In all the other cases, the name of the member function aimed to receive the
-event must be provided to the `connect` member function of the sink bound to the
-specific event:
+The name of the member function aimed to receive the event must be provided to
+the `connect` member function of the sink in charge for the specific event:
 
 ```cpp
 struct an_event { int value; };
@@ -287,7 +293,7 @@ struct listener
 // ...
 
 listener listener;
-dispatcher.sink<an_event>().connect(&listener);
+dispatcher.sink<an_event>().connect<&listener::receive>(&listener);
 dispatcher.sink<another_event>().connect<&listener::method>(&listener);
 ```
 
@@ -295,7 +301,7 @@ The `disconnect` member function follows the same pattern and can be used to
 selectively remove listeners:
 
 ```cpp
-dispatcher.sink<an_event>().disconnect(&listener);
+dispatcher.sink<an_event>().disconnect<&listener::receive>(&listener);
 dispatcher.sink<another_event>().disconnect<&listener::method>(&listener);
 ```
 

+ 87 - 28
src/entt/signal/delegate.hpp

@@ -25,16 +25,16 @@ template<typename Ret, typename... Args>
 auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
 
 
-template<typename Class, typename Ret, typename... Args>
-auto to_function_pointer(Ret(Class:: *)(Args...)) -> Ret(*)(Args...);
+template<typename Ret, typename... Args, typename Type, typename Value>
+auto to_function_pointer(Ret(*)(Type, Args...), Value) -> Ret(*)(Args...);
 
 
 template<typename Class, typename Ret, typename... Args>
-auto to_function_pointer(Ret(Class:: *)(Args...) const) -> Ret(*)(Args...);
+auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...);
 
 
-template<auto Func>
-using function_type = std::remove_pointer_t<decltype(to_function_pointer(Func))>;
+template<typename Class, typename Ret, typename... Args>
+auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...);
 
 
 }
@@ -80,7 +80,7 @@ class delegate;
  * @tparam Args Types of arguments of a function type.
  */
 template<typename Ret, typename... Args>
-class delegate<Ret(Args...)> final {
+class delegate<Ret(Args...)> {
     using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
     using proto_fn_type = Ret(storage_type &, Args...);
 
@@ -107,16 +107,32 @@ public:
     }
 
     /**
-     * @brief Constructs a delegate and connects a member function to it.
-     * @tparam Member Member function to connect to the delegate.
-     * @tparam Class Type of class to which the member function belongs.
-     * @param instance A valid instance of type pointer to `Class`.
+     * @brief Constructs a delegate and connects a member function for a given
+     * instance or a curried free function to it.
+     * @tparam Candidate Member function or curried free function to connect to
+     * the delegate.
+     * @tparam Type Type of class to which the member function belongs or type
+     * of value used for currying.
+     * @param value_or_instance A valid pointer to an instance of class type or
+     * the value to use for currying.
      */
-    template<auto Member, typename Class, typename = std::enable_if_t<std::is_member_function_pointer_v<decltype(Member)>>>
-    delegate(connect_arg_t<Member>, Class *instance) ENTT_NOEXCEPT
+    template<auto Candidate, typename Type>
+    delegate(connect_arg_t<Candidate>, Type value_or_instance) ENTT_NOEXCEPT
+        : delegate{}
+    {
+        connect<Candidate>(value_or_instance);
+    }
+
+    /**
+     * @brief Constructs a delegate and connects a lambda or a functor to it.
+     * @tparam Invokable Type of lambda or functor to connect.
+     * @param invokable A valid instance of the given type.
+     */
+    template<typename Invokable>
+    delegate(Invokable invokable) ENTT_NOEXCEPT
         : delegate{}
     {
-        connect<Member>(instance);
+        connect(std::move(invokable));
     }
 
     /**
@@ -168,6 +184,25 @@ public:
         };
     }
 
+    /**
+     * @brief Connects a lambda or a functor to a delegate.
+     * @tparam Invokable Type of lambda or functor to connect.
+     * @param invokable A valid instance of the given type.
+     */
+    template<typename Invokable>
+    void connect(Invokable invokable) ENTT_NOEXCEPT {
+        static_assert(sizeof(Invokable) < sizeof(void *));
+        static_assert(std::is_class_v<Invokable>);
+        static_assert(std::is_trivially_destructible_v<Invokable>);
+        static_assert(std::is_invocable_r_v<Ret, Invokable, Args...>);
+        new (&storage) Invokable{std::move(invokable)};
+
+        fn = [](storage_type &storage, Args... args) -> Ret {
+            Invokable &invokable = *reinterpret_cast<Invokable *>(&storage);
+            return std::invoke(invokable, args...);
+        };
+    }
+
     /**
      * @brief Resets a delegate.
      *
@@ -220,14 +255,17 @@ public:
     }
 
     /**
-     * @brief Checks if the contents of the two delegates are different.
+     * @brief Checks if the connected functions differ.
+     *
+     * In case of member functions, the instances connected to the delegate
+     * are not verified by this operator. Use the instance member function
+     * instead.
+     *
      * @param other Delegate with which to compare.
-     * @return True if the two delegates are identical, false otherwise.
+     * @return False if the connected functions differ, true otherwise.
      */
     bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
-        auto *lhs = reinterpret_cast<const unsigned char *>(&storage);
-        auto *rhs = reinterpret_cast<const unsigned char *>(&other.storage);
-        return fn == other.fn && std::equal(lhs, lhs + sizeof(storage_type), rhs);
+        return fn == other.fn;
     }
 
 private:
@@ -237,12 +275,16 @@ private:
 
 
 /**
- * @brief Checks if the contents of the two delegates are different.
+ * @brief Checks if the connected functions differ.
+ *
+ * In case of member functions, the instances connected to the delegate are not
+ * verified by this operator. Use the `instance` member function instead.
+ *
  * @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 delegates are different, false otherwise.
+ * @return True if the connected functions differ, false otherwise.
  */
 template<typename Ret, typename... Args>
 bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
@@ -253,27 +295,44 @@ bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)>
 /**
  * @brief Deduction guideline.
  *
- * It allows to deduce the function type of the delegate directly from the
+ * It allows to deduce the function type of the delegate directly from a
  * function provided to the constructor.
  *
  * @tparam Function A valid free function pointer.
  */
 template<auto Function>
-delegate(connect_arg_t<Function>) ENTT_NOEXCEPT -> delegate<internal::function_type<Function>>;
+delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
+-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Function))>>;
 
 
 /**
+ * @brief Deduction guideline.
+ *
+ * It allows to deduce the function type of the delegate directly from a member
+ * function or a curried free function provided to the constructor.
+ *
+ * @tparam Candidate Member function or curried free function to connect to
+ * the delegate.
+ * @tparam Type Type of class to which the member function belongs or type
+ * of value used for currying.
+ */
+template<auto Candidate, typename Type>
+delegate(connect_arg_t<Candidate>, Type) ENTT_NOEXCEPT
+-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type>()))>>;
 
+
+/**
  * @brief Deduction guideline.
  *
- * It allows to deduce the function type of the delegate directly from the
- * member function provided to the constructor.
+ * It allows to deduce the function type of the delegate directly from a lambda
+ * or a functor provided to the constructor.
  *
- * @tparam Member Member function to connect to the delegate.
- * @tparam Class Type of class to which the member function belongs.
+ * @tparam Invokable Type of lambda or functor to connect.
+ * @param invokable A valid instance of the given type.
  */
-template<auto Member, typename Class>
-delegate(connect_arg_t<Member>, Class *) ENTT_NOEXCEPT -> delegate<internal::function_type<Member>>;
+template<typename Invokable>
+delegate(Invokable invokable) ENTT_NOEXCEPT
+-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(&Invokable::operator(), &invokable))>>;
 
 
 }

+ 5 - 6
src/entt/signal/dispatcher.hpp

@@ -24,15 +24,14 @@ namespace entt {
  * type `const Event &` plus an extra list of arguments to forward with the
  * event itself, no matter what the return type is.
  *
- * Member functions named `receive` are automatically detected and registered or
- * unregistered by the dispatcher. The type of the instances is `Class *` (a
- * naked pointer). It means that users must guarantee that the lifetimes of the
- * instances overcome the one of the dispatcher itself to avoid crashes.
+ * The type of the instances is `Class *` (a naked pointer). It means that users
+ * must guarantee that the lifetimes of the instances overcome the one of the
+ * dispatcher itself to avoid crashes.
  *
  * @tparam Args Types of arguments to forward along with an event.
  */
 template<typename... Args>
-class dispatcher final {
+class dispatcher {
     using event_family = family<struct internal_dispatcher_event_family>;
 
     template<typename Class, typename Event>
@@ -44,7 +43,7 @@ class dispatcher final {
     };
 
     template<typename Event>
-    struct signal_wrapper final: base_wrapper {
+    struct signal_wrapper: base_wrapper {
         using signal_type = sigh<void(const Event &, Args...)>;
         using sink_type = typename signal_type::sink_type;
 

+ 2 - 2
src/entt/signal/emitter.hpp

@@ -49,7 +49,7 @@ class emitter {
     };
 
     template<typename Event>
-    struct event_handler final: base_handler {
+    struct event_handler: base_handler {
         using listener_type = std::function<void(const Event &, Derived &)>;
         using element_type = std::pair<bool, listener_type>;
         using container_type = std::list<element_type>;
@@ -145,7 +145,7 @@ public:
      * @tparam Event Type of event for which the connection is created.
      */
     template<typename Event>
-    struct connection final: private event_handler<Event>::connection_type {
+    struct connection: private event_handler<Event>::connection_type {
         /** @brief Event emitters are friend classes of connections. */
         friend class emitter;
 

+ 36 - 75
src/entt/signal/sigh.hpp

@@ -48,14 +48,14 @@ struct invoker<void(Args...), Collector> {
 
 
 template<typename Ret>
-struct null_collector final {
+struct null_collector {
     using result_type = Ret;
     bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
 };
 
 
 template<>
-struct null_collector<void> final {
+struct null_collector<void> {
     using result_type = void;
     bool operator()() const ENTT_NOEXCEPT { return true; }
 };
@@ -66,7 +66,7 @@ struct default_collector;
 
 
 template<typename Ret, typename... Args>
-struct default_collector<Ret(Args...)> final {
+struct default_collector<Ret(Args...)> {
     using collector_type = null_collector<Ret>;
 };
 
@@ -124,7 +124,7 @@ struct sigh;
  * @tparam Args Types of arguments of a function type.
  */
 template<typename Ret, typename... Args>
-class sink<Ret(Args...)> final {
+class sink<Ret(Args...)> {
     /*! @brief A signal is allowed to create sinks. */
     template<typename, typename>
     friend struct sigh;
@@ -151,42 +151,41 @@ public:
     }
 
     /**
-     * @brief Connects a member function for a given instance to a signal.
+     * @brief Connects a member function for a given instance or a curried free
+     * function to a signal.
      *
-     * The signal isn't responsible for the connected object. Users must
-     * guarantee that the lifetime of the instance overcomes the one of the
-     * signal. On the other side, the signal handler performs checks to avoid
-     * multiple connections for the same member function of a given instance.
+     * When used to connect a member function, the signal isn't responsible for
+     * the connected object. Users must guarantee that the lifetime of the
+     * instance overcomes the one of the delegate. On the other side, the signal
+     * handler performs checks to avoid multiple connections for the same member
+     * function of a given instance.<br/>
+     * When used to connect a curried free function, the linked value must be
+     * both trivially copyable and trivially destructible, other than such that
+     * its size is lower than or equal to the one of a `void *`. It means that
+     * all the primitive types are accepted as well as pointers. Moreover, the
+     * signature of the free function must be such that the value is the first
+     * argument before the ones used to define the delegate itself.
      *
-     * @tparam Member Member function to connect to the signal.
-     * @tparam Class Type of class to which the member function belongs.
-     * @param instance A valid instance of type pointer to `Class`.
+     * @tparam Candidate Member function or curried free function to connect to
+     * the signal.
+     * @tparam Type Type of class to which the member function belongs or type
+     * of value used for currying.
+     * @param value_or_instance A valid pointer to an instance of class type or
+     * the value to use for currying.
      */
-    template<auto Member, typename Class>
-    void connect(Class *instance) {
-        disconnect<Member>(instance);
+    template<auto Candidate, typename Type>
+    void connect(Type value_or_instance) {
+        if constexpr(std::is_class_v<std::remove_pointer_t<Type>>) {
+            disconnect<Candidate>(value_or_instance);
+        } else {
+            disconnect<Candidate>();
+        }
+
         delegate<Ret(Args...)> delegate{};
-        delegate.template connect<Member>(instance);
+        delegate.template connect<Candidate>(value_or_instance);
         calls->emplace_back(std::move(delegate));
     }
 
-    /**
-     * @brief Connects the `receive` member function for a given instance to a
-     * signal.
-     *
-     * The signal isn't responsible for the connected object. Users must
-     * guarantee that the lifetime of the instance overcomes the one of the
-     * signal. On the other side, the signal handler performs checks to avoid
-     * multiple connections for the same member function of a given instance.
-     *
-     * @tparam Class Type of class to which the member function belongs.
-     * @param instance A valid instance of type pointer to `Class`.
-     */
-    template<typename Class>
-    inline void connect(Class *instance) {
-        connect<&Class::receive>(instance);
-    }
-
     /**
      * @brief Disconnects a free function from a signal.
      * @tparam Function A valid free function pointer.
@@ -208,16 +207,9 @@ public:
     void disconnect(Class *instance) {
         delegate<Ret(Args...)> delegate{};
         delegate.template connect<Member>(instance);
-        calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
-    }
-
-    /**
-     * @brief Removes all existing connections for the given instance.
-     * @param instance An instance to be disconnected from the signal.
-     */
-    void disconnect(const void *instance) {
-        auto func = [instance](const auto &delegate) { return delegate.instance() == instance; };
-        calls->erase(std::remove_if(calls->begin(), calls->end(), std::move(func)), calls->end());
+        calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) {
+            return other == delegate && other.instance() == delegate.instance();
+        }), calls->end());
     }
 
     /**
@@ -256,7 +248,7 @@ private:
  * @tparam Collector Type of collector to use, if any.
  */
 template<typename Ret, typename... Args, typename Collector>
-struct sigh<Ret(Args...), Collector> final: private internal::invoker<Ret(Args...), Collector> {
+struct sigh<Ret(Args...), Collector>: private internal::invoker<Ret(Args...), Collector> {
     /*! @brief Unsigned integer type. */
     using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type;
     /*! @brief Collector type. */
@@ -341,42 +333,11 @@ struct sigh<Ret(Args...), Collector> final: private internal::invoker<Ret(Args..
         swap(lhs.calls, rhs.calls);
     }
 
-    /**
-     * @brief Checks if the contents of the two signals are identical.
-     *
-     * Two signals are identical if they have the same size and the same
-     * listeners registered exactly in the same order.
-     *
-     * @param other Signal with which to compare.
-     * @return True if the two signals are identical, false otherwise.
-     */
-    bool operator==(const sigh &other) const ENTT_NOEXCEPT {
-        return calls == other.calls;
-    }
-
 private:
     std::vector<delegate<Ret(Args...)>> calls;
 };
 
 
-/**
- * @brief Checks if the contents of the two signals are different.
- *
- * Two signals are identical if they have the same size and the same listeners
- * registered exactly in the same order.
- *
- * @tparam Ret Return type of a function type.
- * @tparam Args Types of arguments of a function type.
- * @param lhs A valid signal object.
- * @param rhs A valid signal object.
- * @return True if the two signals are different, false otherwise.
- */
-template<typename Ret, typename... Args>
-bool operator!=(const sigh<Ret(Args...)> &lhs, const sigh<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
-    return !(lhs == rhs);
-}
-
-
 }
 
 

+ 99 - 16
test/entt/signal/delegate.cpp

@@ -22,6 +22,9 @@ struct delegate_functor {
     int identity(int i) const {
         return i;
     }
+
+    static const int static_value = 3;
+    const int data_member = 42;
 };
 
 struct const_nonconst_noexcept {
@@ -69,6 +72,29 @@ TEST(Delegate, Functionalities) {
     ASSERT_EQ(ff_del, mf_del);
 }
 
+TEST(Delegate, DataMembers) {
+    entt::delegate<double()> delegate;
+    delegate_functor functor;
+
+    delegate.connect<&delegate_functor::data_member>(&functor);
+
+    ASSERT_EQ(delegate(), 42);
+}
+
+TEST(Delegate, LambdaAndFunctor) {
+    entt::delegate<int(int)> non_capturing_delegate;
+    entt::delegate<int(int)> capturing_delegate;
+    entt::delegate<int(int)> functor_delegate;
+
+    non_capturing_delegate.connect([](int v) { return v*v; });
+    capturing_delegate.connect([value = 5](int v) { return v*value; });
+    functor_delegate.connect(delegate_functor{});
+
+    ASSERT_EQ(non_capturing_delegate(3), 9);
+    ASSERT_EQ(capturing_delegate(3), 15);
+    ASSERT_EQ(functor_delegate(3), 6);
+}
+
 TEST(Delegate, Comparison) {
     entt::delegate<int(int)> lhs;
     entt::delegate<int(int)> rhs;
@@ -80,6 +106,20 @@ TEST(Delegate, Comparison) {
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
 
+    lhs.connect(functor);
+
+    ASSERT_EQ(lhs, entt::delegate<int(int)>{functor});
+    ASSERT_TRUE(lhs != rhs);
+    ASSERT_FALSE(lhs == rhs);
+    ASSERT_NE(lhs, rhs);
+
+    rhs.connect<>(functor);
+
+    ASSERT_EQ(rhs, entt::delegate<int(int)>{functor});
+    ASSERT_FALSE(lhs != rhs);
+    ASSERT_TRUE(lhs == rhs);
+    ASSERT_EQ(lhs, rhs);
+
     lhs.connect<&delegate_function>();
 
     ASSERT_EQ(lhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
@@ -89,7 +129,21 @@ TEST(Delegate, Comparison) {
 
     rhs.connect<&delegate_function>();
 
-    ASSERT_EQ(lhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
+    ASSERT_EQ(rhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
+    ASSERT_FALSE(lhs != rhs);
+    ASSERT_TRUE(lhs == rhs);
+    ASSERT_EQ(lhs, rhs);
+
+    lhs.connect<&curried_function_by_value>(0);
+
+    ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
+    ASSERT_TRUE(lhs != rhs);
+    ASSERT_FALSE(lhs == rhs);
+    ASSERT_NE(lhs, rhs);
+
+    rhs.connect<&curried_function_by_value>(0);
+
+    ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
@@ -103,7 +157,7 @@ TEST(Delegate, Comparison) {
 
     rhs.connect<&delegate_functor::operator()>(&functor);
 
-    ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&delegate_functor::operator()>, &functor}));
+    ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&delegate_functor::operator()>, &functor}));
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
@@ -111,9 +165,10 @@ TEST(Delegate, Comparison) {
     lhs.connect<&delegate_functor::operator()>(&other);
 
     ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&delegate_functor::operator()>, &other}));
-    ASSERT_TRUE(lhs != rhs);
-    ASSERT_FALSE(lhs == rhs);
-    ASSERT_NE(lhs, rhs);
+    ASSERT_NE(lhs.instance(), rhs.instance());
+    ASSERT_FALSE(lhs != rhs);
+    ASSERT_TRUE(lhs == rhs);
+    ASSERT_EQ(lhs, rhs);
 
     lhs.reset();
 
@@ -149,33 +204,34 @@ TEST(Delegate, ConstNonConstNoExcept) {
     ASSERT_EQ(functor.cnt, 4);
 }
 
-TEST(Delegate, Constructors) {
-    delegate_functor functor;
-    entt::delegate<int(int)> empty{};
-    entt::delegate<int(int)> func{entt::connect_arg<&delegate_function>};
-    entt::delegate<int(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
-
-    ASSERT_FALSE(empty);
-    ASSERT_TRUE(func);
-    ASSERT_TRUE(member);
-}
-
 TEST(Delegate, DeducedGuidelines) {
     const_nonconst_noexcept functor;
 
+    entt::delegate invokable_functor{delegate_functor{}};
+    entt::delegate invokable_lambda{[value = 2](double v) { return v * value; }};
     entt::delegate func_deduced{entt::connect_arg<&delegate_function>};
+    entt::delegate curried_func_by_ref_deduced{entt::connect_arg<&curried_function_by_ref>, 0};
+    entt::delegate curried_func_by_value_deduced{entt::connect_arg<&curried_function_by_value>, 0};
     entt::delegate member_f_deduced{entt::connect_arg<&const_nonconst_noexcept::f>, &functor};
     entt::delegate member_g_deduced{entt::connect_arg<&const_nonconst_noexcept::g>, &functor};
     entt::delegate member_h_deduced{entt::connect_arg<&const_nonconst_noexcept::h>, &functor};
     entt::delegate member_i_deduced{entt::connect_arg<&const_nonconst_noexcept::i>, &functor};
 
+    static_assert(std::is_same_v<typename decltype(invokable_functor)::function_type, int(int)>);
+    static_assert(std::is_same_v<typename decltype(invokable_lambda)::function_type, double(double)>);
     static_assert(std::is_same_v<typename decltype(func_deduced)::function_type, int(const int &)>);
+    static_assert(std::is_same_v<typename decltype(curried_func_by_ref_deduced)::function_type, int()>);
+    static_assert(std::is_same_v<typename decltype(curried_func_by_value_deduced)::function_type, int(int)>);
     static_assert(std::is_same_v<typename decltype(member_f_deduced)::function_type, void()>);
     static_assert(std::is_same_v<typename decltype(member_g_deduced)::function_type, void()>);
     static_assert(std::is_same_v<typename decltype(member_h_deduced)::function_type, void()>);
     static_assert(std::is_same_v<typename decltype(member_i_deduced)::function_type, void()>);
 
+    ASSERT_TRUE(invokable_functor);
+    ASSERT_TRUE(invokable_lambda);
     ASSERT_TRUE(func_deduced);
+    ASSERT_TRUE(curried_func_by_ref_deduced);
+    ASSERT_TRUE(curried_func_by_value_deduced);
     ASSERT_TRUE(member_f_deduced);
     ASSERT_TRUE(member_g_deduced);
     ASSERT_TRUE(member_h_deduced);
@@ -215,3 +271,30 @@ TEST(Delegate, CurriedFunctionByRef) {
     ASSERT_EQ(delegate(), 4);
     ASSERT_EQ(delegate(), 8);
 }
+
+TEST(Delegate, Constructors) {
+    delegate_functor functor;
+    entt::delegate<int(int)> empty{};
+    entt::delegate<int(int)> invokable{functor};
+    entt::delegate<int(int)> lambda{[value = 5](int v) { return value * v; }};
+    entt::delegate<int(int)> func{entt::connect_arg<&delegate_function>};
+    entt::delegate<int(int)> curr{entt::connect_arg<&curried_function_by_value>, 2};
+    entt::delegate<int(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
+
+    ASSERT_FALSE(empty);
+
+    ASSERT_TRUE(invokable);
+    ASSERT_EQ(6, invokable(3));
+
+    ASSERT_TRUE(lambda);
+    ASSERT_EQ(15, lambda(3));
+
+    ASSERT_TRUE(func);
+    ASSERT_EQ(9, func(3));
+
+    ASSERT_TRUE(curr);
+    ASSERT_EQ(5, curr(3));
+
+    ASSERT_TRUE(member);
+    ASSERT_EQ(6, member(3));
+}

+ 2 - 2
test/entt/signal/dispatcher.cpp

@@ -15,7 +15,7 @@ TEST(Dispatcher, Functionalities) {
     entt::dispatcher<int> dispatcher;
     receiver receiver;
 
-    dispatcher.sink<an_event>().connect(&receiver);
+    dispatcher.sink<an_event>().connect<&receiver::receive>(&receiver);
     dispatcher.trigger<an_event>(1);
     dispatcher.enqueue<an_event>();
     dispatcher.enqueue<another_event>();
@@ -33,7 +33,7 @@ TEST(Dispatcher, Functionalities) {
     an_event event{};
     const an_event &cevent = event;
 
-    dispatcher.sink<an_event>().disconnect(&receiver);
+    dispatcher.sink<an_event>().disconnect<&receiver::receive>(&receiver);
     dispatcher.trigger(an_event{}, 1);
     dispatcher.enqueue(event);
     dispatcher.update(1);

+ 3 - 50
test/entt/signal/sigh.cpp

@@ -69,54 +69,6 @@ TEST(SigH, Lifetime) {
     ASSERT_NO_THROW(delete new signal{});
 }
 
-TEST(SigH, Comparison) {
-    entt::sigh<void()> sig1;
-    entt::sigh<void()> sig2;
-
-    sigh_listener s1;
-    sigh_listener s2;
-
-    sig1.sink().connect<&sigh_listener::i>(&s1);
-    sig2.sink().connect<&sigh_listener::i>(&s2);
-
-    ASSERT_FALSE(sig1 == sig2);
-    ASSERT_TRUE(sig1 != sig2);
-
-    sig1.sink().disconnect<&sigh_listener::i>(&s1);
-    sig2.sink().disconnect<&sigh_listener::i>(&s2);
-
-    sig1.sink().connect<&sigh_listener::i>(&s1);
-    sig2.sink().connect<&sigh_listener::l>(&s1);
-
-    ASSERT_FALSE(sig1 == sig2);
-    ASSERT_TRUE(sig1 != sig2);
-
-    sig1.sink().disconnect<&sigh_listener::i>(&s1);
-    sig2.sink().disconnect<&sigh_listener::l>(&s1);
-
-    ASSERT_TRUE(sig1 == sig2);
-    ASSERT_FALSE(sig1 != sig2);
-
-    sig1.sink().connect<&sigh_listener::i>(&s1);
-    sig1.sink().connect<&sigh_listener::l>(&s1);
-    sig2.sink().connect<&sigh_listener::i>(&s1);
-    sig2.sink().connect<&sigh_listener::l>(&s1);
-
-    ASSERT_TRUE(sig1 == sig2);
-
-    sig1.sink().disconnect<&sigh_listener::i>(&s1);
-    sig1.sink().disconnect<&sigh_listener::l>(&s1);
-    sig2.sink().disconnect<&sigh_listener::i>(&s1);
-    sig2.sink().disconnect<&sigh_listener::l>(&s1);
-
-    sig1.sink().connect<&sigh_listener::i>(&s1);
-    sig1.sink().connect<&sigh_listener::l>(&s1);
-    sig2.sink().connect<&sigh_listener::l>(&s1);
-    sig2.sink().connect<&sigh_listener::i>(&s1);
-
-    ASSERT_FALSE(sig1 == sig2);
-}
-
 TEST(SigH, Clear) {
     entt::sigh<void(int &)> sigh;
     sigh.sink().connect<&sigh_listener::f>();
@@ -160,7 +112,7 @@ TEST(SigH, Functions) {
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(0), sigh.size());
-    ASSERT_EQ(0, v);
+    ASSERT_EQ(v, 0);
 
     sigh.sink().connect<&sigh_listener::f>();
 }
@@ -190,7 +142,8 @@ TEST(SigH, Members) {
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(2), sigh.size());
 
-    sigh.sink().disconnect(ptr);
+    sigh.sink().disconnect<&sigh_listener::g>(ptr);
+    sigh.sink().disconnect<&sigh_listener::h>(ptr);
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(0), sigh.size());