Browse Source

delegate/signal: support for const member functions

Michele Caini 7 years ago
parent
commit
c4dd06fa45
4 changed files with 127 additions and 7 deletions
  1. 21 0
      src/entt/signal/delegate.hpp
  2. 50 7
      src/entt/signal/sigh.hpp
  3. 27 0
      test/entt/signal/delegate.cpp
  4. 29 0
      test/entt/signal/sigh.cpp

+ 21 - 0
src/entt/signal/delegate.hpp

@@ -42,6 +42,11 @@ class Delegate<Ret(Args...)> final {
         return (Function)(args...);
     }
 
+    template<typename Class, Ret(Class:: *Member)(Args...) const>
+    static Ret proto(void *instance, Args... args) {
+        return (static_cast<const Class *>(instance)->*Member)(args...);
+    }
+
     template<typename Class, Ret(Class:: *Member)(Args...)>
     static Ret proto(void *instance, Args... args) {
         return (static_cast<Class *>(instance)->*Member)(args...);
@@ -71,6 +76,22 @@ public:
         stub = std::make_pair(nullptr, &proto<Function>);
     }
 
+    /**
+     * @brief Connects a member function for a given instance to a delegate.
+     *
+     * The delegate isn't responsible for the connected object. Users must
+     * guarantee that the lifetime of the instance overcomes the one of the
+     * delegate.
+     *
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the delegate.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template<typename Class, Ret(Class:: *Member)(Args...) const>
+    void connect(Class *instance) ENTT_NOEXCEPT {
+        stub = std::make_pair(instance, &proto<Class, Member>);
+    }
+
     /**
      * @brief Connects a member function for a given instance to a delegate.
      *

+ 50 - 7
src/entt/signal/sigh.hpp

@@ -20,14 +20,23 @@ namespace entt {
 namespace internal {
 
 
+template<typename>
+struct sigh_traits;
+
+template<typename Ret, typename... Args>
+struct sigh_traits<Ret(Args...)> {
+    using proto_fn_type = Ret(void *, Args...);
+    using call_type = std::pair<void *, proto_fn_type *>;
+};
+
+
 template<typename, typename>
 struct Invoker;
 
 
 template<typename Ret, typename... Args, typename Collector>
 struct Invoker<Ret(Args...), Collector> {
-    using proto_fn_type = Ret(void *, Args...);
-    using call_type = std::pair<void *, proto_fn_type *>;
+    using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
 
     virtual ~Invoker() = default;
 
@@ -39,8 +48,7 @@ struct Invoker<Ret(Args...), Collector> {
 
 template<typename... Args, typename Collector>
 struct Invoker<void(Args...), Collector> {
-    using proto_fn_type = void(void *, Args...);
-    using call_type = std::pair<void *, proto_fn_type *>;
+    using proto_fn_type = typename sigh_traits<void(Args...)>::proto_fn_type;
 
     virtual ~Invoker() = default;
 
@@ -132,14 +140,18 @@ class Sink<Ret(Args...)> final {
     template<typename, typename>
     friend class SigH;
 
-    using proto_fn_type = Ret(void *, Args...);
-    using call_type = std::pair<void *, proto_fn_type *>;
+    using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
 
     template<Ret(*Function)(Args...)>
     static Ret proto(void *, Args... args) {
         return (Function)(args...);
     }
 
+    template<typename Class, Ret(Class:: *Member)(Args... args) const>
+    static Ret proto(void *instance, Args... args) {
+        return (static_cast<const Class *>(instance)->*Member)(args...);
+    }
+
     template<typename Class, Ret(Class:: *Member)(Args... args)>
     static Ret proto(void *instance, Args... args) {
         return (static_cast<Class *>(instance)->*Member)(args...);
@@ -164,6 +176,25 @@ public:
         calls.emplace_back(nullptr, &proto<Function>);
     }
 
+    /**
+     * @brief Connects a member function for a given instance 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
+     * signal. On the other side, the signal handler performs checks to
+     * avoid multiple connections for the same member function of a given
+     * instance.
+     *
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the signal.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template <typename Class, Ret(Class:: *Member)(Args...) const = &Class::receive>
+    void connect(Class *instance) {
+        disconnect<Class, Member>(instance);
+        calls.emplace_back(instance, &proto<Class, Member>);
+    }
+
     /**
      * @brief Connects a member function for a given instance to a signal.
      *
@@ -193,6 +224,18 @@ public:
         calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
     }
 
+    /**
+     * @brief Disconnects the given member function from a signal.
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the signal.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template<typename Class, Ret(Class:: *Member)(Args...) const>
+    void disconnect(Class *instance) {
+        call_type target{instance, &proto<Class, Member>};
+        calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
+    }
+
     /**
      * @brief Disconnects the given member function from a signal.
      * @tparam Class Type of class to which the member function belongs.
@@ -253,7 +296,7 @@ private:
  */
 template<typename Ret, typename... Args, typename Collector>
 class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
-    using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
+    using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
 
 public:
     /*! @brief Unsigned integer type. */

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

@@ -11,6 +11,14 @@ struct DelegateFunctor {
     }
 };
 
+struct ConstNonConstNoExcept {
+    void f() { ++cnt; }
+    void g() noexcept { ++cnt; }
+    void h() const { ++cnt; }
+    void i() const noexcept { ++cnt; }
+    mutable int cnt{0};
+};
+
 TEST(Delegate, Functionalities) {
     entt::Delegate<int(int)> ffdel;
     entt::Delegate<int(int)> mfdel;
@@ -46,3 +54,22 @@ TEST(Delegate, Comparison) {
     ASSERT_TRUE(def == entt::Delegate<int(int)>{});
     ASSERT_TRUE (def != delegate);
 }
+
+TEST(Delegate, ConstNonConstNoExcept) {
+    entt::Delegate<void()> delegate;
+    ConstNonConstNoExcept functor;
+
+    delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
+    delegate();
+
+    delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
+    delegate();
+
+    delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
+    delegate();
+
+    delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
+    delegate();
+
+    ASSERT_EQ(functor.cnt, 4);
+}

+ 29 - 0
test/entt/signal/sigh.cpp

@@ -45,6 +45,14 @@ struct TestCollectFirst {
     }
 };
 
+struct ConstNonConstNoExcept {
+    void f() { ++cnt; }
+    void g() noexcept { ++cnt; }
+    void h() const { ++cnt; }
+    void i() const noexcept { ++cnt; }
+    mutable int cnt{0};
+};
+
 TEST(SigH, Lifetime) {
     using signal = entt::SigH<void(void)>;
 
@@ -220,3 +228,24 @@ TEST(SigH, Collector) {
     ASSERT_EQ(static_cast<std::vector<int>::size_type>(1), collector_first.vec.size());
     ASSERT_EQ(42, collector_first.vec[0]);
 }
+
+TEST(SigH, ConstNonConstNoExcept) {
+    entt::SigH<void()> sigh;
+    ConstNonConstNoExcept functor;
+
+    sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
+    sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
+    sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
+    sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
+    sigh.publish();
+
+    ASSERT_EQ(functor.cnt, 4);
+
+    sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
+    sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
+    sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
+    sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
+    sigh.publish();
+
+    ASSERT_EQ(functor.cnt, 4);
+}