22.5 Specifying Functors

Ru-Brd

Our previous example of the standard set class shows only one way to handle the selection of functors. A number of different approaches are discussed in this section.

22.5.1 Functors as Template Type Arguments

One way to pass a functor is to make its type a template argument. A type by itself is not a functor, however, so the client function or class must create a functor object with the given type. This, of course, is possible only for class type functors, and it rules out function pointer types. A function pointer type does not by itself specify any behavior. Along the same lines of thought, this is not an appropriate mechanism to pass a class type functor that encapsulates some state information (because no particular state is encapsulated by the type alone; a specific object of that type is needed).

Here is an outline of a function template that takes a functor class type as a sorting criterion:

 template <typename FO>  void my_sort (   )  {      FO cmp;  // create function object    if (cmp(x,y)) {  // use function object to compare two values    }   }  // call function with functor  my_sort<std::less<   > > (   ); 

With this approach, the selection of the comparison code has become a compile-time affair. And because the comparison can be "inlined," a good optimizing compiler should be able to produce code that is essentially equivalent to replacing the functor calls by direct applications of the resulting operations. To be entirely perfect, an optimizer must also be able to elide the storage used by the cmp functor object. In practice, however, only a few compilers are capable of such features.

22.5.2 Functors as Function Call Arguments

Another way to pass functors is to pass them as function call arguments. This allows the caller to construct the function object (possibly using a nontrivial constructor) at run time.

The efficiency argument is essentially similar to that of having just a functor type parameter, except that we must now copy a functor object as it is passed into the routine. This cost is usually low and can in fact be reduced to zero if the functor object has no data members (which is often the case). Indeed, consider this variation of our my_sort example:

 template <typename F>  void my_sort (   , F cmp)  {   if (cmp(x,y)) {  // use function object to compare two values    }   }  // call function with functor  my_sort (   , std::less<   >()); 

Within the my_sort() function, we are dealing with a copy cmp of the value passed in. When this value is an empty class object, there is no state to distinguish a locally constructed functor object from a copy passed in. Therefore, instead of actually passing the "empty functor" as a function call argument, the compiler could just use it for overload resolution and then elide the parameter/argument altogether. Inside the instantiated function, a dummy local object can then serve as the functor.

This almost works, except that the copy constructor of the "empty functor" must also be free of side effects. In practice this means that any functor with a user -defined copy constructor should not be optimized this way.

As written, the advantage of this functor specification technique is that it is also possible to pass an ordinary function pointer as argument. For example:

 bool my_criterion () (T const& x, T const& y);  // call function with function object  my_sort (   , my_criterion); 

Many programmers also prefer the function call syntax over the syntax involving a template type argument.

22.5.3 Combining Function Call Parameters and Template Type Parameters

It is possible to combine the two previous forms of passing functors to functions and classes by defining default function call arguments:

 template <typename F>  void my_sort (   , F cmp = F())  {   if (cmp(x,y)) {  // use function object to compare two values    }   }  bool my_criterion () (T const& x, T const& y);  // call function with functor passed as template argument  my_sort<std::less<   > > (   );  // call function with functor passed as value argument  my_sort (   , std::less<   >());  // call function with function pointer passed as value argument  my_sort (   , my_criterion); 

The ordered collection classes of the C++ standard library are defined in this way: The sorting criterion can be passed as a constructor argument at run time:

 class RuntimeCmp {   };  // pass sorting criterion as a compile-time template argument   // (uses default constructor of sorting criterion)  set<int,RuntimeCmp> c1;  // pass sorting criterion as a run-time constructor argument  set<int,RuntimeCmp> c2(RuntimeCmp(   )); 

For details, see pages 178 and 197 of [JosuttisStdLib].

22.5.4 Functors as Nontype Template Arguments

Functors can also be provided through nontype template arguments. However, as mentioned in Section 4.3 on page 40 and Section 8.3.3 on page 109, a class type functor (and, in general, a class type object) is never a valid nontype template argument. For example, the following is invalid:

 class MyCriterion {    public:      bool operator() (  SomeType  const&,  SomeType  const&) const;  };  template <MyCriterion F>  // ERROR:  MyCriterion  is a class type  void my_sort (   ); 

However, it is possible to have a pointer or reference to a class type object as a nontype argument. This might inspire us to write the following:

 class MyCriterion {    public:      virtual bool operator() (  SomeType  const&,  SomeType  const&) const = 0;  };  class LessThan : public MyCriterion {    public:      virtual bool operator() (  SomeType  const&,  SomeType  const&) const;  };  template<MyCriterion& F>  void sort (   );  LessThan order;  sort<order> (   );  // ERROR: requires derived-to-base   //        conversion  sort<(MyCriterion&)order> (   );  // ERROR: reference nontype argument   //        must be simple name   //        (without a cast)  

Our idea in the previous example is to capture the interface of the sorting criterion in an abstract base class type and use that type for the nontype template parameter. In an ideal world, we could then just plug in derived classes (such as LessThan ) to request a specific implementation of the base class interface ( MyCriterion ). Unfortunately, C++ does not permit such an approach: Nontype arguments with reference or pointer types must match the parameter type exactly. An implicit derived-to-base conversion is not considered , and making the conversion explicit also invalidates the argument.

In light of our previous examples, we conclude that class type functors are not conveniently passed as nontype template arguments. In contrast, pointers (and references) to functions can be valid nontype template arguments. The following section explores some of the possibilities offered by this concept.

22.5.5 Function Pointer Encapsulation

Suppose we have a framework that expects functors like the sorting criteria of the examples in the previous sections. Furthermore, we may have some functions from an older (nontemplate) library that we'd like to act as such a functor.

To solve this problem, we can simply wrap the function call. For example:

 class CriterionWrapper {    public:      bool operator() (   ) {          return  wrapped_function  (   );      }  }; 

Here, wrapped _ function () is a legacy function that we like to fit in our more general functor framework.

Often, the need to integrate legacy functions in a framework of class type functors is not an isolated event. Therefore, it can be convenient to define a template that concisely integrates such functions:

 template<int (*FP)()>  class FunctionReturningIntWrapper {    public:      int operator() () {          return FP();      }  }; 

Here is a complete example:

  // functors/funcwrap.cpp  #include <vector>  #include <iostream>  #include <cstdlib>  // wrapper for function pointers to function objects  template<int (*FP)()>  class FunctionReturningIntWrapper {    public:      int operator() () {          return FP();      }  };  // example function to wrap  int random_int()  {      return std::rand();  // call standard C function  }  // client that uses function object type as template parameter  template <typename FO>  void initialize (std::vector<int>& coll)  {      FO fo;  // create function object  for (std::vector<int>::size_type i=0; i<coll.size(); ++i) {          coll[i] = fo();  // call function for function object  }  }  int main()  {  // create vector with 10 elements  std::vector<int> v(10);  // (re)initialize values with wrapped function  initialize<FunctionReturningIntWrapper<random_int> >(v);  // output elements  for (std::vector<int>::size_type i=0; i<v.size(); ++i) {          std::cout << "coll[" << i << "]: " << v[i] << std::endl;      }  } 

The expression

 FunctionReturningIntWrapper<random_int> 

inside the call of initialize() wraps the function pointer random_int so that it can be passed as a template type parameter.

Note that we can't pass a function pointer with C linkage to this template. For example,

 initialize<FunctionReturningIntWrapper<std::rand> >(v); 

may not work because the std::rand() function comes from the C standard library (and may therefore have C linkage [6] ). Instead, we can introduce a typedef for a function pointer type with the appropriate linkage:

[6] In many implementations , functions from the C standard library have C linkage, but a C++ implementation is allowed to provide these functions with C++ linkage instead. Whether the example call is valid therefore depends on the particular implementation being used.

  // type for function pointer with C linkage  extern "C" typedef int (*C_int_FP)();  // wrapper for function pointers to function objects  template<C_int_FP FP>  class FunctionReturningIntWrapper {    public:      int operator() () {          return FP();      }  }; 

It may be worthwhile to reemphasize at this point that templates correspond to a compile-time mechanism. This means that the compiler knows which value is substituted for the nontype parameter FP of the template FunctionReturningIntWrapper . Because of this, most C++ implementations should be able to convert what at first may look like an indirect call to a direct call. Indeed, if the function were inline and its definition visible at the point of the functor invocation, it would be reasonable to expect the call to be inline.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net