Libasync tutorial

Lesson 3: Callbacks as parameters

Prev Index Next

This lesson demonstrates how callback functions are themselves objects and can even be passed to other callback functions.

Callback functions as objects


 1:   #include "async.h"
 3:   void
 4:   hello()
 5:   {
 6:     warn << "Hello, World!\n";
 7:     exit(0);
 8:   }
11:   void
12:   docallback(callback<void>::ref cb)
13:   {
14:     warn << "docallback\n";
15:     cb();
17:     // just for fun; we could have done:
18:     //
19:     // delaycb(1, 0, cb);
20:   }
22:   int
23:   main(int argc, char *argv[])
24:   {
25:     async_init();
27:     callback<void>::ref foo = wrap(hello);
28:     delaycb(1, 0, wrap(docallback, foo));
29:     amain();
30:   }

Line 27 and 28 could be combined in the more straightforward call:

27:     delaycb(1, 0, wrap(docallback, wrap(hello)));
but we wrote it the way we did to better explain what is going on.

The interesting bit is on line 27. The result of the call to wrap(hello) is of type callback<void>::ref. The < and > means we're dealing with C++ templates. Don't get nervous. The void between the < and > brackets is to indicate that the callback function (hello in this case) returns void. (The ::ref thingy refers to the the ref member of the callback class. Forget it.) Delaycb only accepts callbacks of type callback<void>::ref. But we'll see examples of more complicated callback objects later.

Line 28 is a delaycb that passes the hello callback function---i.e., foo---as a parameter to docallback. When docallback gets called, it invokes the callback function cb. Notice that cb() looks just like a regular function call.

Currying parameters

In the previous lesson you learned how to pass parameters to callback functions; you simply supply the parameters to the callback function as parameters to wrap. But you can get away with doing less.

You can let wrap pass a part---or even none---of the parameters to the callback function, and then supply the missing parameters later. The term for the technique demonstrated in the example below is "currying".


 1:   #include "async.h"
 3:   void
 4:   call_0(callback<void>::ref cb)
 5:   {
 6:     cb();
 7:   }
 9:   void
10:   call_1(callback<void, int>::ref cb)
11:   {
12:     cb(1);
13:   }
15:   void
16:   call_2(callback<void, int, int>::ref cb)
17:   {
18:     cb(11, 22);
19:   }
21:   void
22:   fn(int x, int y)
23:   {
24:     warn << "fn x " << x << " y " << y << "\n";
25:   }
27:   int
28:   main()
29:   {
30:     call_0(wrap(fn, 111, 222));
31:     call_1(wrap(fn, 99));
32:     call_2(wrap(fn));
33:   }

Function fn returns void and expects two int parameters. The wrap on line 30 passes these two required parameters. Now look at call_0, especially the type of parameter cb. Recall that callback<void>::ref cb means that cb is a callback function of type void that expects no additional parameters. That makes sense, since we already supplied the required parameters on line 30.

The wrap on line 31 passes only one parameter to fn. Now look at the parameter of type call_1. callback<void, int>::ref cb means that cb is a callback function that returns void that still expects one more (int) parameter. The first parameter was given on line 31 already, and the second is passed on line 12. Notice that x is 99, and y is 1---not the other way around.

Things are very similar with the wrap on line 32 and the type of cb on line 16. We pass no parameters, so we still have to supply two parameters on line 18.

In essence, wrap allows you to be "lazy" about passing parameters. The callback type expresses the return type of the callback (first parameter between < and >) and the types of all parameters that have yet to be passed to the callback function (all other template parameters).

Prev Index Next

Back to main.