Browse Source

review: delegate/sigh

Michele Caini 7 years ago
parent
commit
9fbdbc1844
5 changed files with 222 additions and 331 deletions
  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
 
 * [Introduction](#introduction)
-* [Signals](#signals)
 * [Delegate](#delegate)
-  * [Currying and free functions](#currying-and-free-functions)
+* [Signals](#signals)
 * [Event dispatcher](#event-dispatcher)
 * [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
 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
 
 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.
 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
 
 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 <cstring>
 #include <algorithm>
 #include <functional>
 #include <type_traits>
@@ -25,8 +26,8 @@ template<typename Ret, typename... 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>
@@ -73,16 +74,15 @@ class delegate;
  * in charge of disconnecting instances before deleting them.
  *
  * 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 Args Types of arguments of a function type.
  */
 template<typename Ret, typename... 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:
     /*! @brief Function type of the delegate. */
@@ -90,10 +90,8 @@ public:
 
     /*! @brief Default constructor. */
     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.
@@ -108,33 +106,18 @@ public:
 
     /**
      * @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>
-    delegate(connect_arg_t<Candidate>, Type value_or_instance) ENTT_NOEXCEPT
+    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(std::move(invokable));
-    }
-
     /**
      * @brief Connects a free function to a delegate.
      * @tparam Function A valid free function pointer.
@@ -142,64 +125,45 @@ public:
     template<auto Function>
     void connect() ENTT_NOEXCEPT {
         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>
-    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.
      */
     void reset() ENTT_NOEXCEPT {
-        new (&storage) void *{nullptr};
         fn = nullptr;
+        data = nullptr;
     }
 
     /**
      * @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.
      */
     const void * instance() const ENTT_NOEXCEPT {
-        return *reinterpret_cast<const void **>(&storage);
+        return data;
     }
 
     /**
@@ -242,7 +201,7 @@ public:
      */
     Ret operator()(Args... args) const {
         assert(fn);
-        return fn(storage, args...);
+        return fn(data, args...);
     }
 
     /**
@@ -257,8 +216,8 @@ public:
     /**
      * @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.
      * @return False if the connected functions differ, true otherwise.
@@ -268,16 +227,16 @@ public:
     }
 
 private:
-    mutable storage_type storage;
     proto_fn_type *fn;
+    const void *data;
 };
 
 
 /**
  * @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 Args Types of arguments of a function type.
@@ -308,29 +267,14 @@ delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
  * @brief Deduction guideline.
  *
  * 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>
-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>
     friend struct sigh;
 
+    template<typename Type>
+    Type * payload_type(Ret(*)(Type *, Args...));
+
     sink(std::vector<delegate<Ret(Args...)>> *calls) ENTT_NOEXCEPT
         : 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.template connect<Member>(instance);
+        delegate.template connect<Candidate>(value_or_instance);
         calls->emplace_back(std::move(delegate));
     }
 
@@ -186,12 +197,19 @@ public:
     template<auto Function>
     void disconnect() {
         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());
     }
 
     /**
-     * @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 Class Type of class to which the member function belongs.
      * @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;
 }
 
-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 {
@@ -81,46 +76,18 @@ TEST(Delegate, DataMembers) {
     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) {
     entt::delegate<int(int)> lhs;
     entt::delegate<int(int)> rhs;
     delegate_functor functor;
     delegate_functor other;
+    const int value = 0;
 
     ASSERT_EQ(lhs, entt::delegate<int(int)>{});
     ASSERT_FALSE(lhs != rhs);
     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>});
@@ -135,16 +102,16 @@ TEST(Delegate, Comparison) {
     ASSERT_TRUE(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_FALSE(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_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
@@ -180,7 +147,7 @@ TEST(Delegate, Comparison) {
 
     rhs.reset();
 
-    ASSERT_EQ(lhs, (entt::delegate<int(int)>{}));
+    ASSERT_EQ(rhs, (entt::delegate<int(int)>{}));
     ASSERT_FALSE(lhs != rhs);
     ASSERT_TRUE(lhs == rhs);
     ASSERT_EQ(lhs, rhs);
@@ -207,32 +174,24 @@ TEST(Delegate, ConstNonConstNoExcept) {
 
 TEST(Delegate, DeducedGuidelines) {
     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 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_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(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_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(curried_func_deduced);
     ASSERT_TRUE(member_f_deduced);
     ASSERT_TRUE(member_g_deduced);
     ASSERT_TRUE(member_h_deduced);
@@ -256,40 +215,27 @@ TEST(Delegate, ConstInstance) {
     ASSERT_EQ(delegate, entt::delegate<int(int)>{});
 }
 
-TEST(Delegate, CurriedFunctionByValue) {
+TEST(Delegate, CurriedFunction) {
     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_EQ(delegate(), 4);
-    ASSERT_EQ(delegate(), 8);
+    ASSERT_EQ(delegate(1), 4);
 }
 
 TEST(Delegate, Constructors) {
     delegate_functor functor;
+    const auto value = 2;
+
     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)> curr{entt::connect_arg<&curried_function>, &value};
     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));
 
@@ -299,3 +245,15 @@ TEST(Delegate, Constructors) {
     ASSERT_TRUE(member);
     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<>
 struct test_collect_all<void> {
     std::vector<int> vec{};
-    static void h() {}
+    static void h(const void *) {}
     bool operator()() noexcept {
         return true;
     }
@@ -39,7 +39,7 @@ struct test_collect_all<void> {
 template<typename Ret>
 struct test_collect_first {
     std::vector<Ret> vec{};
-    static int f() { return 42; }
+    static int f(const void *) { return 42; }
     bool operator()(Ret r) noexcept {
         vec.push_back(r);
         return false;
@@ -151,8 +151,9 @@ TEST(SigH, Members) {
 
 TEST(SigH, Collector) {
     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();
 
     ASSERT_FALSE(sigh_void.empty());
@@ -173,8 +174,8 @@ TEST(SigH, Collector) {
 
     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();
 
     ASSERT_FALSE(sigh_first.empty());