5.6 Handling Functors
Let's start with handling functors. Functors are loosely defined as instances of classes that define operator(), just as Functor itself does (that is, Functor is a functor). Consequently, Functor's constructor that accepts a functor object will be a template parameterized with the type of that functor.
template <typename R, class TList>
class Functor
{
... as above ...
public:
template <class Fun>
Functor(const Fun& fun);
};
To implement this constructor we need a simple class template FunctorHandler derived from FunctorImpl<R, TList>. That class stores an object of type Fun and forwards operator() to it. We resort to the same trick as in the previous section for implementing operator() properly.
To avoid defining too many template parameters for FunctorHandler, we make the Functor instantiation itself a template parameter. This single parameter collects all the others because it provides inner typedefs.
template <class ParentFunctor, typename Fun>
class FunctorHandler
: public FunctorImpl
<
typename ParentFunctor::ResultType,
typename ParentFunctor::ParmList
>
{
public:
typedef typename ParentFunctor::ResultType ResultType;
FunctorHandler(const Fun& fun) : fun_(fun) {}
FunctorHandler* Clone() const
{ return new FunctorHandler(*this); }
ResultType operator()()
{
return fun_();
}
ResultType operator()(typename ParentFunctor::Parm1 p1)
{
return fun_(p1);
}
ResultType operator()(typename ParentFunctor::Parm1 p1,
typename ParentFunctor::Parm2 p2)
{
return fun_(p1, p2);
}
private:
Fun fun_;
};
FunctorHandler looks much like Functor itself: It forwards requests to a stored member variable. The main difference is that the functor is stored by value, not by pointer. This is because, in general, functors are meant to be this way—nonpolymorphic types with regular copy semantics.
Notice the use of the inner types ParentFunctor::ResultType, ParentFunctor::Parm1, and ParentFunctor::Parm2 where needed. FunctorHandler implements a simple constructor, the cloning function, and multiple operator() versions. The compiler picks up the appropriate one. If you use an illegal overload of operator(), a compile-time error occurs, as it should.
There is nothing special about FunctorHandler's implementation. This doesn't mean it didn't require a lot of thought. You will see in the next section just how much genericity this little class template brings us.
Given FunctorHandler's declaration, it's easy to write the templated constructor of Functor declared earlier in this section.
template <typename R, class TList>
template <typename Fun>
Functor<R, TList>::Functor(const Fun& fun)
: spImpl_(new FunctorHandler<Functor, Fun>(fun));
{
}
There's no copyediting error here. The two template parameter sets are necessary: The template <class R, class TList> stands for the class template Functor, and template <typename Fun> stands for the parameter that the constructor itself takes. In standardese, this type of code is known as an "out-of-class member template definition."
The body of the constructor initializes the spImpl_ member to point to a new object of type FunctorHandler, instantiated and initialized with the proper arguments.
There's something worth mentioning here, something that's germane to understanding the Functor implementation we're building. Note that upon entering this constructor of Functor, we have full type knowledge via the template parameter Fun. Upon exiting the constructor, the type information is lost as far as Functor is concerned, because all that Functor knows is spImpl_, which points to the base class FunctorImpl. This apparent loss of type information is interesting: The constructor knows the type and acts like a factory that transforms it in polymorphic behavior. The type information is preserved in the dynamic type of the pointer to FunctorImpl.
Although we haven't written much code (just a bunch of one-line functions), we're ready for a test drive. Here it is:
// Assume Functor.h includes the Functor implementation
#include "Functor.h"
#include <iostream>
// The using directive is acceptable for a small C++ program
using namespace std;
// Define a test functor
struct TestFunctor
{
void operator()(int i, double d)
{
cout << "TestFunctor::operator()(" << i
<< ", " << d << ") called.\n";
}
};
int main()
{
TestFunctor f;
Functor<void, TYPELIST_2(int, double)> cmd(f);
cmd(4, 4.5);
}
This little program will reliably print
TestFunctor::operator()(4, 4.5) called.
This means we've achieved our goal. We can now proceed to the next step.
|