Jump to content

Bind member function to instance with variadic arguments

Go to solution Solved by Unimportant,
10 hours ago, Pinguinsan said:

<snip>

The problem is that a lambda cannot be converted to a function pointer if it has a capture.

 

A solution would be to wrap the callbacks in std::function. However, std::function's cannot be stored in a std::unordered_set as unordered_set relies on std::hash and std::equal_to, both of which don't operate on std::function.

 

Small example using std::vector instead:

#include <functional>
#include <vector>
#include <iostream>

template <class... Params>
class Event
{
public:

    template <class T>
    using MemberPtr = void (T::*)(Params...);

    using Callback = std::function<void(Params...)>;

    template <class T>
    void
    Subscribe(T* t, MemberPtr<T> m)
    {
        const auto lambda = [=](Params&&... params) { (t->*m)(std::forward<Params>(params)...); };
        Addcallback(lambda);
    }

    void
    Notify(Params&&... params) const
    {
        for (const auto& callback : mCallbacks)
        {
            callback(std::forward<Params>(params)...);
        }
    }

private:

    void
    Addcallback(Callback callback)
    {
        mCallbacks.emplace_back(callback);
    }

    std::vector<Callback> mCallbacks;
};

struct EventListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Received " << i << std::endl;
    }
};

struct DoublingListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Double of received value " << i * 2 << std::endl;
    }
};

int
main()
{
    Event<int> event;
    EventListener listener;
    DoublingListener doublingListener;

    event.Subscribe(&listener, &EventListener::eventListenerCallback);
    event.Subscribe(&doublingListener, &DoublingListener::eventListenerCallback);
    event.Notify(1337);

    return 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.

Link to comment
Share on other sites

Link to post
Share on other sites

10 hours ago, Pinguinsan said:

<snip>

The problem is that a lambda cannot be converted to a function pointer if it has a capture.

 

A solution would be to wrap the callbacks in std::function. However, std::function's cannot be stored in a std::unordered_set as unordered_set relies on std::hash and std::equal_to, both of which don't operate on std::function.

 

Small example using std::vector instead:

#include <functional>
#include <vector>
#include <iostream>

template <class... Params>
class Event
{
public:

    template <class T>
    using MemberPtr = void (T::*)(Params...);

    using Callback = std::function<void(Params...)>;

    template <class T>
    void
    Subscribe(T* t, MemberPtr<T> m)
    {
        const auto lambda = [=](Params&&... params) { (t->*m)(std::forward<Params>(params)...); };
        Addcallback(lambda);
    }

    void
    Notify(Params&&... params) const
    {
        for (const auto& callback : mCallbacks)
        {
            callback(std::forward<Params>(params)...);
        }
    }

private:

    void
    Addcallback(Callback callback)
    {
        mCallbacks.emplace_back(callback);
    }

    std::vector<Callback> mCallbacks;
};

struct EventListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Received " << i << std::endl;
    }
};

struct DoublingListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Double of received value " << i * 2 << std::endl;
    }
};

int
main()
{
    Event<int> event;
    EventListener listener;
    DoublingListener doublingListener;

    event.Subscribe(&listener, &EventListener::eventListenerCallback);
    event.Subscribe(&doublingListener, &DoublingListener::eventListenerCallback);
    event.Notify(1337);

    return 0;
}

 

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

THANK YOU. I've spent a long time on this and I appreciate the help.

4 hours ago, Unimportant said:

The problem is that a lambda cannot be converted to a function pointer if it has a capture.

 

A solution would be to wrap the callbacks in std::function. However, std::function's cannot be stored in a std::unordered_set as unordered_set relies on std::hash and std::equal_to, both of which don't operate on std::function.

 

Small example using std::vector instead:


#include <functional>
#include <vector>
#include <iostream>

template <class... Params>
class Event
{
public:

    template <class T>
    using MemberPtr = void (T::*)(Params...);

    using Callback = std::function<void(Params...)>;

    template <class T>
    void
    Subscribe(T* t, MemberPtr<T> m)
    {
        const auto lambda = [=](Params&&... params) { (t->*m)(std::forward<Params>(params)...); };
        Addcallback(lambda);
    }

    void
    Notify(Params&&... params) const
    {
        for (const auto& callback : mCallbacks)
        {
            callback(std::forward<Params>(params)...);
        }
    }

private:

    void
    Addcallback(Callback callback)
    {
        mCallbacks.emplace_back(callback);
    }

    std::vector<Callback> mCallbacks;
};

struct EventListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Received " << i << std::endl;
    }
};

struct DoublingListener
{
    void
    eventListenerCallback(int i)
    {
        std::cout << "Double of received value " << i * 2 << std::endl;
    }
};

int
main()
{
    Event<int> event;
    EventListener listener;
    DoublingListener doublingListener;

    event.Subscribe(&listener, &EventListener::eventListenerCallback);
    event.Subscribe(&doublingListener, &DoublingListener::eventListenerCallback);
    event.Notify(1337);

    return 0;
}

 

 

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×