I l@ve RuBoard Previous Section Next Section

5.5 Implementing the Forwarding Functor::operator()

Functor needs an operator() that forwards to FunctorImpl::operator(). We can use the same approach as with FunctorImpl itself: Provide a bunch of Functor partial specializations, one for each parameter count. However, this approach is not appropriate here. Functor defines a considerable amount of code, and it would be wasteful to duplicate it all only for the sake of operator().

But first, let's define the parameter types. Typelists are of great help here:



template <typename R, class TList>


class Functor


{


   typedef TList ParmList;


   typedef typename TypeAtNonStrict<TList, 0, EmptyType>::Result


      Parm1;


   typedef typename TypeAtNonStrict<TList, 1, EmptyType>::Result


      Parm2;


   ... as above ...


};


TypeAtNonStrict is a template that accesses the type at a given position in a typelist. If no type is found, the result (i.e., the inner class TypeAtNonStrict<...>::Result) evaluates to the third template argument of TypeAtNonStrict. We chose EmptyType as that third argument. EmptyType is, as its name hints, a class that holds nothing between its brackets. (Refer to Chapter 3 for details on TypeAtNonStrict and to Chapter 2 for a description of EmptyType.) In conclusion, ParmN will be either the Nth type in the typelist, or EmptyType if the typelist has fewer than N elements.

To implement operator(), let's rely on an interesting trick. We define all versions of operator()—for any number of parameters—inside Functor's definition, as follows.



template <typename R, class TList>


class Functor


{


   ... as above ...


public:


   R operator()()


   {


      return (*spImpl_)();


   }


   R operator()(Parm1 p1)


   {


      return (*spImpl_)(p1);


   }


   R operator()(Parm1 p1, Parm2 p2)


   {


      return (*spImpl_)(p1, p2);


   }


};


Where's the trick? For a given Functor instantiation, only one operator() is correct. All others constitute compile-time errors. You would expect Functor not to compile at all. This is because each FunctorImpl specialization defines only one operator(), not a bunch of them as Functor does. The trick relies on the fact that C++ does not instantiate member functions for templates until they are actually used. Until you call the wrong operator(), the compiler doesn't complain. If you try to call an overload of operator() that doesn't make sense, the compiler tries to generate the body of operator() and discovers the mismatch. Here's an example.



// Define a Functor that accepts an int and a double and


// returns a double.


Functor<double, TYPELIST_2(int, double)> myFunctor;


// Invoke it.


// operator()(double, int) is generated.


double result = myFunctor(4, 5.6);


// Wrong invocation.


double result = myFunctor(); // error!


// operator()() is invalid because


// FunctorImpl<double, TYPELIST_2(int, double)>


// does not define one.


Because of this neat trick, we don't have to specialize Functor partially for zero, one, two, and so forth parameters—specializations that would have led to much code duplication. We just define all versions and let the compiler generate only the ones that are used.

Now that the necessary scaffolding is in place, we're ready to start defining concrete classes derived from FunctorImpl.

    I l@ve RuBoard Previous Section Next Section