Section 14.8. Call Operator and Function Objects


14.8. Call Operator and Function Objects

The 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.

The function-call operator must be declared as a member function. A class may define multiple versions of the call operator, each of which differs as to the number or types of their parameters.



Objects of class types that define the call operator are often referred to as function objectsthat is, they are objects that act like functions.

Exercises Section 14.8

Exercise 14.31:

Define a function object to perform an if-then-else operation: The function object should take three parameters. It should test its first parameter and if that test succeeds, it should return its second parameter, otherwise, it should return its third parameter.

Exercise 14.32:

How many operands may an overloaded function-call operator take?


14.8.1. Using Function Objects with Library Algorithms

Function 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 Functions

There 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 Object

We 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.

Exercises Section 14.8.1

Exercise 14.33:

Using the library algorithms and the GT_cls class, write a program to find the first element in a sequence that is larger than a specified value.

Exercise 14.34:

Write a function-object class similar to GT_cls but that tests whether two values are equal. Use that object and the library algorithms to write a program to replace all instances of a given value in a sequence.

Exercise 14.35:

Write a class similar to GT_cls, but that tests whether the length of a given string matches its bound. Use that object to rewrite the program in Section 11.2.3 (p. 400) to report how many words in the input are of sizes 1 through 10 inclusive.

Exercise 14.36:

Revise the previous program to report the count of words that are sizes 1 through 9 and 10 or more.


14.8.2. Library-Defined Function Objects

The 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.

Table 14.3. Library Arithmetic Function Objects

Arithmetic Function Objects Types

 

plus<Type>

minus<Type>

multiplies<Type>

divides<Type>

modulus<Type>

negate<Type>

applies +

applies --

applies *

applies /

applies %

applies --

Relational Function Objects Types

 

equal_to<Type>

not_equal_to<Type>

greater<Type>

greater_equal<Type>

less<Type>

less_equal<Type>

applies ==

applies !=

applies >

applies >=

applies <

applies <=

Logical Function Object Types

 

logical_and<Type>

logical_or<Type>

logical_not<Type>

applies &&

applies |

applies !


Each Class Represents a Given Operator

Each 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) Type

Each 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 Algorithms

Function 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 Objects

The 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.

  1. Binders: A binder is a function adaptor that converts a binary function object into a unary function object by binding one of the operands to a given value.

  2. Negators: A negator is a function adaptor that reverses the truth value of a predicate function object.

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.



C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net