22.8 Value Binders

Ru-Brd

Often, a functor with multiple parameters remains useful when one of the parameters is bound to a specific value. For example, a simple Min functor template such as

  // functors/min.hpp  template <typename T>  class Min {    public:      typedef T ReturnT;      typedef T Param1T;      typedef T Param2T;      enum { NumParams = 2 };      ReturnT operator() (Param1T a, Param2T b) {          return a<b ? b : a;      }  }; 

can be used to build a new Clamp functor that behaves like Min with one of its parameters bound to a certain constant. The constant could be specified as a template argument or as a run-time argument. For example, we can write the new functor as follows :

  // functors/clamp.hpp  template <typename T, T max_result>  class Clamp : private Min<T> {    public:      typedef T ReturnT;      typedef T Param1T;      enum { NumParams = 1 };      ReturnT operator() (Param1T a) {          return Min<T>::operator() (a, max_result);      }  }; 

As with composition, it is very convenient to have some template that automates the task of binding a functor parameter available, even though it doesn't take very much code to do so manually.

22.8.1 Selecting the Binding

A binder binds a particular parameter of a particular functor to a particular value. Each of these aspects can be selected at run time (using function call arguments) or at compile time (using template arguments).

For example, the following template selects everything statically (that is, at compile time):

 template<typename F, int P, int V>  class BindIntStatically;  //  F  is the functor type   //  P  is the parameter to bind   //  V  is the value to be bound  

Each of the three binding aspects (functor, bound parameter, and bound value) can instead be selected dynamically with various degrees of convenience.

