I l@ve RuBoard Previous Section Next Section

5.10 Binding

We could stop here. By now, we have everything in place—Functor supports all the C++ callable entities defined in the beginning of the discussion, and in a nice manner. However, as Pygmalion might have remarked, sometimes the actual outcome of work is impossible to predict when you start doing it.

As soon as Functor is ready, new ideas come to mind. For instance, we'd like to be able to convert from a type of Functor to another. One such conversion is binding: Given a Functor that takes two integers, you want to bind one integer to some fixed value and let only the other one vary. Binding yields a Functor that takes only one integer, because the other one is fixed and therefore known, as illustrated in the following example.



void f()


{


   // Define a Functor of two arguments


   Functor<void, TYPELIST_2(int, int)> cmd1(something);


   // Bind the first argument to 10


   Functor<void, int> cmd2(BindFirst(cmd1, 10));


   // Same as cmd1(10, 20)


   cmd2(20);


   // Further bind the first (and only) argument


   // of cmd2 to 30


   Functor<void> cmd3(BindFirst(cmd2, 30));


   // Same as cmd1(10, 30)


   cmd3();


}


Binding is a powerful feature. You can store not only callable entities but also part (or all) of their arguments. This greatly increases the expressive power of Functor because it allows packaging of functions and arguments without requiring glue code.

For instance, think of implementing redo support in a text editor. When the user types an "a," you execute the member function Document::InsertChar('a'). Then you append a canned Functor that contains the pointer to Document, the member function InsertChar, and the actual character. When the user selects Redo, all you have to do is fire that Functor and you're finished. Section 5.14 provides further discussion of undo and redo.

Binding is powerful from another, more profound, standpoint. Think of a Functor as a computation, and of its arguments as the environment necessary to perform that computation. So far, Functor delays the computation by storing pointers to functions and pointers to methods. However, Functor stores only the computation and nothing about the environment of that computation. Binding allows Functor to store part of the environment together with the computation and to reduce progressively the environment necessary at invocation time.

Before jumping into the implementation, let's recap the requirement. For an instantiation Functor<R, TList>, we want to bind the first argument (TList::Head) to a fixed value. Therefore, the return type is Functor<R, TList::Tail>.

This being said, implementing the BinderFirst class template is a breeze. We have to pay special attention only to the fact that there are two instantiations of Functor involved: the incoming Functor and the outgoing Functor. The incoming Functor type is passed as the ParentFunctor parameter. The outgoing Functor type is computed.



template <class Incoming>


class BinderFirst


   : public FunctorImpl<typename Incoming::ResultType,


      typename Incoming::Arguments::Tail>


{


   typedef Functor<typename Incoming::ResultType,


      Incoming::Arguments::Tail> Outgoing;


   typedef typename Incoming::Parm1 Bound;


   typedef typename Incoming::ResultType ResultType;





public:


   BinderFirst(const Incoming& fun, Bound bound)


   : fun_(fun), bound_(bound)


   {


   }


 BinderFirst* Clone() const


 { return new BinderFirst(*this); }


   ResultType operator()()


   {


      return fun_(bound_);


   }


   ResultType operator()(typename Outgoing::Parm1 p1)


   {


      return fun_(bound_, p1);


   }


   ResultType operator()(typename Outgoing::Parm1 p1,


      typename Outgoing::Parm2 p2)


   {


      return fun_(bound_, p1, p2);


   }


private:


   Incoming fun_;


   Bound bound_;


};


The class template BinderFirst works in conjunction with the template function BindFirst. The merit of BindFirst is that it automatically deduces its template parameters from the types of the actual arguments that you pass it.



// See Functor.h for the definition of BinderFirstTraits


template <class Fctor>


typename Private::BinderFirstTraits<Fctor>::BoundFunctorType


BindFirst(


   const Fctor& fun,


   typename Fctor::Parm1 bound)


{


   typedef typename


      private::BinderFirstTraits<Fctor>::BoundFunctorType


         Outgoing;


   return Outgoing(std::auto_ptr<typename Outgoing::Impl>(


      new BinderFirst<Fctr>(fun, bound)));


}


Binding dovetails nicely with automatic conversion, conferring incredible flexibility on Functor. The following example combines binding with automatic conversions.



const char* Fun(int i, int j)


{


   cout << Fun(" << i << ", " << j << ") called\n";


   return "0";


}





int main()


{


   Functor<const char*, TYPELIST_2(char, int)> f1(Fun);


   Functor<std::string, TYPELIST_1(double)> f2(


      BindFirst(f1, 10));


   // Prints: Fun(10, 15) called


   f2(15);


}


    I l@ve RuBoard Previous Section Next Section