Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Yet Another Generalized Functors Implementation in C++

0.00/5 (No votes)
16 Dec 2005 1  
An article on generalized functors implementation in C++. Generalized functor requirements, existing implementation problems and disadvantages are considered. Several new ideas and problem solutions together with the compete implementation are suggested.

Introduction

Generalized functors are important and powerful design artifacts. An excellent introduction on generalized functors idiom, its application and one of the best implementations can be found in [1]. I'll try to retell the primary ideas, concepts and implementation details below with a strong emphasis on the latter as expected from the article title.

So let's discover the generalized functors' goals first. Suppose we have the following C++:

void f2(int, int) {...}
struct A {
  void f2(int, int) {...}
};
struct B {
  void f2(int, int) {...}
  void f2const(int, int) const {...}
};
struct F {
  void operator()(int, int) {...}
};

void (*pf2)(int, int) = &f2;
void (A::*paf2)(int, int) = &A::f2;
void (B::*pbf2)(int, int) = &B::f2;
F f;

Collective notion for pf2 (pointer to static function), paf2 and pbf2 (pointers to member functions), f (an instance of a class containing corresponding operator()) is C++ callable entity that means an entity to which functor-call operator () can be applied:

pf2(1, 2);
A a;
B* pb = new B;
(a.*paf2)(1, 2);
(pb->*pbf2)(1, 2);
f(1, 2);

What if we want to treat all of them in some generic way, e.g. push them in some "container" and make all the above calls at once via a single method call of such a "container"? The first way to implement this is to develop a special "container" class. Another one is to design a universal adapter class capable of storing and making calls to all the callable entities above. Then these adapter class instances can be stored in the std containers as usual. The latter way is more generic because this adapter class can be used in other applications. It is the class that is called generalized functor. The following pseudocode introduces this:

typedef Functor<
  ...
  template arguments that transfer in some way 
  the return type and types of all parameters 
  of operator() - void and int, int for this example 
  ...
> Functor2;                // (0)
Functor2 fun1(&f2);        // (1) 
Functor2 fun2(&a, &A::f2); // (2) 
Functor2 fun3(pb, &B::f2); // (2)
Functor2 fun4(f);          // (3)
fun1(1, 2);                // (4)
fun2(1, 2);                // (4)
fun3(1, 2);                // (4)
fun4(1, 2);                // (4)
// fun1(1);                // (5)
// fun2(1, 2, 3);          // (5)
// fun3(1, true);          // (5)
// bool r = fun4(1, 2);    // (5)
Functor2 fun;              // (6) 
fun = fun1;                // (7) 
fun = fun2;                // (7)
fun = fun3;                // (7)
fun = fun4;                // (7)
// -------------------------------------------
class FunSet 
{
  typedef std::vector<Functor2> FV;
  FV fv_;
public:
  FunSet& operator+=(Functor2 const& f) 
  { 
    fv_.push_back(f);             // (8)
    return *this;
  }
  void operator()(int i1, int i2) const 
  { 
    FV::const_iterator end(fv_.end());
    for(FV::const_iterator i = fv_.begin(); i != end; ++i) 
      (*i)(i1, i2);
  }
};
FunSet fs;
fs += fun1;
fs += fun2;
fs += fun3;
fs += fun4;
fs(1, 2);

The above example is simple but has several important issues. First, it shows an application of generalized functors and the power together with the convenience that is achieved. Particularly, the fact that all fun, fun1-fun4 are of the same type (Functor2) gives a simple way to solve the above problem of "container" for different callable entities. Second, this example implicitly contains the requirements for a possible generalized functor implementation. We'll frequently refer to them later so let's emphasize:

R1.

Lines 1-3 imply a universal support for all kinds of callable entities and demand corresponding ctors.

R2.

Lines 4 and 5 imply the function call of the underlying callable entity instance to be type safe. This means that if the lines (5) are uncommented, they should not be compilable since they try to do a call either with inappropriate number or types of arguments or with inappropriate return type.

R3.

