14.8. Call Operator and Function ObjectsThe function-call operator can be overloaded for objects of class type. Typically, the call operator is overloaded for classes that represent an operation. For example, we could define a struct named absInt that encapsulates the operation of converting a value of type int to its absolute value: struct absInt { int operator() (int val) { return val < 0 ? -val : val; } }; This class is simple. It defines a single operation: the function-call operator. That operator takes a single parameter and returns the absolute value of its parameter. We use the call operator by applying an argument list to an object of the class type, in a way that looks like a function call: int i = -42; absInt absObj; // object that defines function call operator unsigned int ui = absObj(i); // calls absInt::operator(int) Even though absObj is an object and not a function, we can make a "call" on that object. The effect is to run the overloaded call operator defined by the object absObj. That operator takes an int value and returns its absolute value.
Objects of class types that define the call operator are often referred to as function objectsthat is, they are objects that act like functions. 14.8.1. Using Function Objects with Library AlgorithmsFunction objects are most often used as arguments to the generic algorithms. As an example, recall the problem we solved in Section 11.2.3 (p. 400). That program analyzed words in a set of stories, counting how many of them were of size six or greater. One part of that solution involved defining a function to determine whether a given string was longer than six characters in length:
// determine whether a length of a given word is 6 or more
bool GT6(const string &s)
{
return s.size() >= 6;
}
We used GT6 as an argument to the count_if algorithm to count the number of words for which GT6 returned true: vector<string>::size_type wc = count_if(words.begin(), words.end(), GT6); Function Objects Can Be More Flexible than FunctionsThere was a serious problem with our implementation: It hardwired the number six into the definition of the GT6 function. The count_if algorithm runs a function that takes a single parameter and returns a bool. Ideally, we'd pass both the string and the size we wanted to test. In that way, we could use the same code to count strings of differing sizes. We could gain the flexibility we want by defining GT6 as a class with a function-call member. We'll name this class GT_cls to distinguish it from the function:
// determine whether a length of a given word is longer than a stored bound
class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }
private:
std::string::size_type bound;
};
This class has a constructor that takes an integral value and remembers that value in its member named bound. If no value is provided, the constructor sets bound to zero. The class also defines the call operator, which takes a string and returns a bool. That operator compares the length of its string argument to the value stored in its data member bound. Using a GT_cls Function ObjectWe can do the same count as before but this time we'll use an object of type GT_cls rather than the GT6 function: cout << count_if(words.begin(), words.end(), GT_cls(6)) << " words 6 characters or longer" << endl; This call to count_if passes a temporary object of type GT_cls rather than the function named GT6. We initialize that temporary using the value 6, which the GT_cls constructor stores in its bound member. Now, each time count_if calls its function parameter, it uses the call operator from GT_cls. That call operator tests the size of its string argument against the value in bound. Using the function object, we can easily revise our program to test against another value. We need to change only the argument to the constructor for the object we pass to count_if. For example, we could count the number of words of length five or greater by revising our program as follows: cout << count_if(words.begin(), words.end(), GT_cls(5)) << " words 5 characters or longer" << endl; More usefully, we could count the number of words with lengths greater than one through ten: for (size_t i = 0; i != 11; ++i) cout << count_if(words.begin(), words.end(), GT(i)) << " words " << i << " characters or longer" << endl; To write this program using a functioninstead of a function objectwould require that we write ten different functions, each of which would test against a different value.
14.8.2. Library-Defined Function ObjectsThe standard library defines a set of arithmetic, relational, and logical function-object classes, which are listed in Table 14.3 on the following page. The library also defines a set of function adaptors that allow us to specialize or extend the function-object classes defined by the library or those that we define ourselves. The library function-object types are defined in the functional header.
Each Class Represents a Given OperatorEach of the library function-object classes represents an operatorthat is, each class defines the call operator that applies the named operation. For example, plus is a template type that represents the addition operator. The call operator in the plus template applies + to a pair of operands. Different function-object classes define call operators that perform different operations. Just as plus defines a call operator that executes the + operator; the modulus class defines a call operator that applies the binary % operator; the equal_to class applies ==; and so on. There are two unary function-object classes: unary minus (negate<Type>) and logical NOT (logical_not<Type>). The remaining library function objects are binary function-object classes representing the binary operators. The call operators defined for the binary operators expect two parameters of the given type; the unary function-object types define a call operator that takes a single argument. The Template Type Represents the Operand(s) TypeEach of the function-object classes is a class template to which we supply a single type. As we know from the sequential containers such as vector, a class template is a class that can be used on a variety of types. The template type for the function-object classes specifies the parameter type for the call operator. For example, plus<string> applies the string addition operator to string objects; for plus<int> the operands are ints; plus<Sales_item> applies + to Sales_items; and so on: plus<int> intAdd; // function object that can add two int values negate<int> intNegate; // function object that can negate an int value // uses intAdd::operator(int, int) to add 10 and 20 int sum = intAdd(10, 20); // sum = 30 // uses intNegate::operator(int) to generate -10 as second parameter // to intAdd::operator(int, int) sum = intAdd(10, intNegate(10)); // sum = 0 Using a Library Function Object with the AlgorithmsFunction objects are often used to override the default operator used by an algorithm. For example, by default, sort uses operator< to sort a container in ascending order. To sort the container in descending order, we could pass the function object greater. That class generates a call operator that invokes the greater-than operator of the underlying element type. If svec is a vector<string>
// passes temporary function object that applies > operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
sorts the vector in descending order. As usual, we pass a pair of iterators to denote the sequence that should be sorted. The third argument is used to pass a predicate (Section 11.2.3, p. 402) function to use to compare elements. That argument is a temporary of type greater<string>, which is a function object that applies the > operator to two string operands. 14.8.3. Function Adaptors for Function ObjectsThe standard library provides a set of function adaptors with which to specialize and extend both unary and binary function objects. The function adaptors are divided into the following two categories. The library defines two binder adaptors: bind1st and bind2nd. Each binder takes a function object and a value. As you might expect, bind1st binds the given value to the first argument of the binary function object, and bind2nd binds the value to the second. For example, to count all the elements within a container that are less than or equal to 10, we would pass count_if the following: count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10)); The third argument to count_if uses the bind2nd function adaptor. That adaptor returns a function object that applies the <= operator using 10 as the right-hand operand. This call to count_if counts the number of elements in the input range that are less than or equal to 10. The library also provides two negators: not1 and not2. Again, as you might expect, not1 reverses the truth value of a unary predicate function object, and not2 reverses the truth value of a binary predicate function object. To negate our binding of the less_equal function object, we would write count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10))); Here we first bind the second operand of the less_equal object to 10, effectively transforming that binary operation into a unary operation. We then negate the return from the operation using not1. The effect is that each element will be tested to see if it is <= to 10. Then, the truth value of that result will be negated. In effect, this call counts those elements that are not <= to 10. |