1

I'm trying to implement a basic signal/slots system. Everything is up and running but I'm trying to improve the usability of my implementation.

At the moment this is how you connect to a signal:

struct X
{
    void memberFunction(int a, int b)
    {
         // do something
    }
};

void globalStaticFunction(int a, int b)
{
    // do something
}

// this is what the signal::connect function looks like at the moment
ConnectionHandle connect(std::function<RetType(Args...)> func);


int main()
{
    // test instance
    X x;

    // example signal
    Signal<void(int, int)> mySignal;


    // connect a static function to the signal
    mySignal.connect(&globalStaticFunction);

    // connect a member function
    // here we have to use std::bind to get a std::function
    mySignal.connect(std::bind(&X::memberFunction, &x, _1, _2));
}

I would like to supply the user with an easier way to bind member functions. I had something like this in mind (similar to how Qt does it):

// prefered member function connect
// connect(instance_ptr, function_ptr)
mySignal.connect(&x, &X::memberFunction);

Is it possible to get a std::function object for a member function without using std::bind? If not, is there an easy way to generate the std::bind call with just the instance_ptr and the function_ptr for the member function?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
derkie
  • 85
  • 1
  • 5
  • See http://stackoverflow.com/questions/21216762/partial-binding-of-function-arguments for some thoughts on partial binding of functions (which is basically what you're trying to do here). Unfortunately, there's nothing like that in the standard library. – Sneftel Jul 14 '14 at 21:35

2 Answers2

7

As a member of Signal:

template<typename C>
ConnectionHandle connect( C* c, RetType(C::*func)(Args...)) {
  return connect( [c,func](Args&&...args) {
    return (c->*func)( std::forward<Args>(args)... );
  });
}

template<typename C, typename Method, typename... Unused>
ConnectionHandle connect( C* c, Method* m, Unused&&...) {
  return connect( [c,func](Args&&...args) {
    return (c->*m)( std::forward<Args>(args)... );
  });
}

The first overload gives you the ability to distinguish between overloaded methods.

The second, if the first fails, lets you use signature-compatible methods.

The ... portion makes sure the first is a better match if they both match (I hope!)

Args&&... should work with a std::function, assuming the std::function properly perfect forwards. If not, replace with Args....

I see no need for bind or variants, given that we are going to stuff it into a std::function immediately anyhow. Create a lambda, they are well suited to the task.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Oh, lambdas. How simple! – Mooing Duck Jul 14 '14 at 22:06
  • I have just started to transition over to c++11 and never really used boost. I'm blown away with what you can do with lambdas, your solution is very elegant indeed. I have just one concern: So if I understand how lambdas work correctly, then the lambda in your example creates an extra class that takes c and func as arguments in the constructor. The lambda function is then a member of this class and in turn calls func on the c pointer. Is it possible that this affects performance in any way? – derkie Jul 15 '14 at 09:47
  • Also on an other note, I read [this](http://www.cprogramming.com/c++11/c++11-lambda-closures.html) article about lambdas among others to get up to speed. And the author was calling static functions 'functions' and member functions 'methods'. Is this a common way of referencing functions today? Coming from java (standard in my uni) I always used the two interchangeably. – derkie Jul 15 '14 at 09:47
  • 1
    Update on the performance: I did some very simple tests comparing the run times of callbacks bound by using std::bind and the ones using your lambda solution. I timed 100Mil calls for Release and 1Mil for Debug. In Debug mode the lambda solution is consistently ~20% faster. But most STL stuff has poor performance in Debug mode (I'm working with VS2013 btw). In release mode both solutions perform about the same (+-1%), which is awesome. – derkie Jul 15 '14 at 10:42
  • @derkie The lambda creates a function object -- but that is also how `bind` works. Calling a free function a "method" would confuse someone in C++. – Yakk - Adam Nevraumont Jul 15 '14 at 12:26
  • @derkie: On vocabulary (IMO): "Function" can refer to any sort of functions unless specified in a particular context. If the article says he only uses the term for static functions, that's fine, but only applies to his paper. Both static and non-static Member functions are called "methods" in many languages, but also "functions" sometimes. "Free functions" (functions not part of classes, like main in C++) are almost never called methods in C++. – Mooing Duck Jul 15 '14 at 17:12
3

This trick is good in C++03, but for C++11 please use Yakk's answer.

Something akin to this probably does the trick.

First: Make a SIMPLE version of bind that only binds the this.

template<class T, class RetType, class...Args>
RetType CallMemFn(T* obj, RetType(T::*func)(Args...), Args...args)
{return ((*obj).*(func))(std::forward<Args>(args)...);}

template<class T, class RetType, class...Args>
struct boundthis {
    typedef RetType result_type;
    typedef T* thistype;
    typedef RetType(*signature)(Args...);
    boundthis(T* self, RetType(T::*func)(Args...)) :self(self), func(func) {}
    RetType operator()(Args...args) {return CallMemFn(self,func,std::forward<Args>(args)...);}
private:
    T* self;
    RetType(T::*func)(Args...);
};
template<class T, class RetType, class...Args>
boundthis<T,RetType,Args...> bindthis(T* self, RetType(T::*func)(Args...))
{return boundthis<T,RetType,Args...>(self, func);}

And finally, the wrapper you desire:

template<class T, class RetType, class...Args>
ConnectionHandle connect(T* obj, RetType(T::*func)(Args...))
{return connect(std::function<RetType(Args...)>(bindthis(obj, func)));}

http://ideone.com/a2rze0

Community
  • 1
  • 1
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • As an aside, I think the OP doesn't want to use `bind` external to the interface: I suspect internal use of `bind` would be acceptable... – Yakk - Adam Nevraumont Jul 14 '14 at 22:11
  • @Yakk: Absolutely, except, [bind can't bind easily bind only the first argument](http://stackoverflow.com/questions/21216762/partial-binding-of-function-arguments) (See Rev1 of my answer) – Mooing Duck Jul 14 '14 at 22:36