-1

Ok so I already have this as a working example but I'm just trying to clean it up.

currently I have a subscription styled event system with callback routines.

I have no problem with adding regular functions and class member functions into the storage variable for it.

But id like to clean it up by overloading my Event.subscribe method.

Currently it does something like so:

template <class T>
class EventObject
{
public:
    void subscribe(const T& arg)
    {
        this->events.push_back(arg);
    }
std::vector<T> events;
}

And then In my main its used like so:

void someCallback(const std::string& line)
{
    cout <<line;
}

typedef  std::function<void(const std::string& line)> onMessageCallbackFunction;
EventObject<onMessageCallbackFunction> messageCallback;

messageCallback.subscribe(someCallback);

Now the pickle comes forward when I use a Class member function. I hate having to intrinsically bind the function before passing it. It makes the code look dirty.

for instance:

class B
{
    void callbk(const std::string& line) {}
};

B b;
using std::placeholders::_1;
onMessageCallbackFunction memfunc = std::bind(&B::callbk, b, _1);
messageCallback.subscribe(memfunc);

Now yea it works, but it looks crappy. what is the syntax so I can just preform:

messageCallback.subscribe(&B::callbk, b);

Tried a few but I cant seem to get it JUST right.

What it is not:

template <class J>
void subscribe(J::T cls,T& t); //Nope.
void subscribe(J&& cls,T& t); //Nope.

SUPER CLOSE: (thx tobi)

template< class J, class... Args >
void subscribe(void(J::*funct)(Args), J& inst) {
    using std::placeholders::_1;
    subscribe(std::bind(funct, inst, _1));
}

Now just need to generic the argument list.

Steven Venham
  • 600
  • 1
  • 3
  • 18

2 Answers2

1

My suggestion would be to use a lambda (std::bind was obsolete the day it came out because lambdas are superior in almost every way).

It never hurts to spell out the lambda separately. There is no performance cost in doing so. The compiler will elide un-necessary copies.

I would also use std::function to store your signals and convert from the compatible passed-in function type.

Your call-site code then looks something like this, which I think you'll agree is clean and expressive:

EventObject<void(std::string)> messageCallback;
B b;

auto handler = [&b](std::string const& line)
{
    b.callbk(line);
};

messageCallback.subscribe(handler);

Complete example:

#include <string>
#include <vector>
#include <functional>
#include <type_traits>

template<class Sig> struct EventObject;

template<class R, class...Args>
struct EventObject<R (Args...)>
{
    using slot_type = std::function<R(Args...)>;

    template
    <
        class F,
        typename std::enable_if
        <
            std::is_constructible
            <
                slot_type, 
                typename std::decay<F>::type
            >::value
        >::type * = nullptr
    >
    void subscribe(F&& f)
    {
        subscribers_.emplace_back(std::forward<F>(f));
    }

    std::vector<slot_type> subscribers_;
};

class B
{
public:
    void callbk(const std::string& line) {}
};

int main()
{
    EventObject<void(std::string)> messageCallback;
    B b;

    auto handler = [&b](std::string const& line)
    {
        b.callbk(line);
    };

    messageCallback.subscribe(handler);
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

what is the syntax so I can just preform:

messageCallback.subscribe(&B::callbk, b);

Your interface already allows to pass any callable, and there is no reason to make it more complicated. The EventObject should not be aware whether the callback is a free function or something else. I completely agree that std::bind is tedious and does not look nice and I would rather use a lambda:

EventObject<onMessageCallbackFunction> messageCallback;
B b;
messageCallback.subscribe([&b](const std::string& line){return b.callbk(line);});

PS: I am not sure if I corretly understand your motivation to use a tempalte. It seems that you want to use it only with onMessageCallbackFunctions, such that simply storing a vector of them should be fine.

PPS: for the sake of completeness here is how you could hide the bind inside the method:

#include <vector>
#include <string>
#include <iostream>
#include <functional>
using std::placeholders::_1;

template <class T>
class EventObject {
public:
    void subscribe(const T& arg)
    {
        this->events.push_back(arg);
    }
    template<typename C>
    void subscribe(void(C::*mem_fun)(const std::string&),C& c){
        subscribe(std::bind(mem_fun,c,_1));
    }       
    std::vector<T> events;
};

typedef  std::function<void(const std::string& line)> onMessageCallbackFunction;

struct B {
    void callbk(const std::string& line) {}
};

int main(){
    EventObject<onMessageCallbackFunction> messageCallback;
    B b;
    messageCallback.subscribe(&B::callbk,b);
}

This is only for member functions that return void and return a string, but I wouldnt even bother to make it generic and just use lambdas.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185