11.8. operator(): An Introduction To Functors

Time: 00:04:51 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides: PDF, PPTX
Review

C++ uses parentheses to implement three critical features: grouping expressions, casting data from one type to another, and calling functions. The Conversion Operations section presented earlier in the chapter demonstrated how C++ programmers override the casting operator to create new type conversions. This section demonstrates how programmers can overload the function operator. An instance of a class that overloads the function operator is called a function object or a functor. The C++ syntax for invoking a functor is indistinguishable from that of a "normal" function call.

class Foo
{
    public:
        Foo(parameters)
        return-type operator()(parameter-list) { ... }	// (a)
};
Foo functor(parameters);		// (b)

return-type result =  functor(args);	// (c)
Functor class pseudo code. Like general classes, programmers name functor classes and the objects programs instantiate from them appropriately for the roles they play in programs. They can define any necessary member variables or functions as needed. The feature distinguishing functor classes from general classes is that they overload the function operator: operator().
  1. The overloaded function operator making Foo a functor class. The red parentheses denote the overloaded operator, while the black ones surround the parameters of the implementing function.
  2. Instantiating a function object or functor.
  3. Programs call functors using a function call syntax

Common Functor Uses

Like all overloaded operators, functors, and the function operator only represent one possible problem solution. Programmers can replace functors with "normal" or classic functions or function pointers. However, functors offer a convenient, compact, and familiar syntax. Functors accrue all class benefits, making them superior to many of their potential replacements. Specifically, they can have functions beyond the overloaded function operator, including constructors, other members (such as helpers), and friends. Maintaining data as private member variables allows functors to "remember" it between calls and share it with other class functions, while limiting its scope and restricting access to it. The following examples demonstrate some of the ways programmers use functors.

Operations Maintaining State

In this context, state refers to the values currently stored in a functor's member variables and retained between function calls. C++ programmers can achieve similar results using the static keyword albeit with less flexibility

class NPL
{
    private:
        long    ix;
        long    iy;
        long    iz;
    
    public:
        NPL(int seed1, int seed2, int seed3) :
                ix(seed1), iy(seed2), iz(seed3) {}

        double  operator()();
        long    operator()(long max);
};
double NPL::operator()()
{
    ix = 171 * ix % 30269L;
    iy = 172 * iy % 30307L;
    iz = 170 * iz % 30323L;

    return fmod(ix / 30269.0 + iy / 30307.0 + iz / 30323.0, 1);
}

long NPL::operator()(long max)
{
    return (long)fmod(max * operator()(), max);
    //return (long)fmod(max * (*this)(), max);
}
(a)(b)
#include <iostream>
using namespace std;

int main()
{
    NPL	npl(41, 67, 91);

    for (int i = 100; i < 110; i++)
        cout < npl() << "  " << npl(i) << endl;

    return 0;
}
(c)
The NPL pseudo-random number generator. I first saw a FORTRAN implementation of the the Wichmann-Hill generator, in the report A Pseudo-Random Number Generator by B. A. Wichmann and I. D. Hill (June 1982, and reprinted with corrections May 1984) published by Britain's National Physical Laboratory (NPL). I've ported it to numerous languages over the years, calling each version some variation of "NPL." Although no longer considered a "good" random number generator, the example demonstrates the three ways C++ uses parentheses.
  1. The NPL class maintains three members, initialized by the constructor, used to calculate the next pseudo-random number in the sequence. The class defines two versions of the function operator. The first, without parameters, returns a real pseudo-random number; the second, with one parameter, returns an integer ≤ max. The red parentheses represent the overloaded operator, while the black ones represent the functions' parameter lists.
  2. The overloaded function operators demonstrate the three operations implemented with parentheses: yellow are casting operations; the green group expressions, altering their execution order; and together, the red and black are the function operator - the class overloads the red, while the black are the original. The example illustrates two versions demonstrating different ways of invoking the function operator. The first version uses the function's name: operator(), while second uses the functor notation to call the parameterless operator. The this pointer references or points to the functor, but the overloaded function operator requires an object rather than a pointer. The asterisk dereferences this, but the dereference expression must be surrounded by grouping parentheses because the dereference operator has a lower precedence than the function call. This syntax may not seem "familiar" as claimed above, but it is when viewed in the application context, (c).
  3. Demonstrates an application instantiating a functor (pink) and invoking the function operator (blue). npl() returns a pseudo-random real number, while npl(i) returns a pseudo-random integer ≤ i.
