Several aspects of Nano make use of functors, self-contained objects that represent an arbitrary function call.
A functor can represent an ordinary C function, a static method of a class, or even a member function of a class (capturing both the method to invoke, and the object on which to invoke it).
Binding a Function
To produce a functor, a function and a parameter list are "bound" together to produce a self-contained object that executes the function when invoked.
This is performed with nbind:
extern void MyFunction(int a, int b); // Bind a call to MyFunction into myFunc NFunctor myFunc = nbind(MyFunction, 50, 60); // Equivalent to MyFunction(50, 60) myFunc();
Parameters passed to nbind are copied, allowing the functor itself to be copied or passed to other routines before finally being invoked to perform MyFunction.
NFunctor.h provides some helpers to simplify functor syntax:
#define BindFunction(_function, ...) nbind(_function, ...) #define BindMethod( _object, _method, ...) nbind(_method, _object, ...) #define BindSelf( _method, ...) nbind(_method, this, ...) typedef nfunctor<void (void)> NFunctor;
Although nbind or nfunctor can be used directly, these helpers allow functor usage to be self-documenting.
The parameters passed to nbind can be real values, or a "placeholder" value. Placeholders, which are named _1 to _10, are substituted with the appropriate parameter when the functor is invoked.
This feature allows functors to act as type-safe adapters, which map the invocation of a function to its execution:
void MyFunction1(int a, const char *b, int c); void MyFunction2( const char *b); ... typedef nfunctor<void (int, int)> NFunctor1; typedef nfunctor<void (int) > NFunctor2; ... NFunctor1 myFunc1 = BindFunction(MyFunction1, _2, "50", _1); NFunctor2 myFunc2 = BindFunction(MyFunction2, "50"); ... myFunc1(60, 40); // Equivalent to MyFunction1(40, "50", 60) myFunc2(23); // Equivalent to MyFunction2( "50")
myFunc1 expects two integers, while MyFunction1 expects two integers and a pointer. The functor maps its two parameters to the placeholders in the bound function, re-ordering them to produce the final parameter list of (40, "50", 60).
myFunc2 expects an integer, while MyFunction2 expects a pointer. Since all of the parameters for MyFunction2 were determined at bind-time, the supplied parameter is simply discarded when the functor is invoked.
This "adapter" pattern is extremely powerful, as it allows the parameters supplied at run-time to be combined with the parameters supplied at bind-time.
The nfunctor typedef effectively defines the prototype for invocation, while the function passed to nbind defines the prototype for execution. Both sides are fully type-checked, and can only be compiled if the invoked function can be mapped to the executed function.
A practical example can be found in NTimer, which invokes a functor whenever a timer fires. The NTimer functor is defined to take a single NTimerState parameter:
typedef nfunctor<void (NTimerState theState)> NTimerFunctor;
Since the functor can map this parameter list to that of the target function, this allows:
void MyFunction1(void); void MyFunction2(const char *x); void MyFunction3(UInt32 a, NTimerState theState); ... mTimer.AddTimer(BindFunction(MyFunction1 ), 1.0f); mTimer.AddTimer(BindFunction(MyFunction2, "2 secs" ), 2.0f); mTimer.AddTimer(BindFunction(MyFunction3, 42, _1), 3.0f); ... // After 1 second, mTimer invokes MyFunction1() // After 2 seconds, mTimer invokes MyFunction2("2 secs"); // After 3 seconds, mTimer invokes MyFunction3(42, kNTimerFired);
Although NTimer invokes each functor with a single kNTimerFired parameter, nfunctor and nbind can map these calls to the bound functions as shown.