11.8. operator(): An Introduction To Functors

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 call operator. An instance of a class that overloads the function call 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 call operator: operator().
  1. The overloaded function call 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 call 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 call 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);
    //return (long)(floor(max * operator())) % 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() << endl;

    return 0;
}
(c)
The NPL pseudo-random number generator. While the NPL PRNG, more commonly called the Wichmann-Hill generator, is no longer considered a "good" random number generator, the functor implementation demonstrates many function class features. My first exposure to the algorithm was a FORTRAN implementation (evident in the variable names) 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."
  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 call 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 call operators demonstrate the three operations implemented with parentheses: yellow are casting operations; the green group expressions, altering their execution order; the red are the overloaded function call operator; and the black call functions and specify parameter lists. The example illustrates three ways of implementing the parameterized operator:
    • The first version uses the function name, operator(), to call the parameterless function. The C++ fmod library function calculates the floating-point remainder of its argument, a real number ≤ max, and is cast to an integer.
    • Similar to the first, this version uses the functor notation to call the parameterless operator. The this pointer references or points to the functor, but the overloaded function call 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).
    • The last version replaces fmod with the floor function and mod operator,  % .
  3. Demonstrates an application instantiating a functor (pink) and invoking the function call operator (blue).
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 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();
        }
};
class compByName
{
    public:
        bool operator()(const Employee& e1,
            const Employee& e2) const
        {
            return e1.getName() <= e2.getName();
        }
};
(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 by their position and id number: officers before managers, and managers before engineers; otherwise, they are ordered by their ID.
  4. A comparator ordering Employee alphabetically by name.
Please see <algorithm> for an example of the min function generalized with templates. The textbook revisits containers and comparators in the templates chapter.

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