float f(float x) { float y = 2; float z = 3; return x*x+y*y+z*z; } int main() { . . . . . . integrate(&f,0,1); . . . }
But this has some problems. What if we have a function f() of three arguments but want to integrate it along the line y=2, z=3. We need to integrate a helper function
float f(float x,float y,float z) { return x*x+y*y+z*z; } float g(float x) { return f(x,y,z); } float y,z; int main() { . . . . . . y = 2; z = 3; integrate(&g,0,1); . . . }
struct Data { float y,z; }; float f(float x,void *d) { Data *p = (Data *)d; return x*x+p->y*p->y+p->z*p->z; } int main() { . . . . . . Data d; d.y = 2; d.z = 3; integrate(f,0,1,&d); . . . }
That's pretty horrible even though it's more or less the standard in the C world! In Scheme we can simply build a function on the fly like: (lambda (x) (f x y z)). Can we do something like this in C++?
C++ has a mechanism for handling pointers to functions that hides the fact that you are manipulating pointers to functions: virtual functions. Using these we can have our integrator function call a function whose identity isn't yet determined at runtime. Here's an example:
class Integrand { public: float evaluate(float) = 0; float integrate(float x0,float x1) { . . . } }; class F : public Integrand { float y,z; public: Integrand(float y0,float z0) : y(y0), z(z0) { } float evaluate(float x) { return x*x+y*y+z*z; } } int main() { . . . . . . F f(2,3); integrate(f,0,1); . . . }
That's certainly much more elegant. But unfortunately it's authoritarian. It forces users of the integrate method to derive their functions from Integrand. What if you want to hand the same function to a numerical differentiator? Will you have to write the same function again, only derived from another base class? It would be nice if we could make completely generic function objects that can be used in multiple ways.
This has many great advantages. The function object, f, can be pointer to a function: integrate(sin,0,1) would work for example Or it can be an object with operator() defined. One nice advantage over using virtual functions and function pointers is that it can use knowledge about the function object at runtime to optimise.
template<class F> float integrate(const F &f,float x0,float x1) { . . . . . . f(x) . . . . . . } class F { float y,z; public: F(float y0,float z0) : y(y0), z(z0) { } float operator()(float x) const { return x*x+y*y+z*z; } }; int main() { . . . . . . F f(2,3); integrate(f,0,1); . . . }
What we've actually made here is what computer scientists call a closure [3]. That's an aggregate made of a function and some of its arguments. We can't yet evaluate it because we don't yet know all of its arguments, so it's in a kind of suspended animation waiting for its final arguments.
class F { float y,z; public: F(float y0,float z0) : y(y0), z(z0) { } template<class X> X operator()(X x) const { return x*x+y*y+z*z; } };
With the help of a template member function F is truly polymorphic. Our integration algorithm can throw any type it likes at F, as long as the methods operator() uses understand. In fact, it's even possible for the integrate function to reconstruct a symbolic representation of the function for a large class of functions by applying operator() to symbolic types that have the arithmetic operators appropriately defined. With a little extra work we can do some real functional programming and even port Haskell code [1] directly to C++..
We have now come a long way from old-fashioned C function pointers.