0

I am looking everywhere (Modern C++ design & co) but I can't find a nice way to store a set of callbacks that accept different arguments and operate on different classes. I need this because I would like every object of my application to have the possibility to defer the execution of one of its methods to a master Clock object that, keeping track of the current time, can call this methods in the right moment. The code I am aiming for is something along the lines of:

In the void executeAction1Deferred(int time, int arg1, bool arg2) method of class1, where time is the execution time wanted in the future, there should be something like this:

Clock::defer(time, Class1::action1, this, arg1, arg2);

In Clock::defer(??? what signature ????) an object that represents this Task is stored in a priority queue where the time is the Key. For every Clock quantum the list of Tasks is then traversed and the tasks that need to be run in this quantum will be executed. Note that I have used "defer" as a static function because I intend the Clock object of a singleton, but it could also be a member function, it's just matter of choice.

I have thought of using void* to keep a variable number of the arguments, but having my action1() method accepting a void* is pretty terrible, also because I would need to craft a struct for the argument every time I use this function directly without deferring it.

I have been facing this problems various times in the past, and I have never found a really decent solution. Please note that being this a small multi-platform project where the simplicity of building for the inexperienced programmers that could extend it is essential, I don't want to use boost. But, every compiler for the platforms we address have std::tr1 bind. The question is: how to define a container of generic functions, each of these accepting a variable number of parameters (up to N ~ 5), and being a different member method of objects that do not derive from a common virtual class? Thanks

xlecoustillier
  • 16,183
  • 14
  • 60
  • 85
Alex Darsonik
  • 597
  • 5
  • 18
  • 1
    Why do you want to store functions that take a different set of arguments? How do you want to call those? As I understand your questions, you could just bind all except one argument (the time) - then you collection would store function that all have the same signature (taking a time), but internally may wrap functions with different signatures. – Björn Pollex Dec 04 '12 at 13:31
  • You could use a [variable argument list](http://msdn.microsoft.com/en-us/library/kb57fad8%28v=vs.71%29.aspx) as the signature, and let each method deal with what the arguments are. – parrowdice Dec 04 '12 at 13:36
  • @aleguna nope sorry... tr1 only – Alex Darsonik Dec 04 '12 at 13:36

3 Answers3

7

Use std::function<void()> to store the calls and then use a variadic template argument to forward and bind the function parameters:

class Clock
{
    vector<function<void()>> tasks;

    template<class F, class... Args>
    void defer(F f, Args&&... args)
    {
        tasks.push_back(bind(f, forward<Args>(args)...);
    }

}

void f(A a, B b);

int main()
{
    A a;
    B b;

    Clock c;
    c.push_back(f, a, b);
}

see also std::bind and std::mem_fun

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • Shouldn't that be `std::forward(args...)`? Also, what is the performance of this - is it comparable to calling the function directly (assuming all `args...` support move semantics)? – Anders Johansson Dec 04 '12 at 13:36
  • I think my usage is correct, the ellipse will cover the template parameter of forward. The performance is the same as if you use a virtual interface `class Function { virtual void f() = 0; }` and then manually create subclasses for each bound function with member variables to store the arguements. ie it will take a copy of the parameters and then call a polymorphic function. – Andrew Tomazos Dec 04 '12 at 13:39
  • Will this approach allow me to use member functions too? looks fantastic btw – Alex Darsonik Dec 04 '12 at 13:42
  • 1
    Yes `std::bind` will take a member function and then the first parameter is the this pointer. So `class A { void f(B b); }` is bound like `bind(&A::f, this, b)`. Also see `std::mem_fun`. (and you can call my clock class above like `c.defer(&A::f, this, ...)` – Andrew Tomazos Dec 04 '12 at 13:44
  • @AndersJohansson Calling a function through `std::function` instead of directly usually involves a virtual call, so this is acceptable. It is the price of type erasure. The required storage depends if the arguments are stored by value of by reference. – pmr Dec 04 '12 at 13:45
  • @pmr: I think bind will `decay` the arguments to store them by value. So if the function takes a `const string&` then the member variable will be just `string`. – Andrew Tomazos Dec 04 '12 at 13:48
  • @AndrewTomazos-Fathomling, tested with clang and both your and my syntax work fine. Is there any difference between `forward(args...)` and `forward(args)...`? – Anders Johansson Dec 04 '12 at 13:48
  • @AlexDarsonik: No this is the current C++ standard (2011). If you are using a legacy compiler than take a look at boost which has the same stuff. – Andrew Tomazos Dec 04 '12 at 13:49
  • @AndersJohansson: Both the snippets you mentioned are wrong and shouldn't compile afaik. The correct syntax is `forward(args)...` I think. – Andrew Tomazos Dec 04 '12 at 13:50
  • @AndrewTomazos-Fathomling So now I will need to understand if all the platforms I need this to work on (iOs, osx, win, android) support C++11... – Alex Darsonik Dec 04 '12 at 13:53
  • 1
    @AlexDarsonik: I think `function` and `bind` are supported on all those platforms. You may have to manually expand the variadic template argument feature (ie make n different calls with 0,1,2...n-1 with each possible number of template arguments), as that feature may not be available yet on ios or osx. I think it is on win and android. – Andrew Tomazos Dec 04 '12 at 14:00
  • @AndrewTomazos-Fathomling Yes, you need to `std::ref` or `std::cref` what you pass to bind to force a reference. This is more a safety measure than anything else, because it forces you to be aware of scope. – pmr Dec 04 '12 at 14:08
2

In C++11, store std::function<void()>. You can use std::bind to create the function from one of a different signature, for example:

std::vector<std::function<void()>> deferred;
deferred.push_back(std::bind(&Class1::action1, this, arg1, arg2));

// later...
for (auto f : deferred) f();

If C++11 isn't an option, then Boost has very similar function and bind templates. I think they might also exist in TR1, although I don't have any historical references to check.

If Boost really isn't an option (and TR1 doesn't provide these), then I strongly recommend that you make it an option; otherwise, use Boost as an example of how to implement this. Without variadic templates, it gets very hairy.

(And since you mention Modern C++ Design, read the section on type lists; that's how you would do it without variadic templates).

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • We are using gcc 4.1.2 for some embedded development, and it has both `std::tr1::function` and `std::tr1::bind`. – Björn Pollex Dec 04 '12 at 13:52
  • @BjörnPollex: OK, if those exist, then that's probably the answer. – Mike Seymour Dec 04 '12 at 13:53
  • I need to support iOs, osx, win, android, will do a quick check on these compilers.. otherwise I definitely need to build boost for each platform. The reason for not using boost is that I don't wanna be bothered at a later time, since Android will be used later. thx guys – Alex Darsonik Dec 04 '12 at 13:55
1

Since your callbacks get deferred including their provided arguments, the real signature will be void(), i.e. the Clock object won't provide arguments on its own and has no need to evaluate the results. So generally you will want to bind member function pointers (or other function pointers) together with the needed arguments and pass the result (a function object) to the clock. This is where boost::bind/std::tr1::bind and boost::function/std::function<void()> come in - or C++11 lambdas:

Clock::defer(time, boost::bind(&Class1::action1, this, arg1, arg2));
//C++11 lambda:
Clock::defer(time, [=](){action1(arg1, arg2);} );

But what you are doing is already done - take a look at Boost.Asio timers:

boost::asio::basic_deadline_timer timer(ioService, expiryTime);
timer.async_wait([=](){action1(arg1, arg2);} //perform action1 when the timer is done
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90