10. Function Objects

10.1 Function Objects and the Strange Syntax...

Function objects are a fairly new concept of the C++ programming language. Their usage may seem odd at first glance, and the syntax may appear to be confusing. It may be a good idea to read this chapter slowly and deliberately, several times. Readers are encouraged to pull out the code snippets, build them, and try stepping through the debugger.

10.2 Introduction

A function object is an object of a class/struct type that includes an operator() member function. An operator() member function allows us to create an object that behaves like a function. For example, a Matrix class could overload operator() to access an element whose row and column index are specified as arguments to operator().

class Matrix
{
    public:
        Matrix(int, int) ;
        //…
        int operator(int, int) const ;
    private:
        Array<int> m_matrix ;
        int m_row ;
        int m_col ;
} ;
int Matrix::operator(int r, int c) const
{
    if ( r > 0 && r <= m_row && c > 0 && c <= m_col)
        return m_matrix[r, c] ;
    else
        return 0 ;
}
Matrix m(10, 10) ;
//….
int element = m(5, 5) ;

Function objects also have the advantage of the fact that () is the only operator that can take an arbitrary number of arguments. Ever felt the need to create a new function from existing function(s)? C++ does not allow that. How about using function objects? It is important to note:

10.3 STL Function Objects

The Standard Template Library provides function objects for standard math operations such as addition, subtraction, multiplication, and division. STL also provides function objects for unary operations, logical operations, bitwise operations, and comparison operations. Table 12 lists all the function objects provided by STL. The function objects have been defined in the header file <functional>.

Table 12. STL Function Objects
divides logical_and
equal_to logical_not
greater logical_or
greater_equal minus
less modulus
less_equal negate
plus not_equal_to
times  

These function objects can be used with STL algorithms to change the default behavior of the STL algorithms. Consider the STL algorithm sort. By default, sort sorts the elements of a sequence in ascending order. To sort the elements in a descending order use the STL function object greater<T> as demonstrated in the following example:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
void main()
{
    typedef vector<int, allocator<int> > VECTOR_INT ;
    typedef ostream_iterator<int, char, char_traits<char> > OUTIT ;
    int a[10] = {30, 56, 79, 80, 45, 10, 4, 125, 67, 80} ;
    VECTOR_INT v1(a, a + 10) ;
    OUTIT out(cout, ", ") ;
    cout << "v1 before sort(first, last)" << endl ;
    copy(v1.begin(), v1.end(), out) ;
    cout << endl ;

    // using default sort algorithm, sorts elements in ascending order
    sort(v1.begin(), v1.end()) ;
    cout << "v1 after sort(first, last)" << endl ;
    copy(v1.begin(), v1.end(), out) ;
    cout << endl ;
    random_shuffle(v1.begin(), v1.end()) ;

    cout << "v1 before sort(first, last, using STL function object)" << endl ;
    copy(v1.begin(), v1.end(), out) ;
    cout << endl ;

    //customizing sort algorithm, to sort elements in descending order
    //using a function object.
    sort(v1.begin(), v1.end(), greater<int>()) ;
    cout << "v1 after sort(first, last, using STL function-object)" << endl ;
    copy(v1.begin(), v1.end(), out) ;
    cout << endl ;
}

Build the above sample and see the output. We recommend stepping through the code in the debugger to see what is happening under the covers.

STL function objects can also be used in  C++ programs directly. For example:

int n1 = (times<int>())(10, 20) ;   // sets n1 to value 200
int n2 = (minus<int>())(300, 100) ; // sets n2 to value 200

10.4 STL Function Adapters

Function adapters help us construct a wider variety of function objects using existing function objects. Using function adapters is often easier than directly constructing a new function object type with a struct or class definition.

STL provides three categories of function adapters: negators, binders, and adapters for pointer to functions.

10.4.1 Binders

Binders are function adapters that convert binary function objects into unary function objects by binding an argument to some particular value.

STL provides two types of binder function objects: binder1st<Operation> and binder2nd<Operation>. A binder function object takes only a single argument.

STL provides two template functions, bind1st and bind2nd, to create binder function objects. The functions bind1st and bind2nd each take as arguments a binary function object f and a value x. As might be surmised, bind1st returns a function object of type binder1st<Operation>, and bind2nd returns a function object of type binder2nd<Operation>. Here are the function prototypes for the bind1st and bind2nd functions:

template <class Operation, class T>
binder1st<Operation> bind1st(const Operation& f, const T& x) ;
template <class Operation, class T>
binder2nd<Operation> bind2nd(const Operation& f, const T& x) ;

Let us try to understand what binders do by looking at the following:

int result = (bind2nd(greater<int>(), 200))(i)

Assume i is an integer. You can rewrite the above expression as follows:

int result = (greater<int>())(i, 200)

The expression "bind2nd(greater<int>(), 200)" really is a more convenient way to implement the following:

// f is a binary function object that takes two integers, x and y.
// returns true if x > y
greater<int> f ;
// b is a unary function object; it returns true if its argument is > 200.
binder2nd< greater<int> > b = bind2nd(f, 200) ;
// Store the results of the comparison i > 200.
int result = b(i) ; // equivalent to the expression f(i, 200) ;

So now it is easier to understand: f is a binary function object that compares two integers, x and y; b is a unary function object that sets the second argument of f to the value 200; and result is therefore (i > 200).

