Post callbacks to a task queue using boost::bind

1.1k views Asked by At

Suppose I have a function called subscribe() that takes a callback handler, which will be called when the event is triggered.

Now, I have another version, called subscribe2(). Everything is the same except that, when triggered, it needs to post it to an event queue. It is implemented using the original subscribe(), with a helper funciton called helper(). All it does is to bind the original handler and whatever additional arguments into a functor, and call postToEventQueue().

Now, I wonder if there's a way to eliminate the helper function, so that in subsribe2(), I can somehow package the postToTaskQueue() function and the original callback handler directly, and pass it to subscribe(). The reason is that I have a lot of different handler types, and it is tedious and tiring to introduce helper function all over the place. Afterall, boost::bind is supposed to return a new function given the original function, right? I am trying to generate the helper function directly with boost::bind.

One attempt is to say

subscribe(boost::bind(boost::bind(postToTaskQueue, boost::bind(_1, _2)), cb, _1)); 

in subscribe2(), but it doesn't work. Is it possible at all?

Please see detailed example code below. Thanks!

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>

typedef boost::function<void(int)> SomeCallback;
typedef boost::function<void()> Task;

void handler(int i){
 std::cout << "i=" << i <<std::endl;
}

void subscribe(SomeCallback cb)
{
  cb(100);  //just invoke the callback for simplicity
}

void postToTaskQueue(Task t)
{
   t();  // just invoke the task for simplicity
}

void helper(SomeCallback cb, int i)
{
   Task t = boost::bind(cb, i);
   postToTaskQueue(t);
}

void subscribe2(SomeCallback cb)
{
  subscribe(boost::bind(helper, cb, _1));

  // this does not work..
  // subscribe(boost::bind(boost::bind(postToTaskQueue, boost::bind(_1, _2)), cb, _1)); 
}
int main()
{
  subscribe(boost::bind(handler, _1));
  subscribe2(boost::bind(handler, _1));
}
2

There are 2 answers

0
sehe On

I have no answer. However, I've played with this for over an hour:

  • boost::bind
  • boost::apply<>
  • boost::protect

Maybe, just maybe, a more experienced boost developer could take it from here:

void subscribe2(SomeCallback cb)
{
    using boost::bind;
    using boost::protect;
    using boost::apply;

    bind(cb, 41)(); // OK of course
    postToTaskQueue(bind(cb, 46)); // also fine
    bind(postToTaskQueue, protect(bind(cb, 146)))(); // boost::protect to the rescue

    postToTaskQueue(bind(apply<void>(), cb, 47));
    bind(postToTaskQueue, protect(bind(apply<void>(), cb, 147)))();

The above prints

i=41
i=46
i=146
i=47
i=147

But, sadly, I can't seem to make this this thing parameterizing (as suggested should work in the documentation on composition using Nested Binds):

    // but sadly, this appears to not work ...
    auto hmm = bind(postToTaskQueue, bind(apply<void>(), cb, _1));
    hmm(997); // FAIL
}

Here's a fully compiled demo showing the state of affairs: Live on Coliru

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/bind/protect.hpp>
#include <boost/bind/apply.hpp>
#include <iostream>

typedef boost::function<void(int)> SomeCallback;
typedef boost::function<void()>    Task;

void handler(int i){
    std::cout << "i=" << i <<std::endl;
}

void subscribe(SomeCallback cb)
{
    cb(100);  //just invoke the callback for simplicity
}

void postToTaskQueue(Task t)
{
    t();  // just invoke the task for simplicity
}

void helper(SomeCallback cb, int i)
{
    postToTaskQueue(boost::bind(cb, i));
}

void subscribe2(SomeCallback cb)
{
    using boost::bind;
    using boost::protect;
    using boost::apply;

    bind(cb, 41)(); // OK of course
    postToTaskQueue(bind(cb, 46)); // also find
    bind(postToTaskQueue, protect(bind(cb, 146)))(); // boost::protect to the rescue

    postToTaskQueue(bind(apply<void>(), cb, 47));
    bind(postToTaskQueue, protect(bind(apply<void>(), cb, 147)))();

    // but sadly, this appears to not work ...
    auto hmm = bind(postToTaskQueue, bind(apply<void>(), cb, _1));
    //hmm(997); // FAIL
}
int main()
{
    subscribe (boost::bind(handler, _1));
    subscribe2(boost::bind(handler, _1));
}
1
Eric Niebler On

You are binding a function (helper) that itself does a bind. That mean you are (indirectly) binding bind itself. This is the key insight. The solution is to write a little bind function object wrapper that can itself be bound. Here's what my solution looks like:

#include <utility>
#include <iostream>
#include <boost/function.hpp>
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/core/argument.hpp>
using boost::phoenix::placeholders::_1;
typedef boost::function<void(int)> SomeCallback;
typedef boost::function<void()> Task;

struct bind_t
{
    template<typename Sig>
    struct result;

    template<typename This, typename ...A>
    struct result<This(A...)>
    {
        typedef decltype(boost::phoenix::bind(std::declval<A>()...)) type;
    };

    template<typename ...A>
    auto operator()(A &&...a) const -> decltype(boost::phoenix::bind(std::forward<A>(a)...))
    {
        return boost::phoenix::bind(std::forward<A>(a)...);
    }
};

bind_t const bind = {};

void handler(int i)
{
    std::cout << "i=" << i <<std::endl;
}

void subscribe(SomeCallback cb)
{
    cb(100);  //just invoke the callback for simplicity
}

void postToTaskQueue(Task t)
{
    t();  // just invoke the task for simplicity
}

void subscribe2(SomeCallback cb)
{
    subscribe(bind(postToTaskQueue, bind(bind, cb, _1)));
}

int main()
{
    subscribe(::bind(handler, _1));
    subscribe2(::bind(handler, _1));
}

I switched to Phoenix's bind because it lets you bind polymorphic function objects (which bind above is).

This solution requires decltype. It also uses variadics, but that can be faked with overloads up to N arguments. Rvalue refs are also a convenience that can be done without with a little more work.