Search code examples
c++c++11std-functionstdbind

Try to create C# style event access modifier in C++ using std::functional and std::bind


I'm trying to create C# style event handling, i.e expose += operator for everybody and expose Invoke method only for the containing class.

I'm using std::functional and std::bind as explained here to create callback mechanism: C++ callback using class member

To enable invoking the event only from containing class, I created Event::Invoke() method private, and then I create a friend class of Event called ClassWithEvent thats has protected method InvokeEvent which calling Event::Invoke. All the inheritanced classes of ClassWithEvent can invoke the event using base class InvokeEvent method.

Additionly, I want to enable events to have different kind of args, so I create base class EventArgs which can be extended by other king of args.

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

using namespace std;
using namespace placeholders;

class EventArgs
{
};

class Event
{
    friend class ClassWithEvent;

public:
    void operator+=(function<void(const EventArgs &)> callback)
    {
        m_funcsList.push_back(callback);
    }

protected:
    void Invoke(const EventArgs & args) const
    {
        for (function<void(const EventArgs &)> func : m_funcsList)
        {
            func(args);
        }
    }

    vector<function<void(const EventArgs &)>> m_funcsList;
};

class ClassWithEvent
{
protected:
    void InvokeEvent(const Event & event, const EventArgs & args)
    {
        event.Invoke(args);
    }
};

For exaple, I have ApplesTree class with AppleFallEvent event, I have ApplesCollector class which should be notified about falling apples:

class AppleFallEventArgs : public EventArgs
{
public:
    int size;
    int weight;
};

class ApplesTree : public ClassWithEvent
{
public:
    Event AppleFallEvent;

    void triggerAppleFall(int size, int weight)
    {
        AppleFallEventArgs args;

        args.size = size;
        args.weight = weight;

        ClassWithEvent::InvokeEvent(AppleFallEvent, args);
    }
};

class ApplesCollector
{
public:
    void HandleAppleFallEvent(const AppleFallEventArgs & args)
    {
        cout << "Apple size is " << args.size << "weight is " << args.weight << endl;
    }
};

int main(int argc, char* argv[])
{
    ApplesTree applesTree;
    ApplesCollector applesCollector;

    applesTree.AppleFallEvent += bind(&ApplesCollector::HandleAppleFallEvent, &applesCollector, _1);

    applesTree.triggerAppleFall(1, 2);

    return 0;
}

Well, I try to compile this and get the following errors:

C2672 'std::invoke': no matching overloaded function found

and

C2893 Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...)'

I can't look out where is the problem because this errors belongs to std code and its really hard to deal with it. Any way, I found that if I removing the usage of Event::operator+=() the compile is successfull.

Can someone point out what is the problem here?

Thanks!


Solution

  • You need to have Event know which particular args it will use. Note that using namespace std; is a bad idea. I've also replaced the bind with a lambda.

    // no empty class EventArgs
    
    template<typename EventArgs>
    class Event
    {
        friend class ClassWithEvent;
    
    public:
        void operator+=(std::function<void(const EventArgs &)> callback)
        {
            m_funcsList.emplace_back(std::move(callback));
        }
    
    private:
        void Invoke(const EventArgs & args) const
        {
            for (auto & func : m_funcsList)
            {
                func(args);
            }
        }
    
        std::vector<std::function<void(const EventArgs &)>> m_funcsList;
    };
    
    class ClassWithEvent
    {
    protected:
        template<typename EventArgs>
        void InvokeEvent(const Event<EventArgs> & event, const EventArgs & args)
        {
            event.Invoke(args);
        }
    };
    
    class AppleFallEventArgs
    {
    public:
        int size;
        int weight;
    };
    
    class ApplesTree : public ClassWithEvent
    {
    public:
        Event<AppleFallEventArgs> AppleFallEvent;
    
        void triggerAppleFall(int size, int weight)
        {
            AppleFallEventArgs args;
    
            args.size = size;
            args.weight = weight;
    
            InvokeEvent(AppleFallEvent, args);
        }
    };
    
    class ApplesCollector
    {
    public:
        void HandleAppleFallEvent(const AppleFallEventArgs & args)
        {
            std::cout << "Apple size is " << args.size << "weight is " << args.weight << std::endl;
        }
    };
    
    int main(int argc, char* argv[])
    {
        ApplesTree applesTree;
        ApplesCollector applesCollector;
    
        applesTree.AppleFallEvent += [&](auto & args){ applesCollector.HandleAppleFallEvent(args); };
    
        applesTree.triggerAppleFall(1, 2);
    
        return 0;
    }