Michele Caini пре 7 година
родитељ
комит
9fbdbc1844
5 измењених фајлова са 222 додато и 331 уклоњено
  1. 97 127
      docs/signal.md
  2. 55 111
      src/entt/signal/delegate.hpp
  3. 33 15
      src/entt/signal/sigh.hpp
  4. 31 73
      test/entt/signal/delegate.cpp
  5. 6 5
      test/entt/signal/sigh.cpp

+ 97 - 127
docs/signal.md

@@ -6,9 +6,8 @@
 # Table of Contents
 # Table of Contents
 
 
 * [Introduction](#introduction)
 * [Introduction](#introduction)
-* [Signals](#signals)
 * [Delegate](#delegate)
 * [Delegate](#delegate)
-  * [Currying and free functions](#currying-and-free-functions)
+* [Signals](#signals)
 * [Event dispatcher](#event-dispatcher)
 * [Event dispatcher](#event-dispatcher)
 * [Event emitter](#event-emitter)
 * [Event emitter](#event-emitter)
 <!--
 <!--
@@ -33,6 +32,102 @@ In case that the flexibility and potential of an `std::function` are not
 required or where you are looking for something different, `EnTT` offers a full
 required or where you are looking for something different, `EnTT` offers a full
 set of classes to solve completely different problems.
 set of classes to solve completely different problems.
 
 
+# Delegate
+
+A delegate can be used as a general purpose invoker with no memory overhead for
+free functions and members provided along with an instance on which to invoke
+them.<br/>
+It does not claim to be a drop-in replacement for an `std::function`, so do not
+expect to use it whenever an `std::function` fits well. However, it can be used
+to send opaque delegates around to be used to invoke functions as needed.
+
+The interface is trivial. It offers a default constructor to create empty
+delegates:
+
+```cpp
+entt::delegate<int(int)> delegate{};
+```
+
+All what is needed to create an instance is to specify the type of the function
+the delegate will _contain_, that is the signature of the free function or the
+member function one wants to assign to it.
+
+Attempting to use an empty delegate by invoking its function call operator
+results in undefined behavior or most likely a crash. Before to use a delegate,
+it must be initialized.<br/>
+There exists a bunch of overloads of the `connect` member function to do that.
+As an example of use:
+
+```cpp
+int f(int i) { return i; }
+
+struct my_struct {
+    int f(const int &i) { return i }
+};
+
+// bind a free function to the delegate
+delegate.connect<&f>();
+
+// bind a member function to the delegate
+my_struct instance;
+delegate.connect<&my_struct::f>(&instance);
+```
+
+The delegate class accepts also data members, if needed. 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/>
+Functions having type equivalent to `void(T *, args...)` are accepted as well.
+In this case, `T *` is considered a payload and the function will receive it
+back every time it's invoked. In other terms, this works just fine with the
+above definition:
+
+```cpp
+void g(const char *c, int i) { /* ... */ }
+const char c = 'c';
+
+delegate.connect<&g>(&c);
+delegate(42);
+```
+
+The function `g` will be invoked with a pointer to `c` and `42`. However, the
+function type of the delegate is still `void(int)`, mainly because this is also
+the signature of its function call operator.
+
+To create and initialize a delegate at once, there are also some specialized
+constructors. Because of the rules of the language, the listener is provided by
+means of the `entt::connect_arg` variable template:
+
+```cpp
+entt::delegate<int(int)> func{entt::connect_arg<&f>};
+```
+
+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 a delegate is empty, it can be used explicitly in every conditional
+statement:
+
+```cpp
+if(delegate) {
+    // ...
+}
+```
+
+Finally, to invoke a delegate, the function call operator is the way to go as
+usual:
+
+```cpp
+auto ret = delegate(42);
+```
+
+As shown above, listeners do not have to strictly follow the signature of the
+delegate. As long as a listener can be invoked with the given arguments to yield
+a result that is convertible to the given result type, everything works just
+fine.
+
+Probably too much small and pretty poor of functionalities, but the delegate
+class can help in a lot of cases and it has shown that it is worth keeping it
+within the library.
+
 # Signals
 # Signals
 
 
 Signal handlers work with naked pointers, function pointers and pointers to
 Signal handlers work with naked pointers, function pointers and pointers to
@@ -143,131 +238,6 @@ to which the return type of the listeners can be converted. Moreover, it has to
 return a boolean value that is false to stop collecting data, true otherwise.
 return a boolean value that is false to stop collecting data, true otherwise.
 This way one can avoid calling all the listeners in case it isn't necessary.
 This way one can avoid calling all the listeners in case it isn't necessary.
 
 
-# Delegate
-
-A delegate can be used as general purpose invoker with no memory overhead for
-free functions, members provided along with an instance on which to invoke them,
-lambdas and functors in general.<br/>
-It does not claim to be a drop-in replacement for an `std::function`, so do not
-expect to use it whenever an `std::function` fits well. However, it can be used
-to send opaque delegates around to be used to invoke functions as needed.
-
-The interface is trivial. It offers a default constructor to create empty
-delegates:
-
-```cpp
-entt::delegate<int(int)> delegate{};
-```
-
-All what is needed to create an instance is to specify the type of the function
-the delegate will _contain_, that is the signature of the free function or the
-member function one wants to assign to it.
-
-Attempting to use an empty delegate by invoking its function call operator
-results in undefined behavior or most likely a crash. Before to use a delegate,
-it must be initialized.<br/>
-There exists a bunch of overloads of the `connect` member function to do that.
-As an example of use:
-
-```cpp
-int f(int i) { return i; }
-
-struct my_struct {
-    int f(const int &i) { return i }
-};
-
-// bind a free function to the delegate
-delegate.connect<&f>();
-
-// bind a member function to the delegate
-my_struct instance;
-delegate.connect<&my_struct::f>(&instance);
-```
-
-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/>
-Moreover, it can work with invokable objects in general (lambdas or functors),
-as long as they are trivially destructible and the size fits 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; });
-```
-
-To create and initialize a delegate at once, there are also some specialized
-constructors. Because of the rules of the language, the listener is provided by
-means of the `entt::connect_arg` class template:
-
-```cpp
-entt::delegate<int(int)> func{entt::connect_arg<&f>};
-```
-
-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) {
-    // ...
-}
-```
-
-Finally, to invoke a delegate, the function call operator is the way to go as
-usual:
-
-```cpp
-auto ret = delegate(42);
-```
-
-As shown above, listeners do not have to strictly follow the signature of the
-delegate. As long as a listener can be invoked with the given arguments to yield
-a result that is convertible to the given result type, everything works just
-fine.
-
-Probably too much small and pretty poor of functionalities, but the delegate
-class can help in a lot of cases and it has shown that it is worth keeping it
-within the library.
-
-## Currying and free functions
-
-The delegate class comes with a rather obscure feature to be considered as an
-_advanced mode_, that is its limited support for curried free functions.
-
-Consider the following definition:
-
-```cpp
-entt::delegate<void(int)> delegate{};
-```
-
-As shown previously, it accepts functions having type `void(int)`. However, we
-can do something more in this case, because of how the delegate class is
-implemented internally (that is something that goes beyond the purposes of this
-document).<br/>
-In particular, the delegate accepts also functions having type equivalent to
-`void(T &, int)`, as long as `sizeof(T)` is lower than or equal to
-`sizeof(void *)`. The first parameter is stored directly by the delegate class
-and passed to the connected function when needed.
-
-In other terms, this works as well with the above definition:
-
-```cpp
-void g(char c, int i) { /* ... */ }
-
-delegate.connect<&g>('c');
-delegate(42);
-```
-
-In this case, the function `g` is invoked with parameters `'c'` and `42`.
-However, the function type of the delegate is still `void(int)`, mainly because
-this is also the signature of its function call operator.<br/>
-When the curried function receives the linked parameter by reference, it can
-modify it and the new value will be stored in place of the previous one. It's
-highly discouraged to accept the parameter by reference, unless you know exactly
-what you're doing. Prefer accepting it by value if possible.
-
 # Event dispatcher
 # Event dispatcher
 
 
 The event dispatcher class is designed so as to be used in a loop. It allows
 The event dispatcher class is designed so as to be used in a loop. It allows

+ 55 - 111
src/entt/signal/delegate.hpp

@@ -3,6 +3,7 @@
 
 
 
 
 #include <cassert>
 #include <cassert>
+#include <cstring>
 #include <algorithm>
 #include <algorithm>
 #include <functional>
 #include <functional>
 #include <type_traits>
 #include <type_traits>
@@ -25,8 +26,8 @@ template<typename Ret, typename... Args>
 auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
 auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
 
 
 
 
-template<typename Ret, typename... Args, typename Type, typename Value>
-auto to_function_pointer(Ret(*)(Type, Args...), Value) -> Ret(*)(Args...);
+template<typename Ret, typename... Args, typename Type>
+auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...);
 
 
 
 
 template<typename Class, typename Ret, typename... Args>
 template<typename Class, typename Ret, typename... Args>
@@ -73,16 +74,15 @@ class delegate;
  * in charge of disconnecting instances before deleting them.
  * in charge of disconnecting instances before deleting them.
  *
  *
  * A delegate can be used as general purpose invoker with no memory overhead for
  * A delegate can be used as general purpose invoker with no memory overhead for
- * free functions and members provided along with an instance on which to invoke
- * them. It comes also with limited support for curried functions.
+ * free functions (with or without payload) and members provided along with an
+ * instance on which to invoke them.
  *
  *
  * @tparam Ret Return type of a function type.
  * @tparam Ret Return type of a function type.
  * @tparam Args Types of arguments of a function type.
  * @tparam Args Types of arguments of a function type.
  */
  */
 template<typename Ret, typename... Args>
 template<typename Ret, typename... Args>
 class delegate<Ret(Args...)> {
 class delegate<Ret(Args...)> {
-    using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
-    using proto_fn_type = Ret(storage_type &, Args...);
+    using proto_fn_type = Ret(const void *, Args...);
 
 
 public:
 public:
     /*! @brief Function type of the delegate. */
     /*! @brief Function type of the delegate. */
@@ -90,10 +90,8 @@ public:
 
 
     /*! @brief Default constructor. */
     /*! @brief Default constructor. */
     delegate() ENTT_NOEXCEPT
     delegate() ENTT_NOEXCEPT
-        : storage{}, fn{nullptr}
-    {
-        new (&storage) void *{nullptr};
-    }
+        : fn{nullptr}, data{nullptr}
+    {}
 
 
     /**
     /**
      * @brief Constructs a delegate and connects a free function to it.
      * @brief Constructs a delegate and connects a free function to it.
@@ -108,33 +106,18 @@ public:
 
 
     /**
     /**
      * @brief Constructs a delegate and connects a member for a given instance
      * @brief Constructs a delegate and connects a member for a given instance
-     * or a curried free function to it.
-     * @tparam Candidate Member or curried free function to connect to the
-     * delegate.
-     * @tparam Type Type of class to which the member 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.
+     * or a free function with payload.
+     * @tparam Candidate Member or free function 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<auto Candidate, typename Type>
     template<auto Candidate, typename Type>
-    delegate(connect_arg_t<Candidate>, Type value_or_instance) ENTT_NOEXCEPT
+    delegate(connect_arg_t<Candidate>, Type *value_or_instance) ENTT_NOEXCEPT
         : delegate{}
         : delegate{}
     {
     {
         connect<Candidate>(value_or_instance);
         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(std::move(invokable));
-    }
-
     /**
     /**
      * @brief Connects a free function to a delegate.
      * @brief Connects a free function to a delegate.
      * @tparam Function A valid free function pointer.
      * @tparam Function A valid free function pointer.
@@ -142,64 +125,45 @@ public:
     template<auto Function>
     template<auto Function>
     void connect() ENTT_NOEXCEPT {
     void connect() ENTT_NOEXCEPT {
         static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>);
         static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>);
-        new (&storage) void *{nullptr};
+        data = nullptr;
 
 
-        fn = [](storage_type &, Args... args) -> Ret {
-            return std::invoke(Function, args...);
+        fn = [](const void *, Args... args) -> Ret {
+            // this allows void(...) to eat return values and avoid errors
+            return Ret(std::invoke(Function, args...));
         };
         };
     }
     }
 
 
     /**
     /**
-     * @brief Connects a member for a given instance or a curried free function
-     * to a delegate.
+     * @brief Connects a member function for a given instance or a free function
+     * with payload to a delegate.
      *
      *
-     * When used to connect a member, the delegate isn't responsible for the
-     * connected object. Users must guarantee that the lifetime of the instance
-     * overcomes the one of the delegate.<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.
+     * 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.<br/>
+     * 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 Member or curried free function to connect to the
-     * delegate.
-     * @tparam Type Type of class to which the member 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.
+     * @tparam Candidate Member or free function 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<auto Candidate, typename Type>
     template<auto Candidate, typename Type>
-    void connect(Type value_or_instance) ENTT_NOEXCEPT {
-        static_assert(sizeof(Type) <= sizeof(void *));
-        static_assert(std::is_trivially_copyable_v<Type>);
-        static_assert(std::is_trivially_destructible_v<Type>);
-        static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>);
-        new (&storage) Type{value_or_instance};
-
-        fn = [](storage_type &storage, Args... args) -> Ret {
-            Type &value_or_instance = *reinterpret_cast<Type *>(&storage);
-            return std::invoke(Candidate, value_or_instance, args...);
-        };
-    }
+    void connect(Type *value_or_instance) ENTT_NOEXCEPT {
+        static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>);
+        data = value_or_instance;
 
 
-    /**
-     * @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...);
+        fn = [](const void *data, Args... args) -> Ret {
+            Type *value_or_instance = nullptr;
+
+            if constexpr(std::is_const_v<Type>) {
+                value_or_instance = static_cast<Type *>(data);
+            } else {
+                value_or_instance = static_cast<Type *>(const_cast<void *>(data));
+            }
+
+            // this allows void(...) to eat return values and avoid errors
+            return Ret(std::invoke(Candidate, value_or_instance, args...));
         };
         };
     }
     }
 
 
@@ -209,21 +173,16 @@ public:
      * After a reset, a delegate cannot be invoked anymore.
      * After a reset, a delegate cannot be invoked anymore.
      */
      */
     void reset() ENTT_NOEXCEPT {
     void reset() ENTT_NOEXCEPT {
-        new (&storage) void *{nullptr};
         fn = nullptr;
         fn = nullptr;
+        data = nullptr;
     }
     }
 
 
     /**
     /**
      * @brief Returns the instance linked to a delegate, if any.
      * @brief Returns the instance linked to a delegate, if any.
-     *
-     * @warning
-     * Attempting to use an instance returned by a delegate that doesn't contain
-     * a pointer to a member results in undefined behavior.
-     *
      * @return An opaque pointer to the instance linked to the delegate, if any.
      * @return An opaque pointer to the instance linked to the delegate, if any.
      */
      */
     const void * instance() const ENTT_NOEXCEPT {
     const void * instance() const ENTT_NOEXCEPT {
-        return *reinterpret_cast<const void **>(&storage);
+        return data;
     }
     }
 
 
     /**
     /**
@@ -242,7 +201,7 @@ public:
      */
      */
     Ret operator()(Args... args) const {
     Ret operator()(Args... args) const {
         assert(fn);
         assert(fn);
-        return fn(storage, args...);
+        return fn(data, args...);
     }
     }
 
 
     /**
     /**
@@ -257,8 +216,8 @@ public:
     /**
     /**
      * @brief Checks if the connected functions differ.
      * @brief Checks if the connected functions differ.
      *
      *
-     * In case of members, the instances connected to the delegate are not
-     * verified by this operator. Use the `instance` member function instead.
+     * Instances connected to delegates are ignored by this operator. Use the
+     * `instance` member function instead.
      *
      *
      * @param other Delegate with which to compare.
      * @param other Delegate with which to compare.
      * @return False if the connected functions differ, true otherwise.
      * @return False if the connected functions differ, true otherwise.
@@ -268,16 +227,16 @@ public:
     }
     }
 
 
 private:
 private:
-    mutable storage_type storage;
     proto_fn_type *fn;
     proto_fn_type *fn;
+    const void *data;
 };
 };
 
 
 
 
 /**
 /**
  * @brief Checks if the connected functions differ.
  * @brief Checks if the connected functions differ.
  *
  *
- * In case of members, the instances connected to the delegate are not verified
- * by this operator. Use the `instance` member function instead.
+ * Instances connected to delegates are ignored by this operator. Use the
+ * `instance` member function instead.
  *
  *
  * @tparam Ret Return type of a function type.
  * @tparam Ret Return type of a function type.
  * @tparam Args Types of arguments of a function type.
  * @tparam Args Types of arguments of a function type.
@@ -308,29 +267,14 @@ delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
  * @brief Deduction guideline.
  * @brief Deduction guideline.
  *
  *
  * It allows to deduce the function type of the delegate directly from a member
  * It allows to deduce the function type of the delegate directly from a member
- * or a curried free function provided to the constructor.
+ * or a free function with payload provided to the constructor.
  *
  *
- * @tparam Candidate Member or curried free function to connect to the delegate.
- * @tparam Type Type of class to which the member belongs or type of value used
- * for currying.
+ * @tparam Candidate Member or free function to connect to the delegate.
+ * @tparam Type Type of class or type of payload.
  */
  */
 template<auto Candidate, typename Type>
 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 a lambda
- * or a functor provided to the constructor.
- *
- * @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<std::remove_pointer_t<decltype(internal::to_function_pointer(&Invokable::operator(), &invokable))>>;
+delegate(connect_arg_t<Candidate>, Type *) ENTT_NOEXCEPT
+-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type *>()))>>;
 
 
 
 
 }
 }

+ 33 - 15
src/entt/signal/sigh.hpp

@@ -129,6 +129,9 @@ class sink<Ret(Args...)> {
     template<typename, typename>
     template<typename, typename>
     friend struct sigh;
     friend struct sigh;
 
 
+    template<typename Type>
+    Type * payload_type(Ret(*)(Type *, Args...));
+
     sink(std::vector<delegate<Ret(Args...)>> *calls) ENTT_NOEXCEPT
     sink(std::vector<delegate<Ret(Args...)>> *calls) ENTT_NOEXCEPT
         : calls{calls}
         : calls{calls}
     {}
     {}
@@ -159,23 +162,31 @@ public:
     }
     }
 
 
     /**
     /**
-     * @brief Connects a member function to a signal.
+     * @brief Connects a member function or a free function with payload 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
-     * delegate. On the other side, the signal handler performs checks to avoid
-     * multiple connections for the same member function of a given instance.
+     * The signal 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. On the other side, the signal handler performs
+     * checks to avoid multiple connections for the same function.<br/>
+     * 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 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 or free function 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<auto Member, typename Class>
-    void connect(Class *instance) {
-        static_assert(std::is_member_function_pointer_v<decltype(Member)>);
-        disconnect<Member>(instance);
+    template<auto Candidate, typename Type>
+    void connect(Type *value_or_instance) {
+        if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) {
+            disconnect<Candidate>(value_or_instance);
+        } else {
+            disconnect<Candidate>();
+        }
+
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
-        delegate.template connect<Member>(instance);
+        delegate.template connect<Candidate>(value_or_instance);
         calls->emplace_back(std::move(delegate));
         calls->emplace_back(std::move(delegate));
     }
     }
 
 
@@ -186,12 +197,19 @@ public:
     template<auto Function>
     template<auto Function>
     void disconnect() {
     void disconnect() {
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
-        delegate.template connect<Function>();
+
+        if constexpr(std::is_invocable_r_v<Ret, decltype(Function), Args...>) {
+            delegate.template connect<Function>();
+        } else {
+            decltype(payload_type(Function)) payload = nullptr;
+            delegate.template connect<Function>(payload);
+        }
+
         calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
         calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
     }
     }
 
 
     /**
     /**
-     * @brief Disconnects the given member function from a signal.
+     * @brief Disconnects a given member function from a signal.
      * @tparam Member Member function to disconnect from the signal.
      * @tparam Member Member function to disconnect from the signal.
      * @tparam Class Type of class to which the member function belongs.
      * @tparam Class Type of class to which the member function belongs.
      * @param instance A valid instance of type pointer to `Class`.
      * @param instance A valid instance of type pointer to `Class`.

+ 31 - 73
test/entt/signal/delegate.cpp

@@ -5,13 +5,8 @@ int delegate_function(const int &i) {
     return i*i;
     return i*i;
 }
 }
 
 
-int curried_function_by_value(int i, int j) {
-    return i+j;
-}
-
-int curried_function_by_ref(int &value) {
-    value *= 2;
-    return value;
+int curried_function(const int *i, int j) {
+    return *i+j;
 }
 }
 
 
 struct delegate_functor {
 struct delegate_functor {
@@ -81,46 +76,18 @@ TEST(Delegate, DataMembers) {
     ASSERT_EQ(delegate(), 42);
     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;
-    const int value = 5;
-
-    non_capturing_delegate.connect([](int v) { return v*v; });
-    capturing_delegate.connect([val = &value](int v) { return v * *val; });
-    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) {
 TEST(Delegate, Comparison) {
     entt::delegate<int(int)> lhs;
     entt::delegate<int(int)> lhs;
     entt::delegate<int(int)> rhs;
     entt::delegate<int(int)> rhs;
     delegate_functor functor;
     delegate_functor functor;
     delegate_functor other;
     delegate_functor other;
+    const int value = 0;
 
 
     ASSERT_EQ(lhs, entt::delegate<int(int)>{});
     ASSERT_EQ(lhs, entt::delegate<int(int)>{});
     ASSERT_FALSE(lhs != rhs);
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(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>();
     lhs.connect<&delegate_function>();
 
 
     ASSERT_EQ(lhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
     ASSERT_EQ(lhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
@@ -135,16 +102,16 @@ TEST(Delegate, Comparison) {
     ASSERT_TRUE(lhs == rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
     ASSERT_EQ(lhs, rhs);
 
 
-    lhs.connect<&curried_function_by_value>(0);
+    lhs.connect<&curried_function>(&value);
 
 
-    ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
+    ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function>, &value}));
     ASSERT_TRUE(lhs != rhs);
     ASSERT_TRUE(lhs != rhs);
     ASSERT_FALSE(lhs == rhs);
     ASSERT_FALSE(lhs == rhs);
     ASSERT_NE(lhs, rhs);
     ASSERT_NE(lhs, rhs);
 
 
-    rhs.connect<&curried_function_by_value>(0);
+    rhs.connect<&curried_function>(&value);
 
 
-    ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
+    ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function>, &value}));
     ASSERT_FALSE(lhs != rhs);
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
     ASSERT_EQ(lhs, rhs);
@@ -180,7 +147,7 @@ TEST(Delegate, Comparison) {
 
 
     rhs.reset();
     rhs.reset();
 
 
-    ASSERT_EQ(lhs, (entt::delegate<int(int)>{}));
+    ASSERT_EQ(rhs, (entt::delegate<int(int)>{}));
     ASSERT_FALSE(lhs != rhs);
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
     ASSERT_EQ(lhs, rhs);
@@ -207,32 +174,24 @@ TEST(Delegate, ConstNonConstNoExcept) {
 
 
 TEST(Delegate, DeducedGuidelines) {
 TEST(Delegate, DeducedGuidelines) {
     const_nonconst_noexcept functor;
     const_nonconst_noexcept functor;
+    const int value = 0;
 
 
-    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 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 curried_func_deduced{entt::connect_arg<&curried_function>, &value};
     entt::delegate member_f_deduced{entt::connect_arg<&const_nonconst_noexcept::f>, &functor};
     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_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_h_deduced{entt::connect_arg<&const_nonconst_noexcept::h>, &functor};
     entt::delegate member_i_deduced{entt::connect_arg<&const_nonconst_noexcept::i>, &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(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(curried_func_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_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_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_h_deduced)::function_type, void()>);
     static_assert(std::is_same_v<typename decltype(member_i_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(func_deduced);
-    ASSERT_TRUE(curried_func_by_ref_deduced);
-    ASSERT_TRUE(curried_func_by_value_deduced);
+    ASSERT_TRUE(curried_func_deduced);
     ASSERT_TRUE(member_f_deduced);
     ASSERT_TRUE(member_f_deduced);
     ASSERT_TRUE(member_g_deduced);
     ASSERT_TRUE(member_g_deduced);
     ASSERT_TRUE(member_h_deduced);
     ASSERT_TRUE(member_h_deduced);
@@ -256,40 +215,27 @@ TEST(Delegate, ConstInstance) {
     ASSERT_EQ(delegate, entt::delegate<int(int)>{});
     ASSERT_EQ(delegate, entt::delegate<int(int)>{});
 }
 }
 
 
-TEST(Delegate, CurriedFunctionByValue) {
+TEST(Delegate, CurriedFunction) {
     entt::delegate<int(int)> delegate;
     entt::delegate<int(int)> delegate;
-    delegate.connect<&curried_function_by_value>(3);
-
-    ASSERT_TRUE(delegate);
-    ASSERT_EQ(delegate(1), 4);
-}
+    const auto value = 3;
 
 
-TEST(Delegate, CurriedFunctionByRef) {
-    entt::delegate<int()> delegate;
-    delegate.connect<&curried_function_by_ref>(2);
+    delegate.connect<&curried_function>(&value);
 
 
     ASSERT_TRUE(delegate);
     ASSERT_TRUE(delegate);
-    ASSERT_EQ(delegate(), 4);
-    ASSERT_EQ(delegate(), 8);
+    ASSERT_EQ(delegate(1), 4);
 }
 }
 
 
 TEST(Delegate, Constructors) {
 TEST(Delegate, Constructors) {
     delegate_functor functor;
     delegate_functor functor;
+    const auto value = 2;
+
     entt::delegate<int(int)> empty{};
     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)> func{entt::connect_arg<&delegate_function>};
-    entt::delegate<int(int)> curr{entt::connect_arg<&curried_function_by_value>, 2};
+    entt::delegate<int(int)> curr{entt::connect_arg<&curried_function>, &value};
     entt::delegate<int(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
     entt::delegate<int(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
 
 
     ASSERT_FALSE(empty);
     ASSERT_FALSE(empty);
 
 
-    ASSERT_TRUE(invokable);
-    ASSERT_EQ(6, invokable(3));
-
-    ASSERT_TRUE(lambda);
-    ASSERT_EQ(15, lambda(3));
-
     ASSERT_TRUE(func);
     ASSERT_TRUE(func);
     ASSERT_EQ(9, func(3));
     ASSERT_EQ(9, func(3));
 
 
@@ -299,3 +245,15 @@ TEST(Delegate, Constructors) {
     ASSERT_TRUE(member);
     ASSERT_TRUE(member);
     ASSERT_EQ(6, member(3));
     ASSERT_EQ(6, member(3));
 }
 }
+
+TEST(Delegate, VoidVsNonVoidReturnType) {
+    delegate_functor functor;
+
+    entt::delegate<void(int)> func{entt::connect_arg<&delegate_function>};
+    entt::delegate<void(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
+    entt::delegate<void(int)> cmember{entt::connect_arg<&delegate_functor::identity>, &std::as_const(functor)};
+
+    ASSERT_TRUE(func);
+    ASSERT_TRUE(member);
+    ASSERT_TRUE(cmember);
+}

+ 6 - 5
test/entt/signal/sigh.cpp

@@ -30,7 +30,7 @@ struct test_collect_all {
 template<>
 template<>
 struct test_collect_all<void> {
 struct test_collect_all<void> {
     std::vector<int> vec{};
     std::vector<int> vec{};
-    static void h() {}
+    static void h(const void *) {}
     bool operator()() noexcept {
     bool operator()() noexcept {
         return true;
         return true;
     }
     }
@@ -39,7 +39,7 @@ struct test_collect_all<void> {
 template<typename Ret>
 template<typename Ret>
 struct test_collect_first {
 struct test_collect_first {
     std::vector<Ret> vec{};
     std::vector<Ret> vec{};
-    static int f() { return 42; }
+    static int f(const void *) { return 42; }
     bool operator()(Ret r) noexcept {
     bool operator()(Ret r) noexcept {
         vec.push_back(r);
         vec.push_back(r);
         return false;
         return false;
@@ -151,8 +151,9 @@ TEST(SigH, Members) {
 
 
 TEST(SigH, Collector) {
 TEST(SigH, Collector) {
     entt::sigh<void(), test_collect_all<void>> sigh_void;
     entt::sigh<void(), test_collect_all<void>> sigh_void;
+    const void *fake_instance = nullptr;
 
 
-    sigh_void.sink().connect<&test_collect_all<void>::h>();
+    sigh_void.sink().connect<&test_collect_all<void>::h>(fake_instance);
     auto collector_void = sigh_void.collect();
     auto collector_void = sigh_void.collect();
 
 
     ASSERT_FALSE(sigh_void.empty());
     ASSERT_FALSE(sigh_void.empty());
@@ -173,8 +174,8 @@ TEST(SigH, Collector) {
 
 
     entt::sigh<int(), test_collect_first<int>> sigh_first;
     entt::sigh<int(), test_collect_first<int>> sigh_first;
 
 
-    sigh_first.sink().connect<&test_collect_first<int>::f>();
-    sigh_first.sink().connect<&test_collect_first<int>::f>();
+    sigh_first.sink().connect<&test_collect_first<int>::f>(fake_instance);
+    sigh_first.sink().connect<&test_collect_first<int>::f>(fake_instance);
     auto collector_first = sigh_first.collect();
     auto collector_first = sigh_first.collect();
 
 
     ASSERT_FALSE(sigh_first.empty());
     ASSERT_FALSE(sigh_first.empty());