Lines 6-8 imply full support of value semantics, i.e. the need for proper default ctor, operator=, copy ctor and dtor. This is important and very convenient, taking into account C++ forbids assignment and conversions for raw callable entities of different kinds.

There are additional issues touched at line 0. They concern some other important implementation details:

D1.

The above listing shows an example of functor for callable entities with the function-call operator () taking two ints as parameters and returning void. Other parameters and return types should be supported of course that leads to a question about the way of parameterization of a generalized functor template.

D2.

But what about the different counts of the arguments of the underlying function-call operator ()? Of course, this should be supported, the problem is how to implement this as clear as possible. This is not easy in C++ as it will be shown later.

Known implementations

The two known implementations of the generalized functors are provided by Loki [2] and boost [3] libraries (the latter is proposed for C++ standard [4]). Providing full generalized functor semantics and functionality, they are completely different in internal implementation, from the concepts to the resulting complexity. Of course, there are other implementations as well. Going through the mentioned R1, R2, R3, D1 and D2 issues, we'll build our own one.

Let's consider D1 issues now. There are two main ways to parameterize the generalized functor template. The first uses function types, and the second uses a pair of return type and typelist containing the types of all the arguments of the corresponding function-call operator ():

template <class F> class Functor;              (a)
//and 

template <class R, class TList> class Functor; (b)

to use them as:

typedef Functor<void (*)(int, int)> Functor2;            (a)
//and 

typedef Functor<void, TYPELIST_2(int, int)> Functor2;    (b)
typedef Functor<void, CreateTL<int, int>::Type> Functor2;

Note: In case (b), TYPELIST and CreateTL are a macro [1] and a meta-template respectively, used to generate typelists.

Case (a) (used in boost) looks more elegant, but has several drawbacks. Functions with the same signature can have different modifiers. Cv-qualifiers (const and volatile) are the first important examples. They can be applied only to member functions, thus:

typedef Functor<void (*)(int, int) const> Functor2;

declaration will be rejected by the C++ compiler because the corresponding function type is invalid. So the question arises - how to create a generalized functor instance for B::f2const function in this case? It is possible of course, but at the cost of unnecessary code complication spoiling the initial elegance. Other examples are __cdecl, __stdcall, __fastcall (both for non-member and member functions) and implicit __thiscall (for member functions) modifiers. Yes, they are all non-standard extensions provided by compiler vendors, but some of the vendors are too important to ignore. The problem is that the function types with different modifiers are of different types, the same can be said about the functors instantiated with them. Summing up, the function type is a too low-level entity to use for parameterization of such a high level entity as generalized functors.

Cases (b) are free from the above disadvantages though they look a bit less elegant. But we can fight for elegance, providing functor creation functions for example. With their help we can bring the client code to the following nice syntax:

FunSet fs;
fs += MakeFunctor(f2);
fs += MakeFunctor(A::f2, &a);
fs += MakeFunctor(B::f2, &b);

To implement MakeFunctor functions we need mapping between the function type and its return type and the typelist containing types of all its arguments, which can be defined using a common traits idiom:

template <typename F> struct FunTraits;
//... partial specializations for different numbers

//    of arguments of the function types ... 

template <typename R, typename P1, P2>
struct FunTraits<R (*)(P1, P2)> 
{
  typedef NullType ObjType;
  typedef R ResultType;
  typedef P1 Parm1;
  typedef P2 Parm2;
  typedef TYPELIST_2(P1, P2) TypeListType;
};
//... specializations for types with __cdecl,

//    __stdcall etc. modifies can be here ...

template <class O, typename R, P1, P2>
struct FunTraits<R (O::*)(P1, P2)> 
{
  typedef O ObjType;
  typedef R ResultType;
  typedef P1 Parm1;
  typedef P2 Parm2;
  typedef TYPELIST_2(P1, P2) TypeListType;
};
//... specializations for all combinations of 

//    cv-qualified types should be here ...

The functor creation helper functions are very simple to implement now:

