Functors

A functor is an object that can be called like a function. In other words, a functor is an object that overloads the function call operator: operator(). For example, we can define square and cube as functions or functor classes:

double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }

 

class Square
{
public:
   double operator()(double x) { return square(x); }
};

 

class Cube
{
public:
   double operator()(double x) { return cube(x); }
};

We can call functors or functions, it doesn't matter:

void testFunctors()
{
   Square f; // f is a functor
   Cube g; // g is a functor
   cout << f(3) << '\n';
   cout << g(3) << '\n';
   cout << f(g(2)) << '\n';

   cout << square(3) << '\n';
   cout << cube(3) << '\n';
   cout << square(cube(2)) << '\n';
}

Here's the output produced:

9
27
64
9
27
64

The standard C++ library even provides some functor classes in <functional>:

void testFunctors2()
{   // f, g, h, & k are functors
   multiplies<double> f;
   plus<string> h; // concatonation functor
   plus<double> g;
   less<int> k;
   cout << f(3, 4) << '\n';
   cout << g(3, 4) << '\n';
   cout << h("bat", "man") << '\n';
   cout << k(2, 9) << '\n';
}

Here's the output produced:

12
7
batman
1

Unlike functions, functors can provide additional services:

class Line // represents a line in the plane
{
public:
   Line(double s = 0, double yi = 0)
   {
      slope = s; yIntercept = yi;
   }
   double getSlope() const { return slope; }
   double get_yIntercept() const { return yIntercept; }
   double get_xIntercept() const { return –yIntercept/slope; }
   bool parallel(const Line& l) const { return l.slope == slope; }
   string equation() const
   {
      ostringstream os; // #include <sstream> needed
      os << "y = " << slope << "x + " << yIntercept;
      return os.str();
   }
   double operator()(double x)
   {
      return slope * x + yIntercept;
   }
private:
      double slope, yIntercept;
};

 

void testLine()
{
   Line a(3, 4), b(-2, -1);
   cout << a.equation() << '\n';
   cout << b.equation() << '\n';
   cout << a.get_xIntercept() << '\n';
   cout << a(2) << '\n';
   cout << a(1) << '\n';
   cout << b(2) << '\n';
}

 

y = 3x + 4
y = -2x + -1
-1.33333
10
7
–5

Binders (Currying)

A binary functor expects two arguments. For example, instances of Wsum compute weighted sums:

class Wsum: public binary_function<double, double, double>
{
public:
   Wsum(double a = 1, double b = 1) { u = a; v = b; }
   double operator() (double x, double y) const
   {
      return u * x + v * y;
   }
private:
   double u, v;
};

The base class is a standard library type that fixes standard names for the parameter and return types:

template <class Arg, class Arg2, class Res>
struct binary_function
{
   typedef Arg first_argument_type;
   typedef Arg2 second_argument_type;
   typedef Res result_type;
};

The standard library also provides a unary_function type.

Assume f is an instance of Wsum:

Wsum f(3, 2); // f(x, y) = 3 * x + 2 * y

A binder is a unary functor that is created a from binary functor by fixing one of the parameters. Assume we need the unary functors h(y) = f(3, y) or g(x) = f(x, 3). We can create them as binders from f:

binder1st<Wsum> h(f, 3); // h(y) = 3 * 3 + 2 * y

binder2nd<Wsum> g(f, 3); // g(x) = 3 * x + 2 * 3

We can call f and g with a single argument:

cout << h(4) << '\n'; // prints 3 * 3 + 2 * 4 = 17

cout << g(4) << '\n'; // prints 3 * 4 + 2 * 3 = 18

The binder classes make heavy use of the names defined in the binary_function class. Thus, we can only make binders out of binary functors instantiated from a binary functor class derived from the binary_function class.

Adapters

A member functor is a functor made from a member function using an adapter. We start by defining a class:

class Box // represents a 3D box
{
public:
   Box(double h = 2, double w = 3, double d = 4)
   {
      height = h; width = w; depth = d;
   }
   double volume() { return height * width * depth;}
   bool print()
   {
      cout << "height = " << height << '\n';
      cout << "width = " << width << '\n';
      cout << "depth = " << depth << '\n';
      return true;
   }
   bool willFit(const Box& b)
   {
      return b.height < height && b.width < width && b.depth < depth;
   }
private:
   double height, width, depth;
};

 

