Chapter 9. The function Class Template

Form ever follows function.

— “The Tall Office Building
Artistically Considered,”
from Lippincott’s Magazine
LOUIS HENRI SULLIVAN

One of the drawbacks of template programming is the proliferation of template instantiations. If you write a function template that takes a callable type as one of its type arguments and makes a function call through an object of that type, the code that is generated for an instantiation with a pointer to function is distinct from the code that is generated for an instantiation with a class type T1 that has a function call operator, and that code is distinct from the code that is generated for an instantiation with a different class type T2 that also has a function call operator. Further, additional template code that uses this template will also splinter into multiple instantiations, as will template code that uses this additional template code, and so on.[1]

Old-fashioned C++ designers recognize this as the classic case for the use of polymorphism.[2] By factoring the code into a common part and several specialized parts, one for each distinct callable type, and defining a uniform interface to these specialized parts, supported by an abstract class, we can give up a bit of speed in exchange for reduced code size.[3] All these details can be hidden by wrapping them in a template whose type argument provides the argument types and return type for the resulting template instance’s function call operator. Once this is done, a template function that calls through that specialization needs to be instantiated only once, regardless of how many callable types that wrapper is used with, because there is only one template specialization for the wrapper.

The argument types and return type are described by a function type, which is a return type followed by a left parenthesis followed by an argument list, which can be empty, followed by a right parenthesis. For example, the function type of the standard function float cosf(float) is float(float); the function type of the standard function int rand() is int().

The class template function creates polymorphic function objects. The argument to the template must be a function type that describes the argument types and return type of objects of the template type. These objects can hold any target object that can be called with those argument types and returns a type that can be converted to the object’s return type. For example, the type function<float(float)> can hold the function pointer cosf as its target object. The target object can be reassigned at runtime.

template <class Fty> // Fty of type Ret(T1, T2, …, TN)
class function {
  : public unary_function<T1, Ret>      // see Section 9.5
  : public binary_function<T1, T2, Ret> // see Section 9.5
public :
  typedef Ret result_type;
  function();
  function(null_ptr_type npc);
  function(const function & right);
  template<class Fty2>
    function(Fty2 right);
  template<class Fty2>
    function(reference_wrapper<Fty2> fnref);

  function& operator=(const function& right);
  function& operator=(null_ptr_type npc);
  template<class Fty2>
    function& operator=(Fty2 right);
  template<class Fty2>
    function& operator=(reference_wrapper<Fty2> fnref);
  void swap(function& right);

  operator boolean-type()  const;

  result_type operator()(T1, T2, …, TN) const;
  const type_info & target_type() const;
  template<class Fty2> Fty2 *target();
  template<class Fty2> const Fty2 *target() const;

private :
  template<class Fty2>

    bool operator==(const Fty2&) const;
  template<class   Fty2>
    bool operator!=(const Fty2&) const;
  };

class bad_function_call  :  public std::exception {
};

An empty function object does not hold a callable object or a reference to a callable object. The class bad_function_call describes an exception thrown to indicate that a call to operator() on a function object failed because the object was empty.

Some member functions take an operand that names a callable object. You can specify such an operand in several ways:

fn:A callable object. After the call, the function object holds a copy of fn.

fnref:A reference_wrapper object holding a reference to a callable object. After the call, the function object holds a reference to fnref.get().

right:Another function object. After the call, the function object holds the same callable object as right.

npc:A null pointer constant. After the call, the function object is empty. The member function takes an implementation-specific argument type that ensures that it cannot be called with a pointer that is not null or with an integral constant.

The member operators operator== and operator!= are private and not defined, so they cannot be called. But see Section 9.4 for comparisons with null pointers.

9.1. Constructing a function Object

function::function();
function::function(null_ptr_type  npc);
function::function(const function& right);
template <class Fty2>
  function::function(Fty2 fn);
template <class Fty2>
  function::function(reference_wrapper <Fty2> fnref);

The first two constructors construct an empty function object. The other constructors construct a function object that holds the callable object passed as the operand.

Example 9.1. Constructing function Objects (funobjfun/construct.cpp)


#include <functional>
#include <iostream>
using std::tr1::function;
using std::cout;

int func()
  { // simple function
  return   0;
  }

struct S
  { // simple function object
  int operator()() const
    {
    return 1;
    }
  typedef int result_type;
  } obj;

void report (const char *title, bool val)
  { // report state of function object
  cout << title << ": object is";
  if (val)
    cout << "not";
  cout << "empty ";
  }

int main()
  { // demonstrate construction of function<> types
  function <int()> f0;
  report("after default construction", f0);
  function <int()> f1(0);
  report("after construction from 0", f1);
  function <int()> f2(f1);
  report("after construction from f1", f2);
  function <int()> f3(func);
  report("after construction from func", f3);
  function <int()> f4(obj);
  report("after construction from obj", f4);

  return 0;
  }


