2

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.

Dalen Catt
  • 111
  • 1
  • 9
  • Ummm why not just use `std::function` by itself? – StoryTeller - Unslander Monica Oct 31 '17 at 08:14
  • Something similar I asked before https://stackoverflow.com/questions/43396129/parameter-pack-expansion-not-working-in-lambda – Sam Daniel Oct 31 '17 at 08:24
  • 1
    *"lets me call it with any arguments I want depending on the event fired"*. That is the point: each event requires a specific signature so create a list of function by type of event `std::vector> onClick; std::vector> onKeyPressed;`... – Jarod42 Oct 31 '17 at 09:12
  • The idea of creating a different vector for each type of event "might" work. I would have to do things significantly different. right now events are stored in a map, by "event_Key":vector of functions to call. This way if an event is emitted and there aren't any listeners, it doesn't matter. I don't really know how I would go about creating a new vector list automatically and dynamically when a listener is added, but I might be able to. Part of the other problem is that I would like to make some events templated. So a different list for EventMsg, EventMsg, EventMsg ... – Dalen Catt Oct 31 '17 at 09:50
  • Also related: https://stackoverflow.com/questions/45715219/store-functions-with-different-signatures-in-a-map/45718187#45718187 – Caleth Oct 31 '17 at 10:22

5 Answers5

1

It isn't quite clear what your DelayedCaller is doing. If you refactor the code and get rid of it, you will get just this:

auto c1 = []() {myFunc1(123, 45.6);}; // or use bind, the result is exactly the same
auto c2 = []() {myFunc2("A string");};

vector<function<void()>> v {c1, c2};
v[0]();
v[1](); // ok

Now if you try to introduce the placeholder modification in this version, it becomes clear why it didn't work in the first place:

auto cSome = [](???) {getValueBasic(???)};

What do you replace the ??? with?

getValueBasic accepts some specific type of argument, and it will leak out into the cSome signature. No matter how many template wrappers you wrap it in, it will leak out into the signature of every wrapper up to and including the outermost one. bind and std::placeholders are not a magic wand capable of making it unhappen.

In other words, if you don't know the type of your function, you cannot call it (kind of obvious, isn't it?)

One way to type-erase the signature and have all callables to conform to the same type is to typecheck and typecast them at run time (a.k.a. dynamic_cast). Another one is double dispatch. Both methods are different incarnations of the same general idea of visitor. Google "the visitor pattern" for more info.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you for some clarification here. As I mentioned in the question, I've only been learning c++ for a few days. I come from a heavy web dev background and Java for most app dev. Trying to learn something new. I am not used to the idea that I can't pass a function as a callback without knowing what the parameters are. It seems it would be simple to say "store a reference to this arbitrary function in a map, then call it later using these arguments. Call a different function from the map with different arguments." But map only can store one type. It's odd for me. – Dalen Catt Oct 31 '17 at 18:45
  • @DalenCatt this is static typing for you. Suppose you can store functions with different signatures in the same map. Suppose now you fetch a function that expects a pointer to a keyboard event, and pass it an array of 3 doubles instead. What do you expect to happen? How should the implementation handle this? – n. m. could be an AI Oct 31 '17 at 19:13
  • I guess my expectation in this use case is that if you are paying attention to what you are doing, that wouldn't happen. And in this case, because it is checking the type of the event before calling the function, then passing that same event, it shouldn't be an issue, but yes, I understand why under normal circumstances it wouldn't work. I guess I just wish that the language was more capable of trusting that I know what I want to do. I feel like it's to restrictive. – Dalen Catt Oct 31 '17 at 19:40
  • @DalenCatt It is capable of trusting you, but you need to be very explicit about it. To say "trust me I know what I'm doing" in C++ you use type casts, in particular `reinterpret_cast`. I would strongly advice against it though. If you have to ask how to do a dangerous thing, you shouldn't be doing it. – n. m. could be an AI Oct 31 '17 at 20:54
  • Right, I understand when you are passing completely different variable types. But what I am talking about is passing a child of a base class. And storing functions that are all of the same "sructure" just with different child classes of a base class all in one array/vector. It just seems to me that there must be some way to do that. It may be more advanced than the level I am at right now (clearly) but the concept seems so simple. – Dalen Catt Oct 31 '17 at 21:07
  • There are ways, read the last paragraph of the answer again. They are not exactly needed in this application though. Separate event and callback lists for events of different types is the way to go. Perhaps you want to ask a different question about how to implement such a thing. – n. m. could be an AI Oct 31 '17 at 21:19
