So I am creating a type of event handler and I am in the process of writing an "Event Listener Wrapper", if you will.
The basic idea is this: When you want to subscribe to an event, you create a function that should be called when the event fires. <-- already have that done (kinda, I'll explain)
You put this listener function into a wrapper to pass the function onto the dispatcher.
The dispatcher gets an event, finds the wrapper for you listener, and calls the underlying function with the parameter values set by the event.
I already have something working so long as the listeners all only accept one argument of my EventBase
class. Then I have to type cast that into the proper event that the listener is passed.
What I want instead is for my listener functions to have "any" type of arguments, and store the function in a way that lets me call it with any arguments I want depending on the event fired. Each listener function would only ever receive one type of event, or the event it's self. This would allow me to not have to type cast each event in every listener, but instead the correct event would be passed.
I found a bit of code for this wrapper that is almost perfect, with a few minor issues that I can't seem to fix. I'll explain below.
Code by @hmjd:
#include <iostream>
#include <string>
#include <functional>
#include <memory>
void myFunc1(int arg1, float arg2)
{
std::cout << arg1 << ", " << arg2 << '\n';
}
void myFunc2(const char *arg1)
{
std::cout << arg1 << '\n';
}
class DelayedCaller
{
public:
template <typename TFunction, typename... TArgs>
static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func,
TArgs&&... a_args)
{
return std::unique_ptr<DelayedCaller>(new DelayedCaller(
std::bind(std::forward<TFunction>(a_func),
std::forward<TArgs>(a_args)...)));
}
void call() const { func_(); }
private:
using func_type = std::function<void()>;
DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
func_type func_;
};
int main()
{
auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6));
auto caller2(DelayedCaller::setup(&myFunc2, "A string"));
caller1->call();
caller2->call();
return 0;
}
The first thing I did here was I had to replace std::unique_ptr
with std::shared_ptr
. Not sure why really. This almost works. In my use case, I need to store a method function (meaning bind needs to be passed the containing method object?), and at the time of storing the function I don't know what the argument value will be, thats up for the event to decide. So my adjustment is as follows:
class DelayedCaller
{
public:
template <typename TFunction, typename TClass>
static std::shared_ptr<DelayedCaller> setup(TFunction&& a_func,
TClass && a_class)
{
auto func = std::bind(std::forward<TFunction>(a_func),
std::forward<TClass>(a_class),
std::placeholders::_1);
return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));
}
template <typename T>
void call( T v ) const { func_(v); }
private:
using func_type = std::function<void( )>;
DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
func_type func_;
};
For the sake of testing, I removed the parameter pack and replaced it with a direct parameter to the class object holding the function. I also gave the bind a placeholder for 1 argument (ideally replaced by the void call()
function later).
It's created like this:
eventManager->subscribe(EventDemo::descriptor, DelayedCaller::setup(
&AppBaseLogic::getValueBasic,
this
));
Problem is: on this line:
return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));
I get "no matching function for call to 'DelayedCaller::DelayedCaller(std::_Bind(AppBaseLogic*, std::_Placeholder<1>)>&)' return std::shared_ptr(new DelayedCaller(func));"
This only happens when using the placeholder::_1
. if I replace that with a known value of the correct type, it works, with the exception that the function gets called without any useful data of course.
So, I guess I need a way to store the function with placeholders that I don't know the type of?
Forgive me if I am getting names of things wrong. I am very new to c++, I have only started learning it the past few days.
**Edit: **
Ok, so I am just updating why I need to store functions like this. I have a map in my event dispatcher that looks like this:
std::map< const char*, std::vector<DelayedCaller> > _observers;
I want to be able to call the function inside the "Delayed Caller" something like this:
void Dispatcher::post( const EventBase& event ) const
{
// Side Note: I had to do this instead of map.find() and map.at() because
// passing a "const char*" was not evaluating as equal to event.type() even
// though event.type() is also a const char*. So instead I am checking it
// myself, which is fine because it gives me a little more control.
std::string type(event.type());
for( auto const &x : _observers ) {
std::string type2(x.first);
if ( type == type2 ) {
auto&& observers = x.second;
for( auto&& observer : observers ) {
// event may be any descendant of EventBase.
observer.slot->call(event);
}
break;
}
}
}
My listeners currently look like this:
void AppBaseLogic::getValue(const EventBase &e) {
const EventDemo& demoEvent = static_cast<const EventDemo&>( e );
std::cout << demoEvent.type();
}
I am trying to store each function so that the argument may look like this:
void AppBaseLogic::getValue(const EventAnyDescendant &e) {
std::cout << e.type();
}
Hopefully that helps. Thank you all for taking the time to help me with this.
Side note on lambdas: Someone suggested them, I have know idea what they are or how to use them, but I am going to do some reaserch on them so see if that would make more sense. I am worried about maintainability with them though from what I have seen.