template <typename F> inline 
Functor<typename FunTraits<F>::ResultType, 
  typename FunTraits<F>::TypeListType> MakeFunctor(F fun)
{
  return Functor<typename FunTraits<F>::ResultType, 
         typename FunTraits<F>::TypeListType>(fun);
}
template <typename MF, class P> inline 
Functor<typename FunTraits<MF>::ResultType, 
    typename FunTraits<MF>::TypeListType> 
MakeFunctor(MF memfun, P const& pobj)
{
  return Functor<typename FunTraits<MF>::ResultType, 
     typename FunTraits<MF>::TypeListType>(pobj, memfun);
}

Let's move to requirement R1 considerations. You can readily observe that a generalized functor class template is parameterized by the types completely unrelated to the types of the objects that are passed to its ctors. This means that those ctors should be defined as member templates:

template <class R, class TList> 
class Functor
{
public:
  // ctor for non-member functions and arbitrary functors

  template <typename F> Functor(F const& fun) { ... } 
  // ctor for member functions 

  template <typename P, typename MF> Functor(P const& pobj, 
                                                   MF memfn) 
      { ... } 
  ...
};

Only the ctors know the callable entity type and it gets lost after the ctors exit. But some operations still require the callable entity's type knowledge, e.g. functor copying, assignment and destruction operations. The operator() should also forward the call to the underlying callable entity instance according to its actual type. So template ctors should store the callable entity type information in some "universal form", which will allow treating them later in some polymorphic way. The most straightforward solution is as follows:

template <class R, class TList> 
class Functor
{
  struct FunImplBase
  {
    virtual R call_(...params evaluated from TList...) = 0;
    ...
  };
  template <typename F>
  class FunctorImpl : public FunImplBase
  {
    F fun_;
  public:
    FunctorImpl(F const& fun) : fun_(fun) {}
    virtual R call_(...params evaluated from TList...)
        { ... use params to make a call to fun_ ... }
    ...
  };
  template <typename P, typename MF> 
  class MemberFnImpl : public FunImplBase
  {
    P pobj_;
    MF memfun_;
  public:
    MemberFnImpl(P const& pobj, MF memfun) : pobj_(pobj), 
                                            memfun_(fun) {}
    virtual R call_(...params evaluated from TList...)
      { ... use params to make a call to memfun_ of pobj_ ... }
    ...
  };
  FunImplBase *pimpl_;
public:
  // ctor for non-member functions and arbitrary functors

  template <typename F> Functor(F const& fun) 
  {
    pimpl_ = new FunctorImpl<F>(fun);
  }
  // ctor for member functions 

  template <typename P, typename MF> Functor(P const& pobj, 
                                                   MF memfn) 
  {
    pimpl_ = new MemberFnImpl<P, MF>(pobj, memfn);
  } 
  ...
  R operator(...params evaluated from TList...)
  {
    return pimpl_->call_(... params ...);
  }
  ...
};

In the above example, an abstract base class FunImplBase defines all the operations depending on the different callable entity types. FunctorImpl and MemberFnImpl are its concrete successors implementing those operations for non-member functions together with arbitrary functors and member functions respectively. Generalized functor template itself incorporates a pointer to FunImplBase, initializes it in the corresponding ctors and uses it where it is needed. It should be noted that the above example conceptually resembles Loki's functor template.

Let's address R2 and D2 issues now. Using typelists you can pack an arbitrary number of different types into one type and this is great for many generic algorithms. But this very arbitrary number of types should be fixed in the typelists design (and usually is chosen high enough to fit all uses). A similar problem - C++ has no facility to manipulate function parameter's count and so you can't treat them in a generic way. In our case having some TypeList<T1,T2,T3,...Tn> type of arbitrary length would be nice to generate the corresponding function e.g. operator()(T1,T2,T3,...Tn). This would completely solve the problem of type safe function-call. But as we already said this can't be done in C++ and we have to find out roundabout ways. There are two ways. In the following first way we can declare a Functor class template and then define partial specializations for all possible count of types in the operator():

template <class R, class TList> class Functor;
template <class R> class Functor<R, TYPELIST_0(P1)> { ... };
template <class R, class P1> class Functor<R, TYPELIST_1(P1)> 
    { ... };
