Parcourir la source

the delegate accepts now also functions with a shorter list of parameters

Michele Caini il y a 6 ans
Parent
commit
ce00eee29f
3 fichiers modifiés avec 96 ajouts et 40 suppressions
  1. 29 16
      docs/md/signal.md
  2. 52 24
      src/entt/signal/delegate.hpp
  3. 15 0
      test/entt/signal/delegate.cpp

+ 29 - 16
docs/md/signal.md

@@ -38,8 +38,9 @@ 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
 free functions and members provided along with an instance on which to invoke
 them.<br/>
 them.<br/>
 It does not claim to be a drop-in replacement for an `std::function`, so do not
 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.
+expect to use it whenever an `std::function` fits well. That said, it's most
+likely even a better fit than an `std::function` in a lot of cases, so expect to
+use it quite a lot anyway.
 
 
 The interface is trivial. It offers a default constructor to create empty
 The interface is trivial. It offers a default constructor to create empty
 delegates:
 delegates:
@@ -50,7 +51,7 @@ entt::delegate<int(int)> delegate{};
 
 
 All what is needed to create an instance is to specify the type of the function
 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
 the delegate will _contain_, that is the signature of the free function or the
-member function one wants to assign to it.
+member one wants to assign to it.
 
 
 Attempting to use an empty delegate by invoking its function call operator
 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,
 results in undefined behavior or most likely a crash. Before to use a delegate,
@@ -75,7 +76,8 @@ delegate.connect<&my_struct::f>(&instance);
 
 
 The delegate class accepts also data members, if needed. In this case, the
 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
 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/>
+value of the data member is at least convertible to the return type.
+
 Functions having type equivalent to `void(T *, args...)` are accepted as well.
 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
 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
 back every time it's invoked. In other terms, this works just fine with the
@@ -90,10 +92,25 @@ delegate(42);
 ```
 ```
 
 
 The function `g` will be invoked with a pointer to `c` and `42`. However, the
 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.
+function type of the delegate is still `void(int)`. This is also the signature
+of its function call operator.
+
+Another interesting aspect of the delegate class is that it accepts also
+functions with a list of parameters that is shorter than that of the function
+type used to specialize the delegate itself.<br/>
+This is a nice-to-have feature in a lot of cases. The following code is
+therefore perfectly valid:
+
+```cpp
+void g() { /* ... */ }
+delegate.connect<&g>();
+delegate(42);
+```
 
 
-To create and initialize a delegate at once, there are also some specialized
+Where the function type of the delegate is `void(int)` as above. It goes without
+saying that the extra arguments are silently discarded internally.
+
+To create and initialize a delegate at once, there are a few specialized
 constructors. Because of the rules of the language, the listener is provided by
 constructors. Because of the rules of the language, the listener is provided by
 means of the `entt::connect_arg` variable template:
 means of the `entt::connect_arg` variable template:
 
 
@@ -113,26 +130,22 @@ if(delegate) {
 ```
 ```
 
 
 Finally, to invoke a delegate, the function call operator is the way to go as
 Finally, to invoke a delegate, the function call operator is the way to go as
-usual:
+already shown in the examples above:
 
 
 ```cpp
 ```cpp
 auto ret = delegate(42);
 auto ret = delegate(42);
 ```
 ```
 
 
-As shown above, listeners do not have to strictly follow the signature of the
+In all cases, the listeners don't have to strictly follow the signature of the
 delegate. As long as a listener can be invoked with the given arguments to yield
 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
 a result that is convertible to the given result type, everything works just
 fine.
 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
-member functions. Listeners can be any kind of objects and users are in charge
-of connecting and disconnecting them from a signal to avoid crashes due to
+member. Listeners can be any kind of objects and users are in charge of
+connecting and disconnecting them from a signal to avoid crashes due to
 different lifetimes. On the other side, performance shouldn't be affected that
 different lifetimes. On the other side, performance shouldn't be affected that
 much by the presence of such a signal handler.<br/>
 much by the presence of such a signal handler.<br/>
 A signal handler can be used as a private data member without exposing any
 A signal handler can be used as a private data member without exposing any
@@ -192,7 +205,7 @@ signal.sink().disconnect<&listener::bar>(&instance);
 signal.sink().disconnect();
 signal.sink().disconnect();
 ```
 ```
 
 
-As shown above, listeners do not have to strictly follow the signature of the
+As shown above, the listeners don't have to strictly follow the signature of the
 signal. As long as a listener can be invoked with the given arguments to yield a
 signal. 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.
 result that is convertible to the given result type, everything works just fine.
 
 

+ 52 - 24
src/entt/signal/delegate.hpp

@@ -2,7 +2,9 @@
 #define ENTT_SIGNAL_DELEGATE_HPP
 #define ENTT_SIGNAL_DELEGATE_HPP
 
 
 
 
+#include <tuple>
 #include <cstring>
 #include <cstring>
+#include <utility>
 #include <algorithm>
 #include <algorithm>
 #include <functional>
 #include <functional>
 #include <type_traits>
 #include <type_traits>
@@ -41,6 +43,20 @@ template<typename Class, typename Type>
 auto to_function_pointer(Type Class:: *, const Class *) -> Type(*)();
 auto to_function_pointer(Type Class:: *, const Class *) -> Type(*)();
 
 
 
 
+template<typename>
+struct function_extent;
+
+
+template<typename Ret, typename... Args>
+struct function_extent<Ret(*)(Args...)> {
+    static constexpr auto value = sizeof...(Args);
+};
+
+
+template<typename Func>
+constexpr auto function_extent_v = function_extent<Func>::value;
+
+
 }
 }
 
 
 
 
