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!
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;
}