0

My goal is to create a generic Event type that can be used in subscribe/notify architecture, but I am having trouble getting class member functions to work.

Event.hpp:

#ifndef EVENT_HPP
#define EVENT_HPP

#include <functional>
#include <unordered_set>

template <typename ... EventParameterTypes>
struct Event {
    typedef void(*EventCallback)(EventParameterTypes ...);

    template <typename ClassType>
    Event &subscribe(ClassType *instance, void(ClassType::*eventCallback)(EventParameterTypes...)) {
        auto bound = [=](EventParameterTypes&& ... params) { return ((instance)->*(eventCallback))(std::forward<EventParameterTypes>(params)...); };
        return this->subscribe(bound);
    }

    Event &subscribe(EventCallback eventCallback) {
        return this->addEventCallback(eventCallback);
    }

    void notify(EventParameterTypes ... types) {
        for (const auto &it : this->m_eventCallbacks) {
            if (it) (*it)(types...);
        }
    }

private:
    std::unordered_set<EventCallback> m_eventCallbacks;


    Event &addEventCallback(EventCallback eventCallback) {
        auto foundIterator = std::find(this->m_eventCallbacks.begin(), this->m_eventCallbacks.end(), eventCallback);
        if (foundIterator != this->m_eventCallbacks.end()) {
            return *this;
        }
        this->m_eventCallbacks.insert(eventCallback);
        return *this;
    }
};

#endif //EVENT_HPP

Main.cpp:

#include "Event.hpp"
struct EventTest {
    using MyEvent = Event<int>;
    MyEvent myEvent;
};

void myEventCallback(int) {
    //Stuff
}

struct EventListener {
    void eventListenerCallback(int) {
        //Stuff
    }
};


int main() {
    EventListener eventListener{};
    EventTest eventTest{};
    eventTest.myEvent.subscribe(&myEventCallback); //OK
    eventTest.myEvent.subscribe(&eventListener, &EventListener::eventListenerCallback); //Compile error
}

Is there any way to resolve this? I have looked into std::bind but it only works with a certain amount of placeholders, and the lambda causes the function to be of a local lambda type.

Tyler Lewis
  • 881
  • 6
  • 19
  • You are passing a lambda into the subscribe method. So that will never work unless you call the lambda i.e. "return this->subscribe(bound(params));". What are you trying to do with those params? How are the two subscribes supposed to interact? – Tagger5926 Aug 19 '19 at 01:36
  • The lambda is meant to pack the instance and eventCallback into a single function pointer with the signature of a bare function pointer. The subscribe method simply takes a raw function pointer and saves it in a container, then calls it later with whatever parameters are passed to the notify method. – Tyler Lewis Aug 19 '19 at 03:33

2 Answers2

0

You are hoping to somehow pack instance and eventCallback into a single function pointer. You can't do that, no more than you can pour an ocean into a thimble. One way out is to make EventCallback a typedef for a suitable specialication of std::function, as in

typedef std::function<void(EventParameterTypes ...)> EventCallback;

I won't be surprised if the rest of the code would just work after this, with no modifications.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • std::bind packs an instance and eventCallback into a single function pointer, see https://stackoverflow.com/questions/17131768/how-to-directly-bind-a-member-function-to-an-stdfunction-in-visual-studio-11 . The issue in my example is that I have a parameter pack to expand. Also, I already have a typedef for EventCallback, why would changing it from a raw function pointer to a std::function change the behavior? – Tyler Lewis Aug 19 '19 at 03:31
  • No. `std::bind` packs them into an object of some (unspecified) class type, which is definitely larger than a function pointer. You can see for yourself: print `sizeof(bound)`. `std::function` is itself a class, that can wrap any callable, including a plain function pointer and an object manufactured by `std::bind` – Igor Tandetnik Aug 19 '19 at 04:22
  • Okay, I see what you're saying. I'll try this tomorrow. Eventually I will have to determine how to compare std::function objects as well for an "unsubscribe" method. – Tyler Lewis Aug 19 '19 at 05:46
  • That's going to be tricky. I don't believe `std::function` provides any form of `operator==` or similar comparison. Neither does the object produced by `std::bind`, or a lambda. You'd likely have to store raw pointers instead of or in addition to the callable wrapper, and compare those. Alternatively, `subscribe` could return some kind of subscription ID, that the client could pass to `unsubscribe`. – Igor Tandetnik Aug 19 '19 at 14:25
0

Using @Igor Tandetnik s advice, I came up with the following (working) implementation:

Event.hpp

#ifndef EVENT_HPP
#define EVENT_HPP

#include <functional>
#include <functional>
#include <type_traits>
#include <vector>

    template<typename T, typename... U>
    size_t getFunctionAddress(std::function<T(U...)> f) {
        typedef T(fnType)(U...);
        fnType **fnPointer = f.template target<fnType *>();
        if (fnPointer == nullptr) {
            return 0;
        }
        return (size_t) *fnPointer;
    }

    template<typename ... EventParameterTypes>
    struct Event {
        template<class ClassType> using MemberPtr = void (ClassType::*)(EventParameterTypes...);
        using EventCallback = std::function<void(EventParameterTypes ...)>;

        template<class ClassType>
        Event &subscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
            const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
            return this->operator+=(bound);
        }

        Event &operator+=(EventCallback eventCallback) {
            return this->addEventCallback(eventCallback);
        }

        template<class ClassType>
        Event &unsubscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
            const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
            return this->operator-=(bound);
        }

        Event &operator-=(EventCallback eventCallback) {
            return this->removeEventCallback(eventCallback);
        }

        template<class ClassType>
        bool isListenerRegistered(ClassType *instance, MemberPtr<ClassType> eventCallback) {
            const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
            return this->isListenerRegistered(bound);
        }

        bool isListenerRegistered(EventCallback eventCallback) {
            return findListener(eventCallback) != this->m_eventCallbacks.cend();
        }

        void operator()(EventParameterTypes ... types) {
            this->notify(std::forward<EventParameterTypes>(types)...);
        }

        void notify(EventParameterTypes ... types) {
            for (const auto &it : this->m_eventCallbacks) {
                if (it) (it)(std::forward<EventParameterTypes>(types)...);
            }
        }

    private:
        std::vector<EventCallback> m_eventCallbacks;
        std::mutex m_eventListenerMutex;

        typename std::vector<EventCallback>::const_iterator findListener(EventCallback eventCallback) {
            for (auto iter = this->m_eventCallbacks.cbegin(); iter != this->m_eventCallbacks.cend(); iter++) {
                if (getFunctionAddress(*iter) == getFunctionAddress(eventCallback)) {
                    return iter;
                }
            }
            return this->m_eventCallbacks.cend();
        }

        Event &addEventCallback(EventCallback eventCallback) {
            auto foundPosition = this->findListener(eventCallback);
            if (foundPosition != this->m_eventCallbacks.cend()) {
                return *this;
            }
            this->m_eventCallbacks.emplace_back(eventCallback);
            return *this;
        }

        Event &removeEventCallback(EventCallback eventCallback) {
            auto foundPosition = this->findListener(eventCallback);
            if (foundPosition == this->m_eventCallbacks.cend()) {
                return *this;
            }
            this->m_eventCallbacks.erase(foundPosition);
            return *this;
        }
    };
#endif //EVENT_HPP
Tyler Lewis
  • 881
  • 6
  • 19