0

May be this suits you. using c++11

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

namespace test
{


  std::vector<std::function<void()>> listeners;

  template<typename F, typename... Args>
  void add_listener(F call, Args&& ...args )
  {   
    std::cout << "callback_dispatcher>" << __PRETTY_FUNCTION__ << "enter <<< " << std::endl;                                                 
    auto invoke_me = [=]()mutable{
      call(std::move(args)...);
    };  
    listeners.push_back(invoke_me);
  }   

  void dispatch_all()
  {
    for(auto func: listeners)
    {   
       func();
    }   
  }   

}

int main()
{
  std::cout << "Main entered..." << std::endl;


  test::add_listener(
    [](int a)
    {   
      std::cout << "void(int) lambda dispatched with a = " << a << std::endl;
    },  
    5   
  );  
  test::add_listener(
    [](int a, std::string str)
    {   
      std::cout << "void(int, string) lambda dispatched with a = " << a << ", str = " << str << std::endl;
    },  
    10, "Hello World!"
  );  

  test::dispatch_all();

  std::cout << "Main exited..." << std::endl;
}

Output:

Main entered...
callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int)>; Args = {int}]enter <<< 
callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int, std::__cxx11::string)>; Args = {int, const char (&)[13]}]enter <<< 
void(int) lambda dispatched with a = 5
void(int, string) lambda dispatched with a = 10, str = Hello World!
Main exited...

Refer SO_QUESTION for why mutable and std::move is used when expanding args in a lambda.

Sam Daniel
  • 1,800
  • 12
  • 22
  • Wow, I didn't expect responses this quickly. I was going to bed, lol. I am not familiar with lambdas and was not sure if they would work in this context. Would this work with placeholders as well? Also, is this in place of calling a member function? I guess I need to learn how lambdas work. – Dalen Catt Oct 31 '17 at 09:41
  • This doesn't solve any problem. All the arguments are supplied right where `add_listener` is called. You could have captured them in the lambdas right there just as well. This is the same as the first example. In the real situation the arguments are not available until the time of call to dispatch(). – n. m. could be an AI Oct 31 '17 at 14:58
0

Take a look at std::bind and perhaps std::mem_fn

The c+=11 version is able to do all sorts of clever transformations on your argument list to generate a function-like object.

Lambdas provide even more flexibility, of course, and you can mix them, mostly.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
0

I see 2 main problems in your modified (method and placeholder) version of DelayedCaller

(1) now call() receive a parameter (of type T) so func_() is called with one parameter; but func_() remain defined of type std::function<void()>, so can't receive the parameter [this point is the reason of your "no matching function" error]

(2) if you templatize call(), receiving a parameter of with type T, it's necessary to templatize also the type of func_ that become std::function<void(T)>; so you have to templatize the full class.

Taking in count (1) and (2), and maintaining std::unique_ptr, I've rewritten your DelayedCaller as dcM1 (M for "method" and 1 for "1 parameter")

template <typename T>
class dcM1
 {
   public:
      template <typename TFunction, typename TClass>
      static std::unique_ptr<dcM1> 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::unique_ptr<dcM1>(new dcM1(func));
       }

      void call( T v ) const
       { func_(v); }

   private:
      using func_type = std::function<void(T)>;

      dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
       { }

      func_type func_;
 };

and can be used as follows

   auto cm1f = dcM1<int>::setup(&foo::func, &f);
   auto cm1b = dcM1<long>::setup(&bar::func, &b);

   cm1f->call(0);
   cm1b->call(1L);

The following is a full working example