Perhaps the least convenient is to make the selection of which parameter to bind dynamic. Presumably this would involve large switch statements that delegate the functor call to different calls to the underlying functor depending on a run-time value. This may, for example look as follows:

   switch (this->param_num) {    case 1:      return F::operator()(v, p1, p2);    case 2:      return F::operator()(p1, v, p2);    case 3:      return F::operator()(p1, p2, v);    default:      return F::operator()(p1, p2);  // or an error?  } 

Of the three binding aspects, this is probably the one that needs to become dynamic the least. In what follows, we therefore keep this as a template parameter so that it is a static selection.

To make the selection of the functor dynamic, it is sufficient to add a constructor that accepts a functor to our binder. Similarly, we can also pass the bound value to the constructor, but this requires us to provide storage in the binder to hold the bound value. The following two helper templates can be used to hold bound values at compile time and run time respectively:

  // functors/boundval.hpp  #include "typeop.hpp"  template <typename T>  class BoundVal {    private:      T value;    public:      typedef T ValueT;      BoundVal(T v) : value(v) {      }      typename TypeOp<T>::RefT get() {          return value;      }  };  template <typename T, T Val>  class StaticBoundVal {    public:      typedef T ValueT;      T get() {          return Val;      }  }; 

Again, we rely on the empty base class optimization (see Section 16.2 on page 289) to avoid unnecessary overhead if the functor or the bound value representation is stateless. The beginning of our Binder template design therefore looks as follows:

  // functors/binder1.hpp  template <typename FO, int P, typename V>  class Binder : private FO, private V {    public:  // constructors:  Binder(FO& f): FO(f) {}      Binder(FO& f, V& v): FO(f), V(v) {}      Binder(FO& f, V const& v): FO(f), V(v) {}      Binder(FO const& f): FO(f) {}      Binder(FO const& f, V& v): FO(f), V(v) {}      Binder(FO const& f, V const& v): FO(f), V(v) {}      template<class T>        Binder(FO& f, T& v): FO(f), V(BoundVal<T>(v)) {}      template<class T>        Binder(FO& f, T const& v): FO(f), V(BoundVal<T const>(v)) {}   }; 

Note that, in addition to constructors taking instances of our helper templates, we also provide constructor templates that automatically wrap a given bound value in a BoundVal object.

22.8.2 Bound Signature

Determining the Param N T types for the Binder template is harder than it was for the Composer template because we cannot just take over the types of the functor on which we build. Instead, because the parameter that is bound is no longer a parameter in the new functor, we must drop the corresponding Param N T and shift the subsequent types by one position.

To keep things modular, we can introduce a separate template that performs the selective shifting operation:

  // functors/binderparams.hpp  #include "ifthenelse.hpp"  template<typename F, int P>  class BinderParams {    public:  // there is one less parameter because one is bound:  enum { NumParams = F::NumParams-1 };  #define ComposeParamT(N)                                      \      typedef typename IfThenElse<(N<P), FunctorParam<F, N>,    \                                         FunctorParam<F, N+1>   \                                 >::ResultT::Type               \              Param##N##T      ComposeParamT(1);      ComposeParamT(2);      ComposeParamT(3);   #undef ComposeParamT  }; 

This can be used in the Binder template as follows:

  // functors/binder2.hpp  template <typename FO, int P, typename V>  class Binder : private FO, private V {    public:  // there is one less parameter because one is bound:  enum { NumParams = FO::NumParams-1 };  // the return type is straightforward:  typedef typename FO::ReturnT ReturnT;  // the parameter types:  typedef BinderParams<FO, P> Params;  #define ComposeParamT(N)                                          \          typedef typename                                          \                  ForwardParamT<typename Params::Param##N##T>::Type \              Param##N##T      ComposeParamT(1);      ComposeParamT(2);      ComposeParamT(3);   #undef ComposeParamT   }; 

As usual, we use the ForwardParamT template to avoid unnecessary copying of arguments.

22.8.3 Argument Selection

To complete the Binder template we are left with the problem of implementing the function call operator. As with Composer we are going to overload this operator for varying numbers of functor call arguments. However, the problem here is considerably harder than for composition because the argument to be passed to the underlying functor can be one of three different values:

  • The corresponding parameter of the bound functor

  • The bound value

  • The parameter of the bound functor that is one position to the left of the argument we must pass

Which of the three values we select depends on the value of P and the position of the argument we are selecting.

Our idea to achieve the desired result is to write a private inline member function that accepts (by reference) the three possible values but returns (still by reference) the one that is appropriate for that argument position. Because this member function depends on which argument we're selecting, we introduce it as a static member of a nested class template. This approach enables us to write a function call operator as follows (here shown for binding a four-parameter functor; others are similar):

  // functors/binder3.hpp  template <typename FO, int P, typename V>  class Binder : private FO, private V {    public:   ReturnT operator() (Param1T v1, Param2T v2, Param3T v3) {          return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),                                ArgSelect<2>::from(v1,v2,V::get()),                                ArgSelect<3>::from(v2,v3,V::get()),                                ArgSelect<4>::from(v3,v3,V::get()));      }   }; 

Note that for the first and last argument, only two argument values are possible: the first or last parameter of the operator, or the bound value. If A is the position of the argument in the call to the underlying functor ( 1 through 3 in the example), then the corresponding parameter is selected when A-P is less than zero, the bound value is selected when A-P is equal to zero, and a parameter to the left of the argument position is selected when A-P is strictly positive. This observation justifies the definition of a helper template that selects one of three types based on the sign of a nontype template argument:

  // functors/signselect.hpp  #include "ifthenelse.hpp"  template <int S, typename NegT, typename ZeroT, typename PosT>  struct SignSelectT {    typedef typename        IfThenElse<(S<0),                   NegT,                   typename IfThenElse<(S>0),                                       PosT,                                       ZeroT                                      >::ResultT                  >::ResultT        ResultT;  }; 

With this in place, we are ready to define the member class template ArgSelect :

  // functors/binder4.hpp  template <typename FO, int P, typename V>  class Binder : private FO, private V {   private:      template<int A>      class ArgSelect {        public:  // type if we haven't passed the bound argument yet:  typedef typename TypeOp<                      typename IfThenElse<(A<=Params::NumParams),                                          FunctorParam<Params, A>,                                          FunctorParam<Params, A-1>                                         >::ResultT::Type>::RefT                 NoSkipT;  // type if we're past the bound argument:  typedef typename TypeOp<                      typename IfThenElse<(A>1),                                          FunctorParam<Params, A-1>,                                          FunctorParam<Params, A>                                         >::ResultT::Type>::RefT                 SkipT;  // type of bound argument:  typedef typename TypeOp<typename V::ValueT>::RefT BindT;  // three selection cases implemented through different classes:  class NoSkip {            public:              static NoSkipT select (SkipT prev_arg, NoSkipT arg,                                     BindT bound_val) {                  return arg;              }          };          class Skip {            public:              static SkipT select (SkipT prev_arg, NoSkipT arg,                                   BindT bound_val) {                  return prev_arg;              }          };          class Bind {            public:              static BindT select (SkipT prev_arg, NoSkipT arg,                                   BindT bound_val) {                  return bound_val;              }          };  // the actual selection function:  typedef typename SignSelectT<A-P, NoSkipT,                                       BindT, SkipT>::ResultT                  ReturnT;          typedef typename SignSelectT<A-P, NoSkip,                                       Bind, Skip>::ResultT                  SelectedT;          static ReturnT from (SkipT prev_arg, NoSkipT arg,                               BindT bound_val) {              return SelectedT::select (prev_arg, arg, bound_val);          }      };  }; 

This is admittedly among the most complicated code segments in this book. The from member function is the one called from the functor call operators. Part of the difficulty lies in the selection of the right parameter types from which the argument is selected: SkipT and NoSkipT also incorporate the convention we use for the first and last argument (that is, repeating v1 and v4 in the operator illustrated earlier). We use the TypeOp<>::RefT construct to define these types: We could just create a reference type using the & symbol, but most compilers cannot handle "references to references" yet. The selection functions themselves are rather trivial, but they were encapsulated in member types NoSkip , Skip , and Bind to dispatch statically the appropriate function easily. Because these functions are themselves simple inline forwarding functions, a good optimizing compiler should be able to "see through" it all and generate near-optimimal code. In practice, only the best optimizers available at the time of this writing perform entirely satifactorily in the performance area. However, most other compilers still do a reasonable job of optimizing uses of Binder .

Putting it all together, our complete Binder template is implemented as follows:

  // functors/binder5.hpp  #include "ifthenelse.hpp"  #include "boundval.hpp"  #include "forwardparam.hpp"  #include "functorparam.hpp"  #include "binderparams.hpp"  #include "signselect.hpp"  template <typename FO, int P, typename V>  class Binder : private FO, private V {    public:  // there is one less parameter because one is bound:  enum { NumParams = FO::NumParams-1 };  // the return type is straightforward:  typedef typename FO::ReturnT ReturnT;  // the parameter types:  typedef BinderParams<FO, P> Params;  #define ComposeParamT(N)                                          \          typedef typename                                          \                  ForwardParamT<typename Params::Param##N##T>::Type \              Param##N##T      ComposeParamT(1);      ComposeParamT(2);      ComposeParamT(3);   #undef ComposeParamT  // constructors:  Binder(FO& f): FO(f) {}      Binder(FO& f, V& v): FO(f), V(v) {}      Binder(FO& f, V const& v): FO(f), V(v) {}      Binder(FO const& f): FO(f) {}      Binder(FO const& f, V& v): FO(f), V(v) {}      Binder(FO const& f, V const& v): FO(f), V(v) {}      template<class T>        Binder(FO& f, T& v): FO(f), V(BoundVal<T>(v)) {}      template<class T>        Binder(FO& f, T const& v): FO(f), V(BoundVal<T const>(v)) {}  // ''function calls'':  ReturnT operator() () {          return FO::operator()(V::get());      }      ReturnT operator() (Param1T v1) {          return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),                                ArgSelect<2>::from(v1,v1,V::get()));      }      ReturnT operator() (Param1T v1, Param2T v2) {          return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),                                ArgSelect<2>::from(v1,v2,V::get()),                                ArgSelect<3>::from(v2,v2,V::get()));      }      ReturnT operator() (Param1T v1, Param2T v2, Param3T v3) {          return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),                                ArgSelect<2>::from(v1,v2,V::get()),                                ArgSelect<3>::from(v2,v3,V::get()),                                ArgSelect<4>::from(v3,v3,V::get()));      }   private:      template<int A>      class ArgSelect {        public:  // type if we haven't passed the bound argument yet:  typedef typename TypeOp<                      typename IfThenElse<(A<=Params::NumParams),                                          FunctorParam<Params, A>,                                          FunctorParam<Params, A-1>                                         >::ResultT::Type>::RefT                 NoSkipT;  // type if we're past the bound argument:  typedef typename TypeOp<                      typename IfThenElse<(A>1),                                          FunctorParam<Params, A-1>,                                          FunctorParam<Params, A>                                         >::ResultT::Type>::RefT                 SkipT;  // type of bound argument:  typedef typename TypeOp<typename V::ValueT>::RefT BindT;  // three selection cases implemented through different classes:  class NoSkip {            public:              static NoSkipT select (SkipT prev_arg, NoSkipT arg,                                     BindT bound_val) {                  return arg;              }          };          class Skip {            public:              static SkipT select (SkipT prev_arg, NoSkipT arg,                                   BindT bound_val) {                  return prev_arg;              }          };          class Bind {            public:              static BindT select (SkipT prev_arg, NoSkipT arg,                                   BindT bound_val) {                  return bound_val;              }          };  // the actual selection function:  typedef typename SignSelectT<A-P, NoSkipT,                                       BindT, SkipT>::ResultT                  ReturnT;          typedef typename SignSelectT<A-P, NoSkip,                                       Bind, Skip>::ResultT                  SelectedT;          static ReturnT from (SkipT prev_arg, NoSkipT arg,                               BindT bound_val) {              return SelectedT::select (prev_arg, arg, bound_val);          }      };  }; 

22.8.4 Convenience Functions

As with the composition templates, it is useful to write function templates that make it easier to express the binding of a value to a functor parameter. The definition of such a template is made a little harder by the need to express the type of the bound value:

  // functors/bindconv.hpp  #include "forwardparam.hpp"  #include "functorparam.hpp"  template <int P,  // position of the bound parameter  typename FO>  // functor whose parameter is bound  inline  Binder<FO,P,BoundVal<typename FunctorParam<FO,P>::Type> >  bind (FO const& fo,        typename ForwardParamT                   <typename FunctorParam<FO,P>::Type>::Type val)  {      return Binder<FO,                    P,                    BoundVal<typename FunctorParam<FO,P>::Type>                   >(fo,                     BoundVal<typename FunctorParam<FO,P>::Type>(val));  } 

The first template parameter is not deducible: It must be specified explicitly when using the bind() template. The following example illustrates this:

  // functors/bindtest.cpp  #include <string>  #include <iostream>  #include "funcptr.hpp"  #include "binder5.hpp"  #include "bindconv.hpp"  bool func (std::string const& str, double d, float f)  {      std::cout << str << ": "                << d << (d<f? "<": ">=")                << f << '\n';      return d<f;  }  int main()  {      bool result = bind<1>(func_ptr(func), "Comparing")(1.0, 2.0);      std::cout << "bound function returned " << result << '\n';  } 

It may be tempting to simplify the bind template by adding a deducible template parameter for the bound value, thereby avoiding the cumbersome expression of the type, as done here. However, this often leads to difficulties in situations like this example, in which a literal of type double ( 2.0 ) is passed to a parameter of a compatible but different type float .

It is also often convenient to be able to bind a function (passed as a function pointer) directly. The definitions of the resulting bindfp() templates is only slightly more complicated than that of the bind template. Here is the code for the case of a function with two parameters:

  // functors/bindfp2.hpp   // convenience function to bind a function pointer with two parameters  template<int PNum, typename RT, typename P1, typename P2>  inline  Binder<FunctionPtr<RT,P1,P2>,         PNum,         BoundVal<typename FunctorParam<FunctionPtr<RT,P1,P2>,                                        PNum                                       >::Type                 >        >  bindfp (RT (*fp)(P1,P2),          typename ForwardParamT                     <typename FunctorParam<FunctionPtr<RT,P1,P2>,                                            PNum                                           >::Type                     >::Type val)  {      return Binder<FunctionPtr<RT,P1,P2>,                    PNum,                    BoundVal                      <typename FunctorParam<FunctionPtr<RT,P1,P2>,                                             PNum                                            >::Type                      >                   >(func_ptr(fp),                     BoundVal<typename FunctorParam                                <FunctionPtr<RT,P1,P2>,                                 PNum                                >::Type                             >(val));  } 
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