mardi 31 octobre 2017

Store a function with arbitrary arguments and placeholders in a class and call it later

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 unique_ptr with 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.

Aucun commentaire:

Enregistrer un commentaire