Просмотр исходного кода

delegate: support for non-capturing lambda functions (see #495)

Michele Caini 5 лет назад
Родитель
Сommit
e57135eaab
3 измененных файлов с 154 добавлено и 10 удалено
  1. 80 5
      docs/md/signal.md
  2. 42 4
      src/entt/signal/delegate.hpp
  3. 32 1
      test/entt/signal/delegate.cpp

+ 80 - 5
docs/md/signal.md

@@ -7,6 +7,8 @@
 
 * [Introduction](#introduction)
 * [Delegate](#delegate)
+  * [Runtime arguments](#runtime-arguments)
+  * [Lambda support](#lambda-support)
 * [Signals](#signals)
 * [Event dispatcher](#event-dispatcher)
 * [Event emitter](#event-emitter)
@@ -63,7 +65,7 @@ As an example of use:
 int f(int i) { return i; }
 
 struct my_struct {
-    int f(const int &i) { return i }
+    int f(const int &i) const { return i; }
 };
 
 // bind a free function to the delegate
@@ -142,10 +144,10 @@ 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.
 
-As a side note, note that members of a class may or may not be associated with
-instances. If they are not, the first argument of the function type must be that
-of the class on which the members operate and an instance of this class must
-obviously be passed when invoking the delegate:
+As a side note, members of classes may or may not be associated with instances.
+If they are not, the first argument of the function type must be that of the
+class on which the members operate and an instance of this class must obviously
+be passed when invoking the delegate:
 
 ```
 entt::delegate<void(my_struct &, int)> delegate;
@@ -160,6 +162,79 @@ argument doesn't necessarily have to be a reference (for example, it can be a
 pointer, as well as a const reference).<br/>
 Therefore, the function type must be declared explicitly for unbound members.
 
+## Runtime arguments
+
+The `delegate` class is meant to be used primarily with template arguments.
+However, as a consequence of its design, it can also offer minimal support for
+runtime arguments.<br/>
+When used in this modality, some feature aren't supported though. In particular:
+
+* Curried functions aren't accepted.
+* Functions with an argument list that differs from that of the delegate aren't
+  supported.
+* Return type and types of arguments **must** coincide with those of the
+  delegate and _being at least convertible_ isn't enough anymore.
+
+Moreover, for a given function type `Ret(Args...)`, the signature of the
+functions connected at runtime must necessarily be `Ret(const void *, Args...)`.
+
+Runtime arguments can be passed both to the constructor of a delegate and to the
+`connect` member function. An optional parameter is also accepted in both cases.
+This argument is used to pass arbitrary user data back and forth as a
+`const void *` upon invocation.<br/>
+To connect a function to a delegate _in the hard way_:
+
+```cpp
+int func(const void *ptr, int i) { return *static_cast<const int *>(ptr) * i; }
+const int value = 42;
+
+// use the constructor ...
+entt::delegate delegate{&func, &value};
+
+// ... or the connect member function
+delegate.connect(&func, &value);
+```
+
+The type of the delegate is deduced from the function if possible. In this case,
+since the first argument is an implementation detail, the deduced function type
+is `int(int)`.<br/>
+Invoking a delegate built in this way follows the same rules as previously
+explained.
+
+## Lambda support
+
+In general, the `delegate` class doesn't fully support lambda functions in all
+their nuances. The reason is pretty simple: a `delegate` isn't a drop-in
+replacement for an `std::function`. Instead, it tries to overcome the problems
+with the latter.<br/>
+That being said, non-capturing lambda functions are supported, even though some
+feature aren't available in this case.
+
+This is a logical consequence of the support for connecting functions at
+runtime. Therefore, lambda functions undergo the same rules and
+limitations.<br/>
+In fact, since non-capturing lambda functions decay to pointers to functions,
+they can be used with a `delegate` as if they were _normal functions_ with
+optional payload:
+
+```cpp
+my_struct instance;
+
+// use the constructor ...
+entt::delegate delegate{+[](const void *ptr, int value) {
+    return static_cast<const my_struct *>(ptr)->f(value);
+}, &instance};
+
+// ... or the connect member function
+delegate.connect([](const void *ptr, int value) {
+    return static_cast<const my_struct *>(ptr)->f(value);
+}, &instance);
+```
+
+As above, the first parameter (`const void *`) isn't part of the function type
+of the delegate and is used to dispatch arbitrary user data back and forth. In
+other terms, the function type of the delegate above is `int(int)`.
+
 # Signals
 
 Signal handlers work with references to classes, function pointers and pointers

+ 42 - 4
src/entt/signal/delegate.hpp

@@ -95,8 +95,6 @@ class delegate;
  */
 template<typename Ret, typename... Args>
 class delegate<Ret(Args...)> {
-    using proto_fn_type = Ret(const void *, Args...);
-
     template<auto Candidate, std::size_t... Index>
     auto wrap(std::index_sequence<Index...>) ENTT_NOEXCEPT {
         return [](const void *, Args... args) -> Ret {
@@ -124,8 +122,12 @@ class delegate<Ret(Args...)> {
     }
 
 public:
+    /*! @brief Function type of the expected target. */
+    using target_type = Ret(const void *, Args...);
     /*! @brief Function type of the delegate. */
     using function_type = Ret(Args...);
+    /*! @brief Return type of the delegate. */
+    using result_type = Ret;
 
     /*! @brief Default constructor. */
     delegate() ENTT_NOEXCEPT
@@ -158,6 +160,18 @@ public:
         connect<Candidate>(std::forward<Type>(value_or_instance));
     }
 
+    /**
+     * @brief Constructs a delegate and connects an user defined function with
+     * optional payload.
+     * @param function Function to connect to the delegate.
+     * @param payload User defined arbitrary data.
+     */
+    delegate(target_type *function, const void *payload = nullptr) ENTT_NOEXCEPT
+        : delegate{}
+    {
+        connect(function, payload);
+    }
+
     /**
      * @brief Connects a free function or an unbound member to a delegate.
      * @tparam Candidate Function or member to connect to the delegate.
@@ -183,7 +197,7 @@ public:
      *
      * 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/>
+     * 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.
@@ -230,6 +244,24 @@ public:
         }
     }
 
+    /**
+     * @brief Connects an user defined function with optional payload to a
+     * delegate.
+     *
+     * The delegate isn't responsible for the connected object or the payload.
+     * Users must always guarantee that the lifetime of an instance overcomes
+     * the one of the delegate.<br/>
+     * The payload is returned as the first argument to the target function in
+     * all cases.
+     *
+     * @param function Function to connect to the delegate.
+     * @param payload User defined arbitrary data.
+     */
+    void connect(target_type *function, const void *payload = nullptr) ENTT_NOEXCEPT {
+        fn = function;
+        data = payload;
+    }
+
     /**
      * @brief Resets a delegate.
      *
@@ -286,7 +318,7 @@ public:
     }
 
 private:
-    proto_fn_type *fn;
+    target_type *fn;
     const void *data;
 };
 
@@ -324,6 +356,12 @@ delegate(connect_arg_t<Candidate>, Type &&) ENTT_NOEXCEPT
 -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
 
 
+/*! @brief Deduction guide. */
+template<typename Ret, typename... Args>
+delegate(Ret(*)(const void *, Args...), const void * = nullptr) ENTT_NOEXCEPT
+-> delegate<Ret(Args...)>;
+
+
 }
 
 

+ 32 - 1
test/entt/signal/delegate.cpp

@@ -48,6 +48,7 @@ struct const_nonconst_noexcept {
 TEST(Delegate, Functionalities) {
     entt::delegate<int(int)> ff_del;
     entt::delegate<int(int)> mf_del;
+    entt::delegate<int(int)> lf_del;
     delegate_functor functor;
 
     ASSERT_FALSE(ff_del);
@@ -56,30 +57,43 @@ TEST(Delegate, Functionalities) {
 
     ff_del.connect<&delegate_function>();
     mf_del.connect<&delegate_functor::operator()>(functor);
+    lf_del.connect([](const void *ptr, int value) {
+        return static_cast<const delegate_functor *>(ptr)->identity(value);
+    });
 
     ASSERT_TRUE(ff_del);
     ASSERT_TRUE(mf_del);
+    ASSERT_TRUE(lf_del);
 
     ASSERT_EQ(ff_del(3), 9);
     ASSERT_EQ(mf_del(3), 6);
+    ASSERT_EQ(lf_del(3), 3);
 
     ff_del.reset();
 
     ASSERT_FALSE(ff_del);
-    ASSERT_TRUE(mf_del);
 
     ASSERT_EQ(ff_del, entt::delegate<int(int)>{});
     ASSERT_NE(mf_del, entt::delegate<int(int)>{});
+    ASSERT_NE(lf_del, entt::delegate<int(int)>{});
+
     ASSERT_NE(ff_del, mf_del);
+    ASSERT_NE(ff_del, lf_del);
+    ASSERT_NE(mf_del, lf_del);
 
     mf_del.reset();
 
     ASSERT_FALSE(ff_del);
     ASSERT_FALSE(mf_del);
+    ASSERT_TRUE(lf_del);
 
     ASSERT_EQ(ff_del, entt::delegate<int(int)>{});
     ASSERT_EQ(mf_del, entt::delegate<int(int)>{});
+    ASSERT_NE(lf_del, entt::delegate<int(int)>{});
+
     ASSERT_EQ(ff_del, mf_del);
+    ASSERT_NE(ff_del, lf_del);
+    ASSERT_NE(mf_del, lf_del);
 }
 
 TEST(Delegate, DataMembers) {
@@ -167,6 +181,20 @@ TEST(Delegate, Comparison) {
     ASSERT_FALSE(lhs == rhs);
     ASSERT_NE(lhs, rhs);
 
+    lhs.connect([](const void *ptr, int value) { return static_cast<const delegate_functor *>(ptr)->identity(value); }, &functor);
+
+    ASSERT_NE(lhs, (entt::delegate<int(int)>{[](const void *ptr, int value) { return static_cast<const delegate_functor *>(ptr)->identity(value); }, &functor}));
+    ASSERT_TRUE(lhs != rhs);
+    ASSERT_FALSE(lhs == rhs);
+    ASSERT_NE(lhs, rhs);
+
+    rhs.connect([](const void *ptr, int value) { return static_cast<const delegate_functor *>(ptr)->identity(value); }, &functor);
+
+    ASSERT_NE(rhs, (entt::delegate<int(int)>{[](const void *ptr, int value) { return static_cast<const delegate_functor *>(ptr)->identity(value); }, &functor}));
+    ASSERT_TRUE(lhs != rhs);
+    ASSERT_FALSE(lhs == rhs);
+    ASSERT_NE(lhs, rhs);
+
     lhs.reset();
 
     ASSERT_EQ(lhs, (entt::delegate<int(int)>{}));
@@ -219,6 +247,7 @@ TEST(Delegate, DeductionGuide) {
     entt::delegate data_member_u{entt::connect_arg<&const_nonconst_noexcept::u>, functor};
     entt::delegate data_member_v{entt::connect_arg<&const_nonconst_noexcept::v>, &functor};
     entt::delegate data_member_v_const{entt::connect_arg<&const_nonconst_noexcept::v>, &std::as_const(functor)};
+    entt::delegate lambda{+[](const void *, int) { return 0; }};
 
     static_assert(std::is_same_v<typename decltype(func)::function_type, int(const int &)>);
     static_assert(std::is_same_v<typename decltype(curried_func_with_ref)::function_type, int(int)>);
@@ -234,6 +263,7 @@ TEST(Delegate, DeductionGuide) {
     static_assert(std::is_same_v<typename decltype(data_member_u)::function_type, int()>);
     static_assert(std::is_same_v<typename decltype(data_member_v)::function_type, const int()>);
     static_assert(std::is_same_v<typename decltype(data_member_v_const)::function_type, const int()>);
+    static_assert(std::is_same_v<typename decltype(lambda)::function_type, int(int)>);
 
     ASSERT_TRUE(func);
     ASSERT_TRUE(curried_func_with_ref);
@@ -249,6 +279,7 @@ TEST(Delegate, DeductionGuide) {
     ASSERT_TRUE(data_member_u);
     ASSERT_TRUE(data_member_v);
     ASSERT_TRUE(data_member_v_const);
+    ASSERT_TRUE(lambda);
 }
 
 TEST(Delegate, ConstInstance) {