template <class R, class P1, class P2> 
    class Functor<R, TYPELIST_2(P1, P2)> { ... };
...

But in this case each specialization also contains all the code independent of the operator() arguments count. One can try to factor out this code in some way into the base classes for example (as boost does) etc. But the result does not seem to be good.

In the second way we define a single front-end functor template and simply overload all possible operator():

template <class R, class TList> class Functor 
{
  ...
  operator()()
  operator()(T1)
  operator()(T1,T2)
  ...
  operator()(T1,T2,...Tn)
  ...
  operator()(T1,T2,T3,...Tmax)
};

This solves the problem but introduces some potential bugs. If we try to use the resulting generalized functor template as in the opening code listings and uncomment the commented line 5, the compiler will gaily compile them guaranteeing runtime crash! The solution is to take FunImplBase, FunctorImpl and MemberFnImpl declarations out of the Functor template and define specializations for all possible count of types in the call_ functions. See an example for FunImplBase:

template <class R, class TList> struct FunImplBase;
... partial specializations for different numbers of arguments ... 
template <class R, typename P1, P2> 
struct FunImplBase<R, TYPELIST_2(P1, P2)>
{
  virtual R call_(P1 p1, P2 p2) = 0;
  ...
};

There are a number of specializations here, but each one contains only one suitable call_ function. So the code for line 5 in the opening code listing is not compilable now. Though the compiler can find a suitable operator() in the Functor template, it fails to find a suitable call_ function in a corresponding FunImplBase specialization. But as you may notice, we've got back to the necessity of partial specializations - not for the Functor template itself but for some helper classes. This is a bad news. The good and the more important ones are that the specializations contain as little unnecessary code (independent from the count of arguments of the call_ function) as possible. Loki's implementation follows this way and achieves very good code factoring.

We've considered how generalized functor requirements and implementation detail issues (R1, R2, D1 and D2) can be solved and how they are solved in the existing libraries. We'll leave the requirement R3 issues untouched since they do not introduce any new ideas and are rather easy to implement.

Going forward

In the previous section, we discussed the generalized functor requirements, design problems and possible solutions. We've also got to a principle implementation usually used in the existing libraries. In this section, we'll try to investigate and analyze its disadvantages and remove them.

One of the disadvantages which can be discovered from the previous section examples is the need of heap allocation for FunctorImpl and MemberFnImpl instances. This is a squander - both from the point of view of speed and memory consumptions (because all general-purpose allocators allocate memory with some granularity wasting some space). Keeping this in mind, Loki provides for its functor template the optimized custom small object allocator. Boost implementation contains a trick that allows to refuse heap allocation for generalized functors for non-member functions, but rolls down to heap allocation with generalized functors in other cases.

How about refusing heap allocation in all cases? The possible solution is to incorporate a buffer of some fixed (but customizable) size into the functor class template itself and use it as a pool for memory allocation. This idea exploits the fact that for all callable entity kinds, only a few bytes of memory are needed to store the internal data. Thus with a functor for a static function only the function pointer (4 bytes on 32-bit systems) should be stored. With functor to a member function a pointer to the target object instance and a pointer-to-member for a function to be called are needed. The former takes 4 bytes again, while the size of the latter can vary from 4 to 20 bytes (see an excellent study here [5]). Internal data size for an arbitrary functor can't be predicted beforehand, but we know from our experience that functors are usually designed to have only a few data members or no members at all.

This idea can be expressed in the following way:

template <class R, class TList, unsigned int size = 4 * sizeof(void*)> 
class Functor
{
  ...
  struct Typeless {
    char buffer_[size];
    template <class T, class V> T* init(V const& v) 
        { new(&buffer[0]) T(v); }
    template <typename T> inline T const& get() 
        const { return *reinterpret_cast<T const*>(&buffer[0]); }
    template <typename T> inline T& get() 
        { return *reinterpret_cast<T*>(&buffer[0]); }
  }
  Typeless val_;
  FunImplBase<R, TList> *pimpl_;
public:
  ...
  // ctor for non-member functions and arbitrary functors