#include <iostream>
#include <string>
#include <functional>
#include <memory>

void myFunc1 (int arg1, float arg2)
 { std::cout << arg1 << ", " << arg2 << '\n'; }

void myFunc2 (char const * arg1)
 { std::cout << arg1 << '\n'; }

class dcVoid
 {
   public:
      template <typename TFunction, typename... TArgs>
      static std::unique_ptr<dcVoid> setup (TFunction && a_func,
                                                   TArgs && ... a_args)
       {
         return std::unique_ptr<dcVoid>(new dcVoid(
               std::bind(std::forward<TFunction>(a_func),
                         std::forward<TArgs>(a_args)...)));
       }

      void call() const
       { func_(); }

   private:
      using func_type = std::function<void()>;

      dcVoid(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
       { }

      func_type func_;
 };

template <typename T>
class dcM1
 {
   public:
      template <typename TFunction, typename TClass>
      static std::unique_ptr<dcM1> 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::unique_ptr<dcM1>(new dcM1(func));
       }

      void call( T v ) const
       { func_(v); }

   private:
      using func_type = std::function<void(T)>;

      dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
       { }

      func_type func_;
 };

struct foo
 { void func (int i) { std::cout << "foo func: " << i << std::endl; } };

struct bar
 { void func (long l) { std::cout << "bar func: " << l << std::endl; } };

int main ()
 {
   auto cv1 = dcVoid::setup(&myFunc1, 123, 45.6);
   auto cv2 = dcVoid::setup(&myFunc2, "A string");

   foo f;
   bar b;

   auto cm1f = dcM1<int>::setup(&foo::func, &f);
   auto cm1b = dcM1<long>::setup(&bar::func, &b);

   cv1->call();
   cv2->call();

   cm1f->call(0);
   cm1b->call(1L);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • So you half understood my problem. You are correct as to why I was receiving errors, and I should have been more clear that I understood this. The problem is that I need to store these arbitrary functions in a map to be called later. I.E: std::map>>. But this can't store templated classes, because they would be a different type. I would then use some form of map.at("someindex")->iterate_through_vector_to_object ->call("some", "args"); Does that make sense? – Dalen Catt Oct 31 '17 at 18:53
  • @DalenCatt - I see... the best I can Imagine is the use of a common base (say `dcBase`) class for the different `DelayedCaller` template classes and store a vector of smart pointers to `dcBase`; something like `std::map>>` – max66 Oct 31 '17 at 19:18
  • @DalenCatt - the problem with a base class is that you can use a virtual function but a virtual function can't be template. So you can make this with `call()`, when call receive no parameters, but not with `template call(T t)` because can't be virtualized; so I think that this can works only if you fix, in the base class, one set (or a limited set) of possible "some" "args" types. – max66 Oct 31 '17 at 19:28
  • And that won't work because the argument passed to call will be any child of EventBase. But it will never be EventBase. And if I write the parameter as EventBase, and pass it EventDemo then it complains that its the wrong type. – Dalen Catt Oct 31 '17 at 19:47
  • And I also can't store functions that use any type other than std::function even child classes. That is the strange part to me. I feel like it should be smart enough to store child classes. – Dalen Catt Oct 31 '17 at 19:50
0

Ok, So I know this has been sitting for a while. I've been doing heavy research into different event patterns trying to find something closer to what I was after. After pouring through everything, and with the advice of those who have left comments here, I have decided to use a Signal/Slot pattern, possibly the most widely used event pattern for C++. The way have have approached it is to have all of my "logic classes" (whether for a gui or for computation) keep a reference to a third "signal event holder class", which I am calling an event broker for simplicity. This is just about as good as I can get it. Any event that you might want to have can be added to this class, and it can be accessed and called from any class with a reference to the event broker. I found a pretty nice signal class made by Simon Schneegans, but I am actively trying to find/learn how to make something better (threadsafe, maybe faster?). If anyone is interested/looking for help like I was, you can find my super basic test case here: https://github.com/Moonlight63/QtTestProject

Thanks!

Dalen Catt
  • 111
  • 1
  • 9