Then why use an expression as complex as "bind2nd(greater<int>(), 200)"? Consider the following example:

int a1[1000] ;
//code to initialize a1 …
int* where = find_if(a1, a1+1000, bind2nd(greater<int>(), 200));

The find_if function finds the first element in array a1, which is > 200. In this case it is not possible to use the expression "(greater<int>())(i, 200)" as the third argument to find_if. How does one determine the value of i? The find_if function will supply the value i to the function object greater and the unary function object created by bind2nd. Also, bind2nd binds the value 200 to the second argument of function object greater.

This notation makes it possible to work with the entire container at once, instead of writing loops to deal with individual elements. It makes the source code smaller, more reliable, and easier to read.

bind1st converts the binary function object f into a unary function object by binding the argument x to the first argument of function object f.

bind2nd converts the binary function object f into a unary function object by binding the argument x to the second argument of function object f.

10.4.2 Negators

A negator returns the complement of a result obtained by applying a provided unary or binary operation.

STL provides two types of negator function objects: unary_negate<Operation> and binary_negate<Operation>. A negator function object takes only a single argument.

The two template functions, not1 and not2, create negator function objects. The function not1 takes a unary function object f as its argument and returns a function object of type unary_negate<Operation>. The function not2 takes a binary function object f as its argument and returns a function object of type binary_negate<Operation>. Here are the function prototypes for the not1 and not2 functions:

template <class Operation>
unary_negate<Operation> not1(const Operation& f) ;
template <class Operation>
binary_negate<Operation> not2(const Operation& f) ;

Let us try to understand what negators do by looking at the following example:

int a1[1000] ;
//code to initialize a1 …
int* where = find_if(a1, a1+1000, not1(bind2nd(greater<int>(), 200)));

In this case, find_if finds the first element in array a1 that is not > 200 (that is, <= 200). The function bind2nd creates a unary function object which returns the result of the comparison i > 200. The function not1 takes the unary function object as an argument and creates another function object. This function object merely negates the results of the comparison i > 200. Here is an example of using the not2 function:

sort(v1.begin(), v1.end(), not2(greater<int>())) ;

In this case, sort will sort elements in ascending order. The not2 function negates the results of the function object greater.

10.4.3 Pointer-to-function adapters

Adapters for pointers to functions convert existing binary or unary functions to function objects. Adapters for pointers to functions allow the programmer to utilize the existing code to extend the library, apply idioms unique to your application, and so forth.

STL provides two types of pointer-to-function objects: pointer_to_unary_function<Arg, Result> and pointer_to_binary_function<Arg1, Arg2, Result>. The pointer_to_unary_function function object takes one argument of type Arg, and pointer_to_binary_function takes two arguments of type Arg1 and Arg2.

STL provides two versions of the template function ptr_fun to create pointer-to-function function objects. The first version of ptr_fun takes a unary function f as its argument and returns a function object of type pointer_to_unary_function<Arg, Result>. The second version of ptr_fun takes a binary function f as its argument and returns a function object of type pointer_to_binary_function<Arg1, Arg2, Result>. Here are the function prototypes for the ptr_fun functions:

template<class Arg, class Result> inline
pointer_to_unary_function<Arg, Result> ptr_fun(Result (*F)(Arg))
      
template<class Arg1, class Arg2, class Result> inline
pointer_to_binary_function<Arg1, Arg2, Result> 
ptr_fun(Result(*F)(Arg1, Arg2)) ;

Let us look at an example and try to understand what pointers to functions do:

#pragma warning(disable: 4786)
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
// Return an integral random number in the range [0, n).
int Rand(int n)
{
    return rand() % n ;
}
void main()
{
    const int VECTOR_SIZE = 8 ;
    // A template class vector of int
    typedef vector<int, allocator<int> > IntVector ;
    //Define an iterator for template class vector of strings
    typedef IntVector::iterator IntVectorIt ;
    IntVector Numbers(VECTOR_SIZE) ;
    IntVectorIt start, end, it ;
    // Initialize vector Numbers
    Numbers[0] = 4 ;
    Numbers[1] = 10;
    Numbers[2] = 70 ;
    Numbers[3] = 30 ;
    Numbers[4] = 10;
    Numbers[5] = 69 ;
    Numbers[6] = 96 ;
    Numbers[7] = 100;
    start = Numbers.begin() ;   // location of first
                                // element of Numbers
    end = Numbers.end() ;       // one past the location
                                // last element of Numbers
    cout << "Before calling random_shuffle\n" << endl ;

    // Print content of Numbers.
    cout << "Numbers { " ;
    for(it = start; it != end; it++)
        cout << *it << " " ;
    cout << " }\n" << endl ;
   
    // Shuffle the elements in a random order
    // the ptr_fun adaptor converts
    // a function to a function object.
    random_shuffle(start, end, ptr_fun<int, int>(Rand)) ;
    cout << "After calling random_shuffle\n" << endl ;
    cout << "Numbers { " ;
    for(it = start; it != end; it++)
        cout << *it << " " ;
    cout << " }\n" << endl ;   
}

In this example, function ptr_fun converts the C++ unary function Rand into a pointer_to_binary_function function object. Build the above sample and examine the output. We recommend stepping through the code in the debugger to see what is happening under the covers!!