9.2. Access

function::operator boolean-type() const;

The member operator returns an object of an unspecified type that is convertible to bool; the converted value is false only if *this is empty.

Example 9.2. Member operator boolean-type (funobjfun/boolean.cpp)


#include <functional>
#include <iostream>
#include <math.h>
using std::tr1::function;
using std::cout;

void report (const char *title, bool val)
  { // report state of function object
  cout << title << ": object is ";
  if (val)
    cout << "not ";
  cout << "empty ";
  }

int main ()
  { // demonstrate conversion to boolean type
  function <float(float)> fn;           // construct empty object
  report("after construction", fn);
  fn = cosf;                            // assign target object
  report("after assigning target object", fn);
  fn = 0;                               // assign null pointer
  report("after assigning null pointer", fn);
  return 0;
  }


9.3. Modification

function& function::operator=(const function& right);
function& function::operator=(null_ptr_type   npc);
template<class Fty2>
  function& function::operator=(Fty2 right);
template<class Fty2>
  function& function::operator=(
    reference_wrapper <Fty2> fnref);

The operators each replace the callable object, if any, held by *this with the callable object passed as the operand.

Example 9.3. Assigning to function Objects (funobjfun/assign.cpp)


#include <functional>
#include <iostream>
using std::tr1::function;
using std::cout;

int func()
  { // simple function
  return 0;
  }

struct S
  { // simple function object
  int operator()() const
    {
    return 1;
    }
  typedef int result_type;
  } obj;

void report(const char *title, bool val)
  { // report state of function object
  cout << title << ": object is ";
  if (val)
    cout << "not ";
  cout << "empty  ";
  }

int main()
  { // demonstrate assignment of function<> types
  function<int()> f0(func), f1;

  report("constructed from func", f0);
  f0 = f1;
  report("assigned empty function object", f0);
  f0 = func;
  report("assigned func", f0);
  f0 = 0;
  report("assigned 0", f0);
  f0 = obj;
  report("assigned obj", f0);
  return 0;
  }


void function::swap(function& right);

The member function swaps, in constant time, the callable objects between *this and right and throws no exceptions.

Example 9.4. function::swap (funobjfun/swap.cpp)


#include <functional>
#include <iostream>
using std::tr1::function;
using std::cout;

int func()
  { // simple function
  return 0;
  }

void report(const char *title, bool val)
  { // report state of function object
  cout << title << ": object is ";
  if (val)
    cout << "not ";
  cout << "empty ";
  }

int main()
  { // demonstrate swapping of function<> types
  function<int()> f0;
  function<int()> f1(func);

  report("f0 before swap", f0);
  report("f1 before swap", f1);
  f0.swap(f1);
  report("f0 after swap", f0);
  report("f1 after swap", f1);
  return 0;
  }


9.4. Comparisons

template<class Fty>
  bool operator==(const function<Fty>& func,
    null_ptr_type   npc);
template<class Fty>
  bool operator==(null_ptr_type   npc,
    const function<Fty>& func);

The functions return true only if the argument func is empty. The argument npc must be a null pointer constant.

Example 9.5. Null Pointer Constant (funobjfun/opequal.cpp)


#include <functional>
#include <iostream>
#include <math.h>
using std::tr1::function;
using std::cout;

void report(const char *title, bool val)
  { // report state of function object
  cout << title << ": object is ";
  if (!val)
    cout << "not ";
  cout << "empty ";
  }

int main()
  { // demonstrate comparison to null pointer constant
  function<float(float)> fn;         // construct empty object

  report("after construction", fn == 0);
  fn = cosf;                        // assign target object
  report("after assigning target object", 0 == fn);
  fn = 0;                           // assign null pointer
  report("after assigning null pointer", fn == 0);
  return 0;
  }


template<class Fty>
  bool operator!=(const function<Fty>&,
    null_ptr_type   npc);
template<class Fty>
  bool operator!=(null_ptr_type   npc,
    const function<Fty>&);

The functions return true only if the argument func is not empty. The argument npc must be a null pointer constant.

9.5. Nested Types

typedef Ret function::result_type;

The typedef is a synonym for the Ret type in the template’s function type argument.

The template specialization function<Fn> is derived from std::unary_-function<T1, Ret>—hence defining the nested type result_type as a synonym for Ret and the nested type argument_type as a synonym for T1—only if Fn is a function type that takes one argument.

The template specialization function<Fn> is derived from std::binary_-function<T1, T2, Ret>—hence defining the nested type result_type as a synonym for Ret, the nested type first_argument_type as a synonym for T1, and the nested type second_argument_type as a synonym for T2—only if Fn is a function type that takes two arguments.