@@ -85,7 +101,37 @@ class delegate;
  */
  */
 template<typename Ret, typename... Args>
 template<typename Ret, typename... Args>
 class delegate<Ret(Args...)> {
 class delegate<Ret(Args...)> {
-    using proto_fn_type = Ret(const void *, Args...);
+    using proto_fn_type = Ret(const void *, std::tuple<Args...>);
+
+    template<auto Function, std::size_t... Index>
+    void connect(std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        data = nullptr;
+
+        fn = +[](const void *, std::tuple<Args...> args) -> Ret {
+            static_assert(std::is_invocable_r_v<Ret, decltype(Function), std::tuple_element_t<Index, std::tuple<Args...>>...>);
+            // Ret(...) allows void(...) to eat return values and avoid errors
+            return Ret(std::invoke(Function, std::get<Index>(args)...));
+        };
+    }
+
+    template<auto Candidate, typename Type, std::size_t... Index>
+    void connect(Type *value_or_instance, std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        data = value_or_instance;
+
+        fn = +[](const void *payload, std::tuple<Args...> args) -> Ret {
+            Type *curr = nullptr;
+
+            if constexpr(std::is_const_v<Type>) {
+                curr = static_cast<Type *>(payload);
+            } else {
+                curr = static_cast<Type *>(const_cast<void *>(payload));
+            }
+
+            static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, std::tuple_element_t<Index, std::tuple<Args...>>...>);
+            // Ret(...) allows void(...) to eat return values and avoid errors
+            return Ret(std::invoke(Candidate, curr, std::get<Index>(args)...));
+        };
+    }
 
 
 public:
 public:
     /*! @brief Function type of the delegate. */
     /*! @brief Function type of the delegate. */
@@ -127,13 +173,8 @@ 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...>);
-        data = nullptr;
-
-        fn = [](const void *, Args... args) -> Ret {
-            // this allows void(...) to eat return values and avoid errors
-            return Ret(std::invoke(Function, args...));
-        };
+        constexpr auto extent = internal::function_extent_v<decltype(internal::to_function_pointer(std::declval<decltype(Function)>()))>;
+        connect<Function>(std::make_index_sequence<extent>{});
     }
     }
 
 
     /**
     /**
@@ -153,21 +194,8 @@ public:
      */
      */
     template<auto Candidate, typename Type>
     template<auto Candidate, typename Type>
     void connect(Type *value_or_instance) ENTT_NOEXCEPT {
     void connect(Type *value_or_instance) ENTT_NOEXCEPT {
-        static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>);
-        data = value_or_instance;
-
-        fn = [](const void *payload, Args... args) -> Ret {
-            Type *curr = nullptr;
-
-            if constexpr(std::is_const_v<Type>) {
-                curr = static_cast<Type *>(payload);
-            } else {
-                curr = static_cast<Type *>(const_cast<void *>(payload));
-            }
-
-            // this allows void(...) to eat return values and avoid errors
-            return Ret(std::invoke(Candidate, curr, args...));
-        };
+        constexpr auto extent = internal::function_extent_v<decltype(internal::to_function_pointer(std::declval<decltype(Candidate)>(), std::declval<Type *>()))>;
+        connect<Candidate>(value_or_instance, std::make_index_sequence<extent>{});
     }
     }
 
 
     /**
     /**
@@ -204,7 +232,7 @@ public:
      */
      */
     Ret operator()(Args... args) const {
     Ret operator()(Args... args) const {
         ENTT_ASSERT(fn);
         ENTT_ASSERT(fn);
-        return fn(data, args...);
+        return fn(data, std::forward_as_tuple(args...));
     }
     }
 
 
     /**
     /**

+ 15 - 0
test/entt/signal/delegate.cpp

@@ -277,3 +277,18 @@ TEST(Delegate, VoidVsNonVoidReturnType) {
     ASSERT_TRUE(member);
     ASSERT_TRUE(member);
     ASSERT_TRUE(cmember);
     ASSERT_TRUE(cmember);
 }
 }
+
+TEST(Delegate, TheLessTheBetter) {
+    delegate_functor functor;
+    entt::delegate<int(int, char)> delegate;
+
+    // int delegate_function(const int &);
+    delegate.connect<&delegate_function>();
+
+    ASSERT_EQ(delegate(3, 'c'), 9);
+
+    // int delegate_functor::operator()(int);
+    delegate.connect<&delegate_functor::operator()>(&functor);
+
+    ASSERT_EQ(delegate(3, 'c'), 6);
+}