Now declare a few boxes:

Box b1(2, 3, 1), b2(5, 6, 8), b3;

The standard C++ library provides two pairs of member functor classes. The parameterless member functor type (mem_fun_t) and member functor reference type (mem_fun_ref_t) create functors that expect the implicit box parameter passed as explicitly as a pointer or reference, respectively. It is assumed there are no other parameters:

mem_fun_ref_t<double, Box> vol(&Box::volume);
cout << vol(b1) << '\n'; // prints 6
cout << vol(b2) << '\n'; // prints 240
cout << vol(b3) << '\n'; // prints 24

 

mem_fun_t<bool, Box> prnt(&Box::print); // can't return void!?!
prnt(&b1);
prnt(&b2);
prnt(&b3);

The unary member functor type (mem_fun1_t) and member functor reference type (mem_fun1_ref_t) create functors that expect the implicit box parameter passed as explicitly as a pointer or reference, respectivel, plus one other parameter:

mem_fun1_ref_t<bool, Box, const Box&> fits(&Box::willHold);
cout << fits(b1, b2) << '\n'; // prints 0
cout << fits(b2, b1) << '\n'; // prints 1

Function Adapters

A function functor is created from a pointer to a global function. Let's begin by defining a few global functions:

double square(double x) { return x * x; }
const double delta = 1e-10;
bool close(double x, double y)
{
   return abs(x - y) <= delta; // #include <cmath> needed for abs
}

A function name is like an array name. It's really a pointer; in this case a pointer to the compiled function's first binary instruction. Following an array name with [] automatically dereferences the array. Similarly, following a function name with () automatically dereferences the function. The C++ library provides two function functor types, one for unary function functors, the other for binary function functors:

pointer_to_unary_function<double, double> sq(square);
pointer_to_binary_function<double, double, bool> cls(close);

Here are a few calls to our functors:

cout << sq(3) << '\n'; // prints 9
cout << cls(3, 100) << '\n'; // prints 0

Unlike functions, function functors can be used with binders. First, let's introduce a shorter name for binary function functors:

typedef pointer_to_binary_function<double, double, bool> DxD2B;

Bind the second argument of the cls functor to 0 to get a test for smallness:

binder2nd<DxD2B> small(cls, 0);

Here's a sample call:

cout << small(1e-20) << '\n'; // prints 1

Callables

A callable is a functor or a function. We can define a universal template function that work with functions or functors:

template <class Callable>
double apply(Callable f, double x)
{
   return f(x);
}

Here's a unary functor class that converts between functions qand functors. It's pretty useless, but the syntax is trippy:

template <class Domain, class Range>
class UnaryFunctor: public unary_function<Domain, Range>
{
public:
   typedef Range (*D2R_FUN)(Domain);
   UnaryFunctor(D2R_FUN f) { fun = f; }
   operator D2R_FUN() { return fun; } // casting operator
   Range operator()(Domain x) { return fun(x); }
protected:
   D2R_FUN fun;
};

 

A combinator is a higher order function that takes a callable as input, and returns a new callable as output. For example, deriv and compose are combinators:

typedef (double)(*D2D)(double);

 

D2D deriv(D2D f) { ... } // retuns (f(x + h) – f(x))/h
D2D compose(D2D f, D2D g) { ... } // returns f(g(x))

 

cout << deriv(square)(3) << '\n'; // prints 6.00000001
cout << compose(square, add1)(3) << '\n'; // prints 16

Regrettably, there's one hard, cruel fact about C++ (actually, I just thought of several others as I was typing this): there are no dynamic functions. In other words, a function can only be called if it is compiled, and it can only be compiled if it was around at compile time. This applies to global and member functions. It even applies to functors. To implement combinators we would need something like LISP's lambda operator. We can add flexibility by providing parameters (both function and template), that radically alter the behavior of a function (for example, member functions can have different behaviors created by their implicit parameters) but we can't create code on the fly.