  template <typename F> Functor(F const& fun) 
  {
    pimpl_ = val_.init<FunctorImpl<F> >(fun);
  }
  ...
};

Functors designed in the above way, waste some space in the case of least memory-consuming functors for static functions. But, firstly, when using heap allocation, some memory wastes occur due to the allocation granularity. And, secondly, the profit is admirable - all functor operations involved in copy-by-value support (ctors, dtor, operator=) work with a giddy speed. These operations are intensively used in important generalized functor applications based on functor chaining and binding.

For the remaining minority of cases when sizeof(T) > size we should provide in Typeless::init() switching to normal allocator. The simplest way to do this is the following:

template <class R, class TList, 
               unsigned int size = 4 * sizeof(void*)> 
class Functor
{
  ...
  struct Typeless {
    char buffer_[size];
    template <class T, class V> T* init(V const& v) 
        { new(&buffer[0]) T(v); }
    template <typename T> inline T const& get() const 
        { return *reinterpret_cast<T const*>(&buffer[0]); }
    template <typename T> inline T& get() 
        { return *reinterpret_cast<T*>(&buffer[0]); }
  }
  template <typename T>
  struct ByValue
  {
    template <typename V> inline static T* init(Typeless& val, 
                V const& v) { return val.template init<T>(v); }
    inline static T const& get(Typeless const& val) 
                                { return val.get<T>(); }
    inline static T& get(Typeless& val) 
                { return val.get<T>(); }
  };
  template <typename T>
  struct NewAlloc
  {
    template <typename V> inline static T* init(Typeless& val, 
        V const& v) { return *val.template init<T*>(new T(v)); }
    inline static T const& get(Typeless const& val) 
        { return *val.get<T const*>(); }
    inline static T& get(Typeless& val) 
        { return *val.get<T*>(); }
  };
  template <typename T>
  struct SelectStored 
  { 
    typedef typename Select<
    sizeof(T)<=sizeof(Typeless), 
    ByValue<T>, 
    NewAlloc<T> 
    >::Result Type;
    // Select meta-template returns

    // an appropriate type depending on its first argument

  };
  struct Stored 
  { 
    template <typename T, typename V> inline T* init(V const& v) 
             { return SelectStored<T>::Type::init(val_, v); }
    template <typename T> inline T const& get() const 
             { return SelectStored<T>::Type::get(val_); }
    template <typename T> inline T& get() 
             { return SelectStored<T>::Type::get(val_); }
    Typeless val_;
  };
  Stored val_;
  FunImplBase *pimpl_;
public:
  ...
  // ctor for non-member functions and arbitrary functors

  template <typename F> Functor(F const& fun) 
  {
    pimpl_ = val_.init<FunctorImpl<F> >(fun);
  }
  ...
};

In the above code snippet, val_ is used either to store the value of T entirely, or to store only the pointer to the newly-allocated value. Note: sizeof(T)<=sizeof(Typeless) expression used in the Select meta-template is not enough and should be improved by adding alignment calculations (see [6] or [3]).

Digging in we discover one more disadvantage. After introducing val_, pimpl_ pointer becomes redundant! We always "almost" know that - it is equal to either &val_ or incorporated into val_ itself. So, if we could make such a choice we could throw pimpl_ away. Remember that each instance of a class containing virtual functions (either own or inherited ones) maintains an invisible pointer to the virtual methods table which directs the virtual function calls towards the right function of the right class. If we could deal with it directly and take it out from FunctorImpl and MemberFnImpl instances into the Functor template, we could make all the needed calls to the right FunctorImpl or MemberFnImpl functions and additionally save 4 bytes! Of course, we couldn't do it dealing with C++ virtual function mechanism immediately. But we could simulate this mechanism. This is a known and a rather powerful technique (see [6] or [7] for example). For our case it could be applied in the following way:

template <class R, class TList, 
          unsigned int size = 4 * sizeof(void*)> 