9.6. Invocation

result_type function::operator()(
  T1 t1, T2 t2, …, TN tN) const;

The member function throws an exception object of type bad_function_-call if *this is empty; otherwise, it returns INVOKE_R (fn, t1, t2, …, tN, Ret), where fn is the callable object held by *this.

Example 9.6. Invocation (funobjfun/invoke.cpp)


#include <functional>
#include <iostream>
using std::tr1::function;
using std::cout;

int func()
  { // simple function
  cout << "called func ";
  return 0;
  }

struct S
  { // simple function object
  int operator()() const
    {
    cout << "called S::operator() ";
    return 1;
    }
  typedef int result_type;
  } obj;

int main()
  { // demonstrate construction of function<> types
  function<int ()> f0(func);
  f0();
  f0 = obj;
  f0();
  return 0;
  }


9.7. The Target Object

const type_info& function::target_type()  const;

The member function returns a reference to an object of type type_info that describes the type of the current target object.

Example 9.7. function::target_type (funobjfun/targettype.cpp)


#include <functional>
#include <iostream>
#include <typeinfo>
#include <math.h>
using std::tr1::function;
using std::cout; using std::type_info;

struct S
  { // simple callable type
  float operator()(float) { return 1.0f; }
  typedef float result_type;
  };

void show_type(const char *title, const type_info& info)
  { // show name of target type
  cout << title << ": " << info.name() << ' ';
  }

int main()
  { // demonstrate function::target_type
  function<float(float)> fn;
  show_type("empty", fn.target_type());
  fn = cosf;
  show_type("cosf", fn.target_type());
  fn = S();
  show_type("S", fn.target_type());
  return 0;
  }


template <class Fty2> Fty2 *function::target();
template <class Fty2> const Fty2 *function::target() const;

The member functions return a pointer to the target object if the target object is of type Fty2; otherwise, they return a null pointer.

Each of these member functions is a function template. Since they do not take any arguments, the compiler can’t deduce the template argument types, so you must give the argument type explicitly when you call these functions.

Example 9.8. function::target (funobjfun/target.cpp)


#include <functional>
#include <iostream>
#include <typeinfo>
#include <math.h>
using std::tr1::function;
using std::cout; using std::type_info;

typedef function<float(float)> Fty;
typedef float(*fptr)(float);

struct S
  { // simple callable type
  float operator()(float) { return 1.0f; }
  typedef float result_type;
  };

void show_pointer(const char *title, Fty& fty)
  { // check pointer type and value
  cout << title << ": ";
  if (fty.target<fptr>())
    cout << " target has type pointer to function ";
  else if (fty.target<S>())
    cout << " target has type S ";
  else
    cout << " target is empty or has unknown type ";
  }

int main()
  { // demonstrate function::target_type
  function<float(float)> fn;
  show_pointer("empty", fn);
  fn = cosf;
  show_pointer("cosf", fn);
  fn = S();

  show_pointer("S", fn);
  return 0;
  }


Exercises

Exercise 1

For each of the following errors, write a simple test case containing the error, and try to compile it. In the error messages, look for the key words that relate to the error in the code.

1. Attempting to construct a function object with a callable type that takes the wrong number of arguments

2. Attempting to construct a function<float(float)> object with a callable type that takes an argument of type void*

3. Attempting to compare two function objects for equality

4. Attempting to compare a function object to a pointer object

Exercise 2

Given an object fn of type function<double(double)>, which of the following objects can be the target object of fn? Write some code to verify your answers.

1. (double(*)(double))cos,[4] declared in <math.h>

2. sinf, declared in <math.h>

3. tanl, declared in <math.h>

4. ilogbl, declared in <math.h>

5. srand, declared in <stdlib.h>

6. Fty f, where Fty is defined as:

struct Fty
  {
  float operator()(double);
  typedef float result_type;
  };

Exercise 3

Write a class named match that stores an integer value that is set to zero by the constructor. Add a member function that changes the stored value to the value of its argument. Add a function call operator that takes an integer argument and returns a Boolean value that is true if the value of the argument is equal to the object’s stored value.

Write a program that creates an object of type std::vector<int> and stores several integer values in it.

1. Use the algorithm std::count_if and a match object to count the number of elements in the vector that are equal to 3.

2. Use the algorithm std::count_if and a match object to count the number of elements in the vector that are equal to 5.

3. Create a function object that can hold an object of type match as its target object. Initialize it with the match object from the previous part, and pass the function object to count_if.

4. Change the stored value of the match object to 7, store the modified match object in the function object, and repeat the search.

5. Store a reference to the match object in the function object, and repeat the search.

6. Change the stored value in the match object to 9, and repeat the search.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.222.132