Rogue Wave banner
Previous fileTop of DocumentContentsIndexNext file

8.4 Constructing Functors

Functors are implemented with a handle-body architecture. Constructing a functor handle does not result in the construction of a complete functor object. You must first construct a functor body object and then bind that object to one or more functor handles.

As is the case with many other Threads.h++ classes, there are two commonly-used ways to construct a functor object:

You can also construct your own functor instance using a static make() function. See Section 8.4.3.3 for an example.

8.4.1 Analyzing Functor Requirements

The type of functor you build depends on the specific function you encapsulate and the way you plan to invoke it. Although the functor is built from a particular function, remember that the functor's arguments and return value are not necessarily the same as the function's; the functor must match the way the caller will invoke it. Each functor requires a handle object bound to a matching body. If you construct your functor with a global template function, the compiler chooses the body class for you, but you always have to specify the handle class.

To choose a functor handle class, answer these questions:

Once you know the answers to the questions, you are ready to choose a functor handle class. Functor handles are classified by whether they take 0, 1 or 2 caller arguments and whether they pass back the return value. If the function has a return value, and you need to use it, choose a functor handle with the R. Choose a handle class that ends in 0, 1, 2, R0, R1, or R2 to match the arguments and return value to be used when you invoke the functor.

For example, suppose your function looks like this:

and the char* argument will always be "calling foo". Then the char* value can be stored in the functor as a callee argument. The value of the long argument needs to be passed when the function is invoked, so you have one caller argument. Also, suppose you want to use the return value from foo(). Then you need the RWTFunctorR1 handle class.

8.4.2 Using Global Template Functions

A global template function relies on the compiler to extract the signature of a specified function and then select, construct, and initialize an appropriate functor body instance. To build a functor this way, you create a functor handle and call the global template function, specifying the information it needs to build and initialize the functor body. The compiler chooses the appropriate body class and builds the functor.

Global template functions are provided to simplify your development, but a word of caution; these are convenience functions that may not be 100% portable. They require your compiler to be able to extract template argument types from a function signature, and there are still a few, older compilers that are unable to do this. Check the Threads.h++ Build Guide for the latest information on which compilers suffer from this restriction. If you find yourself using one of these compilers, there's still hope. The templatized convenience functions can be replaced with macro invocations that, while not quite as simple to use, are 100% portable. See Section 8.4.3, "Using Macros."

You may also experience difficulties in compiling these functions when using a C++ reference data type. C++ compilers are notorious for having difficulties using reference types with templates. See Section 8.4.5, "Using Reference Types," for alternatives.

8.4.2.1 Choosing a Global Template Function

The names of the global template functions match the name of the functor handle type they return. Based on the analysis of functor requirements in Section 8.4.1, choose the functor handle whose name reflects the number of caller arguments (0, 1, or 2) and return value (R or not) that the functor needs. Use the matching global template function to construct the functor.

Functor Handle ClassGlobal Template Function
RWFunctor0*
rwtMakeFunctor0()
RWTFunctor1
rwtMakeFunctor1()
RWTFunctor2
rwtMakeFunctor2()
RWTFunctorR0
rwtMakeFunctorR0()
RWTFunctorR1
rwtMakeFunctorR1()
RWTFunctorR2
rwtMakeFunctorR2()

* RWFunctor0 does not have a T in its name, because it has no arguments or return value to templatize on. The matching function rwtMakeFunctor0() does have a t in its name, because it is a template. When the encapsulated function has a return value, rwtMakeFunctor0() uses templates to handle the value, even though it is finally ignored.

8.4.2.2 Constructing a Functor with a Global Template Function

Example 70 constructs a functor to encapsulate a member function with two arguments and a return value. The functor will take only one caller argument. The encapsulated function's second argument will come from the functor's stored callee data.

Example 70 -- Constructing a functor with a global template function

//1Include all the functor classes whose invocation method takes one argument and returns a value.
//2The function to be encapsulated is declared here.
//3Create an object to call the function on, required because this is a member function.
//4Construct a functor with the following characteristics:

The arguments to rwtMakeFunctorR1 specify the functor's arguments and return value and the function it encapsulates:

(double(*)(char))0
This is how the caller will invoke the functor. Because this object serves only to provide information to the compiler, but is not used any other way, it is made a null pointer (0). The pointer is cast to the function pointer ((double(*)(char)) with a return value of type double and one argument of type char. If you want any type conversions, you tell the compiler here. In this case, the types are the same as the return and first argument types for print() declared in line //2. You could use any compatible types, though, as long as the compiler knows how to do the conversion. (See Section 8.1.5.4.)
d2
This is the callee object, on which the function will be called (required for member functions).
&D::print
This is the function to be encapsulated. The compiler extracts its signature and automatically templatizes on those values for you. Because it is a member function, it requires the &. (For global functions, the compiler can do an implicit conversion from function to function pointer.)
3.14
The second argument to print(), a double, is given the permanent value of 3.14 and stored in the functor as callee data. Each time the functor is called, 3.14 will be passed as the second argument print(). Any arguments supplied from callee data must be the function's last arguments.

8.4.3 Using Macros

If you can't depend on your compiler to extract the signature of the function to be encapsulated, you can construct your functors with macros, instead of the global template functions. You must provide the types of the function's arguments and return value for the compiler to templatize on. The macro uses that data to select, construct, and initialize the appropriate functor body instance. Macros are less flexible and require more effort than global template functions, but macros are portable to all compilers.

8.4.3.1 Choosing a Macro

Based on the analysis of functor requirements in Section 8.4.1, choose the functor handle whose name reflects the number of caller arguments (0, 1, or 2) and return value (R or not) that the functor needs. Each functor handle class has a matching set of macros for building functors. To choose a macro, consider these questions:

The names of the macros are similar to the global template functions, except that they have extra character codes like the functor body classes.

You choose the macro that fits the function to be encapsulated.

Functor Handle ClassMacros
RWFunctor0*
rwtMakeFunctor0(G|M)[A1|A2|A3]
RWTFunctor1
rwtMakeFunctor1(G|M)[A1|A2]
RWTFunctor2
rwtMakeFunctor2(G|M)[A1|A2]
RWTFunctorR0
rwtMakeFunctorR0(G|M)[A1|A2|A3]
RWTFunctorR1
rwtMakeFunctorR1(G|M)[A1|A2]
RWTFunctorR2
rwtMakeFunctorR2(G|M)[A1|A2]

* RWFunctor0 does not have a T in its name, because it has no arguments or return value to templatize on. The matching macros do have a t in their names, to match the corresponding global template function.

8.4.3.2 Constructing a Functor with a Macro

Example 71 constructs a functor for a member function that takes a char and a double as arguments and returns an int. Although the encapsulated function has two arguments, the functor will take only one caller argument. The encapsulated function's second argument will come from the functor's stored callee data.

Example 71 -- Constructing a functor with a macro

//1Include all the functor classes whose invocation method takes one argument and returns a value.
//2The function to be encapsulated is declared here.
//3Construct a functor with the following characteristics:

The arguments to rwtMakeFunctorR1MA1 specify the functor's arguments and return value and the function's declared signature:

int, char
This is how the caller will invoke the functor, with a return value of type int and one argument of type char. If you want any type conversions, you tell the compiler here. In this case, the types are the same as the return and first argument types for print() declared in //2. You could use any compatible types, though, as long as the compiler knows how to do the conversion. (See Section 8.1.5.4.)
D, d2
This is the type and name of the callee object, on which the function will be called (required for a member function).
int, &D::print, char, double
This is the function to be encapsulated. Because it is a member function, it requires the &. (For global functions, the compiler can do an implicit conversion from function to function pointer.)
3.14
The second argument to print(), a double, is given the permanent value of 3.14 and stored in the functor as callee data. Each time the functor is called, 3.14 will be passed as the second argument to print(). Any arguments supplied from callee data must be the function's last arguments.

8.4.3.3 Macro Expansion at Compile Time

The macros resolve to code that constructs a functor using the static make() function provided by the body class. For example, the macro in Example 71 produces this code:

In the debugger and in compiler and linker errors, you see references to code similar to this.

8.4.4 Copying and Assigning Functors

Because functors use the handle-body idiom, copying functors follows the general rules discussed in Section 7.3.1.4, "Handle-body Mechanics." When a handle class instance is copy-constructed from another handle class instance, the new handle is bound to the same body instance, if any, pointed-to by the other handle.

Similarly, assigning one handle to another causes the left-hand instance to detach from its current representation, if any, and then binds it to the same body instance, if any, pointed-to by the right-hand instance.

8.4.5 Using Reference Types

C++ compilers have difficulties using reference types with templates. If you are passing callee data as references, you must construct your functors directly with macros or the body class make() functions. In the following code, for example, the functor is created with a macro, because the action() function's counter argument is an int&.

Reference arguments do not work with the global template functions, because the function parameter and the data value are represented by different template arguments, and you can't cast the value to be a reference for the purposes of instantiation.


Previous fileTop of DocumentContentsIndexNext file

©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.