class Functor
{
  ...
  struct FunImplBase
  {
    struct VTable
    {
      R (*call_)(Functor const&, 
                ...params evaluated from TList...);
      ...
    };
    // VTable vtbl_;

  };
  template <typename F>
  class FunctorImpl : public FunImplBase
  {
    F fun_;
  public:
    FunctorImpl(F const& fun) : fun_(fun) {}
    static R Call(Functor const& f, 
              ...params evaluated from TList...) 
    { 
      FunctorImpl const& this_ = 
         f.val_.template get<FunctorImpl const>();
      ... use this_ and params to make a call to fun_ ... 
    }
    ...
  };
  template <class P, class MF> 
  class MemberFnImpl : public FunImplBase
  {
    P pobj_;
    MF memfun_;
  public:
    MemberFnImpl(P const& pobj, MF memfun) : 
                        pobj_(pobj), memfun_(fun) {}
    static R Call(Functor const& f, 
                        ...params evaluated from TList...) 
    {
      MemberFnImpl const& this_ = 
         f.val_.template get<MemberFnImpl const>();
      ... use this_ and params to make ...
      ... a call to memfun_ of pobj_ ... 
    }
    ...
  };
  ...
  Stored val_;
  typename FunImplBase::VTable* vptr_;
public:
  ...
  // ctor for non-member functions and arbitrary functors 

  template <typename F> Functor(F const& fun) 
  { 
    FunImplBase* pimpl_ = val_.init<FunctorImpl<F> >(fun);
    // throw away pimpl, we don't need it in 

    // this implementation

    static typename FunImplBase::VTable vtbl =
    {
      &FunctorImpl::Call,
      ...
    };
    vptr_ = &vtbl;
  } 
  R operator(...params evaluated from TList...)
  {
    return vptr_->call_(*this, 
             ...params evaluated from TList...);
  }
  ...
};

Note: This is not a plane translation of the previous example with virtual functions, this is an adapted translation, e.g. pointer to FunImplBase is thrown away and is replaced by a pointer to a function table, FunctorImpl::Call function etc. accepts Functor instance reference as the first argument (not a FunctorImpl pointer that could be in plane translation). The result seems to be acceptable.

Let's switch to R2 and D2 issues now. We can notice that when calling FunctorImpl::Call and MemberFnImpl::Call from operator(), all its arguments should always be copied. Because, this is a call via a function pointer (as with virtual function call as well by the way) the compiler could not apply any optimizations. So what if we pack operator() arguments into a tuple and pass it to FunctorImpl::Call or MemberFnImpl::Call? Arguments copying overhead will remain the same, but FunctorImpl and MemberFnImpl could become independent from operator() arguments count. It is not shown in the previous listings but in addition to the Call functions FunctorImplBase, FunctorImpl and MemberFnImpl should also contain functions for value semantics support. So their independence from operator() arguments count evidently improves the code:

template <class TList> struct CallParms;
... partial specializations for typelists with different length ... 
template <typename P1, 
    typename P2> struct CallParms<TYPELIST_2(P1, P2)> 
{
  typedef InstantiateH<tTYPELIST_2(P1, P2)> ParmsListType;
  static inline ParmsListType Make(P1 p1, P2 p2) 
         { ... make ParmsListType tuple from p1 and p2 ... }
};
template <typename CallType, typename R, 
                 class TList> struct FunctorCall;
//... partial specializations for different numbers

//    of arguments of the function types ... 

template <typename CallType, typename R, P1, P2>
struct FunctorCall<CallType, R, TYPELIST_2(P1, P2)> 
{
  typedef InstantiateH<tTYPELIST_2(P1, P2)> ParmsListType;
  template <class Fun> static inline 
           R Call(Fun const& fun, ParmsListType& parms) 
  { 
    return fun(... parameters unpacked from parms ...); 
  }
  template <class PObj> static inline R Call(PObj const& pobj, 
                         CallType memfun, ParmsListType& parms) 
  { 
    return (
      (*pobj).*memfun)(... parameters unpacked from parms ...); 
  }
};
...
template <class R, class TList, 
      unsigned int size = 4 * sizeof(void*)> 