See also List of random number generators and Random number generator.

Functors: Replacing Function Pointers

Some programmers may find the general syntax defining function pointers intimidating and off putting. They can replace function pointers with function objects, also known as functors. Although the idea of functors may be unfamiliar, their implementation only requires object-oriented concepts that should look familiar once explained. Understanding functors and their syntax is less challenging than understanding the problems and programs using them. The following example demonstrates a specialized functor called a comparator used in conjunction with the C++ min library function. Given two objects, the min function determines which is the "smallest," comparing them with an application-defined comparator, which operates like a synchronous callback function. The example attempts to balance simplicity with authenticity by omitting some of the generalizing features of the min function, revisiting them later in the Templates chapter.

#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm> // for the sort function
using namespace std;

enum { OFFICER, MANAGER, ENGINEER };

class Employee
{
    private:
        string name;
        int    position;
        int    id;

    public:
        Employee(string n, int pos, int i) :
            name(n), position(pos), id(i) {}
        string getName() const { return name; }
        int getPosition() const { return position; }
        int getID() const { return id; }
        friend ostream& operator<<(ostream& out,
            const Employee& me)
        {
            out << left << setw(10) << me.name << setw(3)
                << me.position << setw(5) << me.id;
            return out;
         }
};
int main()
{
    Employee e1("Dilbert", ENGINEER, 400);
    Employee e2("Alice", ENGINEER, 100);
    Employee e3("Wally", ENGINEER, 200);
    Employee e4("Asok", ENGINEER, 700);
    Employee e5("PHB", MANAGER, 600);
    Employee e6("Richard", MANAGER, 500);
    Employee e7("Catbert", OFFICER, 300);

    cout << min(e1, e2, compByNumber()) << endl;
    cout << min(e2, e5, compByNumber()) << endl;
    cout << min(e5, e5, compByNumber()) << endl;
    cout << min(e5, e7, compByNumber()) << endl;

    cout << endl;

    cout << min(e1, e2, compByName()) << endl;
    cout << min(e2, e5, compByName()) << endl;
    cout << min(e5, e5, compByName()) << endl;
    cout << min(e5, e7, compByName()) << endl;

    return 0;
}
(a)(b)
class compByName
{
    public:
        bool operator()(const Employee& e1,
            const Employee& e2) const
        {
            return e1.getName() <= e2.getName();
        }
};
class compByNumber
{
    public:
        bool operator()(const Employee& e1,
            const Employee& e2) const
        {
            if (e1.getPosition() == e2.getPosition())
                return e1.getID() <= e2.getID();
            return e1.getPosition() <= e2.getPosition();
        }
};
(c)(d)
Implementing a comparator with a functor. The demonstration problem is ordering a pair of Employee objects (i.e., determine which is first and which is second). The program solves the problem using the C++ min library function. However, the Employee class has three data members: name, position in the company, and an id number. Which member or members determine the relative order of two objects? Comparators are application-defined functors that extract and compare specific data from objects. Applications can define multiple comparators to achieve different orderings at different times.
  1. The Employee class is unremarkable, notable only by including the algorithm header file, which has the min function prototypes. The min functions require the comparators to return a const value, causing the program to use it throughout.
  2. Comparators are classes with an overloaded operator(). Using them, highlighted in blue and pink, is straightforward. The first two arguments are Employee objects, and the third is the comparator.
  3. A comparator ordering Employee objects alphabetically by name.
  4. A comparator ordering Employee objects by their position and ID number: officers before managers, and managers before engineers; otherwise, they are ordered by their ID.
Please see <algorithm> for an example of the min function generalized with templates. The textbook revisits these comparators later in the templates chapter, using them to sort data saved in containers.

Downloadable Code

ViewDownloadComments
npl.h npl.h The NPL pseudo-random number generator class specification
npl.cpp npl.cpp The NPL pseudo-random number generator member functions
driver.cpp driver.cpp A simple application demonstrating the NPL PRNG
Employee.cpp Employee.cpp The complete Employee and comparator example