Just as the STL defines concepts for containers and iterators, it defines functor concepts:
• A generator is a functor that can be called with no arguments.
• A unary function is a functor that can be called with one argument.
• A binary function is a functor that can be called with two arguments.
For example, the functor supplied to for_each()
should be a unary function because it is applied to one container element at a time.
Of course, these concepts come with refinements:
• A unary function that returns a bool
value is a predicate.
• A binary function that returns a bool
value is a binary predicate.
Several STL functions require predicate or binary predicate arguments. For example, Listing 16.9 uses a version of sort()
that takes a binary predicate as its third argument:
bool WorseThan(const Review & r1, const Review & r2);
...
sort(books.begin(), books.end(), WorseThan);
The list
template has a remove_if()
member that takes a predicate as an argument. It applies the predicate to each member in the indicated range, removing those elements for which the predicate returns true
. For example, the following code would remove all elements greater than 100 from the list three
:
bool tooBig(int n){ return n > 100; }
list<int> scores;
...
scores.remove_if(tooBig);
Incidentally, this last example shows where a class functor might be useful. Suppose you want to remove every value greater than 200 from a second list. It would be nice if you could pass the cut-off value to tooBig()
as a second argument so you could use the function with different values, but a predicate can have but one argument. If, however, you design a TooBig
class, you can use class members instead of function arguments to convey additional information:
template<class T>
class TooBig
{
private:
T cutoff;
public:
TooBig(const T & t) : cutoff(t) {}
bool operator()(const T & v) { return v > cutoff; }
};
Here one value (v
) is passed as a function argument, and the second argument (cutoff
) is set by the class constructor. Given this definition, you can initialize different TooBig
objects to different cut-off values to be used in calls to remove_if()
. Listing 16.15 illustrates the technique.
// functor.cpp -- using a functor
#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>
template<class T> // functor class defines operator()()
class TooBig
{
private:
T cutoff;
public:
TooBig(const T & t) : cutoff(t) {}
bool operator()(const T & v) { return v > cutoff; }
};
void outint(int n) {std::cout << n << " ";}
int main()
{
using std::list;
using std::cout;
using std::endl;
TooBig<int> f100(100); // limit = 100
int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> yadayada(vals, vals + 10); // range constructor
list<int> etcetera(vals, vals + 10);
// C++11 can use the following instead
// list<int> yadayada = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
// list<int> etcetera {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
cout << "Original lists:
";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
yadayada.remove_if(f100); // use a named function object
etcetera.remove_if(TooBig<int>(200)); // construct a function object
cout <<"Trimmed lists:
";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
return 0;
}
One functor (f100
) is a declared object, and the second (TooBig<int>(200)
) is an anonymous object created by a constructor call. Here’s the output of the program in Listing 16.15:
Original lists:
50 100 90 180 60 210 415 88 188 201
50 100 90 180 60 210 415 88 188 201
Trimmed lists:
50 100 90 60 88
50 100 90 180 60 88 188
Suppose that you already have a template function with two arguments:
template <class T>
bool tooBig(const T & val, const T & lim)
{
return val > lim;
}
You can use a class to convert it to a one-argument function object:
template<class T>
class TooBig2
{
private:
T cutoff;
public:
TooBig2(const T & t) : cutoff(t) {}
bool operator()(const T & v) { return tooBig<T>(v, cutoff); }
};
That is, you can use the following:
TooBig2<int> tB100(100);
int x;
cin >> x;
if (tB100(x)) // same as if (tooBig(x,100))
...
So the call tB100(x)
is the same as tooBig(x,100)
, but the two-argument function is converted to a one-argument function object, with the second argument being used to construct the function object. In short, the class functor TooBig2
is a function adapter that adapts a function to meet a different interface.
As noted in the listing, C++11’s initializer-list feature simplifies initialization. You can replace
int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> yadayada(vals, vals + 10); // range constructor
list<int> etcetera(vals, vals + 10);
with this:
list<int> yadayada = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> etcetera {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
18.118.12.50