Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Standard function adaptors such as bind1st( )
and bind2nd( ) make some assumptions about the function objects
they process. Consider the following expression from the last line of the
earlier CountNotEqual.cpp program:
not1(bind1st(equal_to<int>(), 20))
The bind1st( ) adaptor creates a unary function
object of type binder1st, which simply stores an instance of equal_to<int>
and the value 20. The binder1st::operator( ) function needs to know
its argument type and its return type; otherwise, it will not have a valid
declaration. The convention to solve this problem is to expect all function
objects to provide nested type definitions for these types. For unary
functions, the type names are argument_type and result_type; for binary function objects they are first_argument_type, second_argument_type, and result_type. Looking at the implementation of bind1st( ) and binder1st
in the <functional> header reveals these expectations. First
inspect bind1st( ), as it might appear in a typical library
implementation:
template<class Op, class T>
binder1st<Op> bind1st(const Op& f, const
T& val) {
typedef typename Op::first_argument_type Arg1_t;
return binder1st<Op>(f, Arg1_t(val));
}
Note that the template parameter, Op, which
represents the type of the binary function being adapted by bind1st( ),
must have a nested type named first_argument_type. (Note also the use of
typename to inform the compiler that it is a member type name, as
explained in Chapter 5.) Now see how binder1st uses the type names in Op
in its declaration of its function call operator:
// Inside the implementation for binder1st<Op>
typename Op::result_type
operator()(const typename Op::second_argument_type&
x)
const;
Function objects whose classes provide these type names are
called adaptable function objects.
Since these names are expected of all standard function
objects as well as of any function objects you create to use with function
object adaptors, the <functional> header provides two templates
that define these types for you: unary_function and binary_function. You simply derive from these classes while filling in the argument types as template
parameters. Suppose, for example, that we want to make the function object gt_n,
defined earlier in this chapter, adaptable. All we need to do is the following:
class gt_n : public unary_function<int, bool> {
int value;
public:
gt_n(int val) : value(val) {}
bool operator()(int n) {
return n > value;
}
};
All unary_function does is to provide the appropriate
type definitions, which it infers from its template parameters as you can see
in its definition:
template<class Arg, class Result> struct
unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
These types become accessible through gt_n because it
derives publicly from unary_function. The binary_function
template behaves in a similar manner.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |