Выбрать главу

Adaptable function objects

Standard function adapters such as bind1st( ) and bind2nd( ) make some assumptions about the function objects they process. To illustrate, consider the following expression from the last line of the earlier CountNotEqual.cpp program:.

not1(bind1st(equal_to<int>(), 20))

The bind1st( ) adapter creates a unary function object of type binder1st, which simply stores an instance of equal_to<int> and the value 20. The binder1st::operator( ) function needs to know its argument type and its return type; otherwise, it will not have a valid declaration. The convention to solve this problem is to expect all function objects to provide nested type definitions for these types. For unary functions, the type names are argument_type and result_type; for binary function objects they are first_argument_type, second_argument_type, and result_type. Looking at the implementation of bind1st( ) and binder1st in the <functional> header reveals these expectations. First inspect bind1st( ), as it might appear in a typical library implementation:.

template <class Op, class T>

binder1st<Op>

bind1st(const Op& f, const T& val)

{

  typedef typename Op::first_argument_type Arg1_t;

  return binder1st<Op>(f, Arg1_t(val));

}

Note that the template parameter, Op, which represents the type of the binary function being adapted by bind1st( ), must have a nested type named first_argument_type. (Note also the use of typename to inform the compiler that it is a member type name, as explained in Chapter 5.) Now notice how binder1st uses the type names in Op in its declaration of its function call operator:.

// Inside the implementation for binder1st<Op>…

typename Op::result_type

operator()(const typename Op::second_argument_type& x)

  const;

Function objects whose classes provide these type names are called adaptable function objects.

Since these names are expected of all standard function objects as well as of any function objects you create that you want to use with the function object adapters, the <functional> header provides two templates that define these types for you: unary_function and binary_function. You simply derive from these classes while filling in the argument types as template parameters. Suppose, for example, that we want to make the function object gt_n, defined earlier in this chapter, adaptable. All we need to do is the following:.

class gt_n : public unary_function<int, bool> {

  int value;

public:

  gt_n(int val) : value(val) {}

  bool operator()(int n) {

    return n > value;

  }

};.

All unary_function does is to provide the appropriate type definitions, which it infers from its template parameters as you can see in its definition:.

template <class Arg, class Result>

struct unary_function {

  typedef Arg argument_type;

  typedef Result result_type;

};

These types become accessible through gt_n because it derives publicly from unary_function. The binary_function template behaves in a similar manner.

More function object examples

The following FunctionObjects.cpp example provides simple tests for most of the built-in basic function object templates. This way, you can see how to use each template, along with their resulting behavior. This example uses one of the following generators for convenience:.

//: C06:Generators.h

// Different ways to fill sequences

#ifndef GENERATORS_H

#define GENERATORS_H

#include <set>

#include <cstdlib>

#include <cstring>

#include <ctime>

// Microsoft namespace work-around

#ifndef _MSC_VER

using std::rand;

using std::srand;

using std::time;

#endif

// A generator that can skip over numbers:

class SkipGen {

  int i;

  int skp;

public:

  SkipGen(int start = 0, int skip = 1)

    : i(start), skp(skip) {}

  int operator()() {

    int r = i;

    i += skp;

    return r;

  }

};

// Generate unique random numbers from 0 to mod:

class URandGen {

  std::set<int> used;

  int limit;

public:

  URandGen(int lim) : limit(lim) {

    srand(time(0));

  }

  int operator()() {

    while(true) {

      int i = int(rand()) % limit;

      if(used.find(i) == used.end()) {

        used.insert(i);

        return i;

      }

    }

  }

};

// Produces random characters:

class CharGen {

  static const char* source;

  static const int len;

public:

  CharGen() { srand(time(0)); }

  char operator()() {

    return source[rand() % len];

  }

};

// Statics created here for convenience, but

// will cause problems if multiply included:

const char* CharGen::source = "ABCDEFGHIJK"

  "LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";