I l@ve RuBoard Previous Section Next Section

11.8 Double Dispatch to Functors

As described earlier, the trampoline trick works nicely with pointers to nonstatic functions. Anonymous namespaces provide a clean way to replace static functions with nonstatic functions that are not visible outside the current compilation unit.

Sometimes, however, you need your callback objects (the CallbackType template parameter of BasicDispatcher) to be more substantial than simple pointers to functions. For instance, you might want each callback to hold some state, and functions cannot hold much state (only static variables). Consequently, you need to register functors, and not functions, with the double dispatcher.

Functors (Chapter 5) are classes that overload the function call operator, operator(), thus imitating simple functions in call syntax. Additionally, functors can use member variables for storing and accessing state. Unfortunately, the trampoline trick cannot work with functors, precisely because functors hold state and simple functions do not. (Where would the trampoline hold the state?)

Client code can use BasicDispatcher directly, instantiated with the appropriate functor type.



struct HatchFunctor


{


   void operator()(Shape&, Shape&)


   {


     ...


   }


};





typedef BasicDispatcher<Shape, Shape, void, HatchFunctor>


   HatchingDispatcher;


HatchFunctor::operator() itself cannot be virtual, because BasicDispatcher needs a functor with value semantics, and value semantics don't mix nicely with runtime polymorphism. However, HatchFunctor::operator() can forward a call to a virtual function.

The real disadvantage is that the client loses some automation that the dispatcher could do—namely, taking care of the casts and providing symmetry.

It seems as if we're back to square one, but only if you haven't read Chapter 5 on generalized functors. Chapter 5 defines a Functor class template that can aggregate any kind of functor and pointers to functions, even other Functor objects. You can even define specialized Functor objects by deriving from the FunctorImpl class. We can define a Functor to take care of the casts. Once the casts are confined to the library, we can implement symmetry easily.

Let's define a FunctorDispatcher that dispatches to any Functor objects. This dispatcher will aggregate a BasicDispatcher that stores Functor objects.



template <class BaseLhs, class BaseRhs = BaseLhs,


   typename ResultType = void>


class FunctorDispatcher


{


   typedef Functor<ResultType,


         TYPELIST_2(BaseLhs&, BaseRhs&)>


      FunctorType;


   typedef BasicDispatcher<BaseLhs, BaseRhs, ResultType,


         FunctorType>


      BackEndType;


   BackEndType backEnd_;


      public:


         ...


      };


FunctorDispatcher uses a BasicDispatcher instantiation as its back end. BasicDispatcher stores objects of type FunctorType, which are Functors that accept two parameters (BaseLhs and BaseRhs) and return a ResultType.

The FunctorDispatcher::Add member function defines a specialized FunctorImpl class by deriving from it. The specialized class (Adapter, shown below) takes care of casting the arguments to the right types; in other words, it adapts the argument types from BaseLhs and BaseRhs to SomeLhs and SomeRhs.



template <class BaseLhs, class BaseRhs = BaseLhs,


   ResultType = void>


class FunctorDispatcher


{


   ... as above ...


   template <class SomeLhs, class SomeRhs, class Fun>


   void Add(const Fun& fun)


   {


      typedef


         FunctorImpl<ResultType, TYPELIST_2(BaseLhs&, BaseRhs&)>


         FunctorImplType;


      class Adapter : public FunctorImplType


      {


         Fun fun_;


         virtual ResultType operator()(BaseLhs& lhs, BaseRhs& rhs)


         {


            return fun_(


               dynamic_cast<SomeLhs&>(lhs),


               dynamic_cast<SomeRhs&>(rhs));


         }


         virtual FunctorImplType* Clone()const


         { return new Adapter; }


      public:


         Adapter(const Fun& fun) : fun_(fun) {}


      };


      backEnd_.Add<SomeLhs, SomeRhs>(


         FunctorType((FunctorImplType*)new Adapter(fun));


   }


};


The Adapter class does exactly what the trampoline function did. Because functors have state, Adapter aggregates a Fun object—something that was impossible with a simple trampoline function. The Clone member function, with obvious semantics, is required by Functor.

FunctorDispatcher::Add has remarkably broad uses. You can use it to register not only pointers to functions, but also almost any functor you want, even generalized functors. The only requirements for the Fun type in Add is that it accept the function-call operator with arguments of types SomeLhs and SomeRhs and that it return a type convertible to ResultType. The following example registers two different functors to a FunctorDispatcher object.



typedef FunctorDispatcher<Shape> Dispatcher;


struct HatchRectanglePoly


{


   void operator()(Rectangle& r, Poly& p)


   {


    ...


   }


};


struct HatchEllipseRectangle


{


   void operator()(Ellipse& e, Rectangle& r)


   {


      ...


   }


};


...


Dispatcher disp;


disp.Add<Rectangle, Poly>(HatchRectanglePoly());


disp.Add<Ellipse, Rectangle>(HatchEllipseRectangle());


The two functors don't have to be related in any way (as with inheriting from a common base). All they have to do is to implement operator() for the types that they advertise to handle.

Implementing symmetry with FunctorDispatcher is similar to implementing symmetry in FnDispatcher. FunctorDispatcher::Add defines a new ReverseAdapter object that does the casts and reverses the order of calls.

    I l@ve RuBoard Previous Section Next Section