diff --git a/docs/md/signal.md b/docs/md/signal.md index 1a2fbdf86..5b01a5c75 100644 --- a/docs/md/signal.md +++ b/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 them.
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 delegates: @@ -50,7 +51,7 @@ entt::delegate 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. +member 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, @@ -75,7 +76,8 @@ 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.
+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. 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 @@ -90,10 +92,25 @@ 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. +function type of the delegate is still `void(int)`. This is also the signature +of its function call operator. -To create and initialize a delegate at once, there are also some specialized +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.
+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); +``` + +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 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 -usual: +already shown in the examples above: ```cpp 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 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 -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 much by the presence of such a signal handler.
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(); ``` -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 result that is convertible to the given result type, everything works just fine. diff --git a/src/entt/signal/delegate.hpp b/src/entt/signal/delegate.hpp index 0900927de..32810a3f6 100644 --- a/src/entt/signal/delegate.hpp +++ b/src/entt/signal/delegate.hpp @@ -2,7 +2,9 @@ #define ENTT_SIGNAL_DELEGATE_HPP +#include #include +#include #include #include #include @@ -41,6 +43,20 @@ template auto to_function_pointer(Type Class:: *, const Class *) -> Type(*)(); +template +struct function_extent; + + +template +struct function_extent { + static constexpr auto value = sizeof...(Args); +}; + + +template +constexpr auto function_extent_v = function_extent::value; + + } @@ -85,7 +101,37 @@ class delegate; */ template class delegate { - using proto_fn_type = Ret(const void *, Args...); + using proto_fn_type = Ret(const void *, std::tuple); + + template + void connect(std::index_sequence) ENTT_NOEXCEPT { + data = nullptr; + + fn = +[](const void *, std::tuple args) -> Ret { + static_assert(std::is_invocable_r_v>...>); + // Ret(...) allows void(...) to eat return values and avoid errors + return Ret(std::invoke(Function, std::get(args)...)); + }; + } + + template + void connect(Type *value_or_instance, std::index_sequence) ENTT_NOEXCEPT { + data = value_or_instance; + + fn = +[](const void *payload, std::tuple args) -> Ret { + Type *curr = nullptr; + + if constexpr(std::is_const_v) { + curr = static_cast(payload); + } else { + curr = static_cast(const_cast(payload)); + } + + static_assert(std::is_invocable_r_v>...>); + // Ret(...) allows void(...) to eat return values and avoid errors + return Ret(std::invoke(Candidate, curr, std::get(args)...)); + }; + } public: /*! @brief Function type of the delegate. */ @@ -127,13 +173,8 @@ public: */ template void connect() ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - 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()))>; + connect(std::make_index_sequence{}); } /** @@ -153,21 +194,8 @@ public: */ template void connect(Type *value_or_instance) ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = value_or_instance; - - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = nullptr; - - if constexpr(std::is_const_v) { - curr = static_cast(payload); - } else { - curr = static_cast(const_cast(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(), std::declval()))>; + connect(value_or_instance, std::make_index_sequence{}); } /** @@ -204,7 +232,7 @@ public: */ Ret operator()(Args... args) const { ENTT_ASSERT(fn); - return fn(data, args...); + return fn(data, std::forward_as_tuple(args...)); } /** diff --git a/test/entt/signal/delegate.cpp b/test/entt/signal/delegate.cpp index 231b7da2c..939a9d6bc 100644 --- a/test/entt/signal/delegate.cpp +++ b/test/entt/signal/delegate.cpp @@ -277,3 +277,18 @@ TEST(Delegate, VoidVsNonVoidReturnType) { ASSERT_TRUE(member); ASSERT_TRUE(cmember); } + +TEST(Delegate, TheLessTheBetter) { + delegate_functor functor; + entt::delegate 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); +}