class Functor
{
  ...
  typedef typename CallParms<TList>::
                         ParmsListType ParmsListType;
  struct FunImplBase
  {
    struct VTable
    {
      R (*call_)(Functor const&, ParmsListType parms);
      ...
    };
    // VTable vtbl_;

  };
  template <typename F>
  class FunctorImpl : public FunImplBase
  {
    F fun_;
  public:
    FunctorImpl(F const& fun) : fun_(fun) {}
    static R Call(Functor const& f, ParmsListType parms) 
    { 
      FunctorImpl const& this_ = 
                  f.val_.template get<FunctorImpl const>();
      return FunctorCall<T, R, TList>::Call(this_.fun_, 
                                                    parms); 
    }
    ...
  };
  template <class P, class MF> 
  class MemberFnImpl : public FunImplBase
  {
    P pobj_;
    MF memfun_;
  public:
    MemberFnImpl(P const& pobj, MF memfun) : 
                              pobj_(pobj), memfun_(fun) {}
    static R Call(Functor const& f, ParmsListType parms) 
    {
      MemberFnImpl const& this_ = 
                   f.val_.template get<MemberFnImpl const>();
      return FunctorCall<T, R, TList>::Call(this_.pobj_, 
                                       this_.memfun_, parms); 
    }
    ...
  };
  ...
  Stored val_;
  typename FunImplBase::VTable* vptr_;
public:
  ...
  // ctor for non-member functions and arbitrary functors 

  template <typename F> Functor(F const& fun) 
  { 
    FunImplBase* pimpl_ = val_.init<FunctorImpl<F> >(fun);
    // throw away pimpl, we don't need it in this 

    // implementation

    static typename FunImplBase::VTable vtbl =
    {
      &FunctorImpl::Call,
      ...
    };
    vptr_ = &vtbl;
  } 
  ... evaluate Parm1 type from TList ...
  ... evaluate Parm2 type from TList ...
  ...
  inline R operator()() const 
  { 
    return vptr_->call_(*this, CallParms<TList>::Make());
  }
  inline R operator()(Parm1 p1) const 
  { 
    return vptr_->call_(*this, CallParms<TList>::Make(p1));
  }
  inline R operator()(Parm1 p1, Parm2 p2) const 
  { 
    return vptr_->call_(*this, 
            CallParms<TList>::Make(p1, p2));
  }
  ...
};

Note: In the above listing InstantiateH - is a kind of GenScatterHierarchy<> template in Loki used here as a tuple generation engine (see [1] for details).

Packing operator() arguments into the tuple and passing them in internal calls can lead to additional benefits that exceed the bounds of Functor implementation itself. E.g. we can add the following overloaded operator():

template <class R, class TList, 
       unsigned int size = 4 * sizeof(void*)> 
class Functor
{
  ... as above ...
  inline R operator()(ParmsListType& parms) const 
  { 
    return vptr_->call_(*this, parms);
  }
};

and use it to pass parms reference around two or more functors. This can simplify the implementation and even improve the efficiency of functor chaining and binding support. We could even dream of named parameters in the Functor's operator() calls - the feature not supported by C++ for simple function-calls.

Conclusions

We've considered generalized functor requirements, existing implementation problems and disadvantages. While trying to solve them we've introduced several ideas, which combined together lead to an implementation that is rather different from the known ones and seems to be more clear and efficient.

Source update (14-Dec-2005)

Generalized functors binding is added. Check out FunBind.h in the source files given with the article.

References

  • [1]. Modern C++ Design: Generic Programming and Design Patterns Applied (Addison-Wesley, 2001, ISBN 0201704315), A.Alexandrescu
  • [2]. Loki Library Project
  • [3]. boost Library Project Home
  • [4]. Generalized Function Pointers, H.Sutter
  • [5]. Member Function Pointers and the Fastest Possible C++ Delegates, D.Clugston
  • [6]. Generic<Programming>: Discriminated Unions (II), A.Alexandrescu
  • [7]. Generic<Programming>: Discriminated Unions (III), A.Alexandrescu

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here