
On Wed, May 28, 2008 at 1:07 PM, Sohail Somani <sohail@taggedtype.net> wrote:
Daniel Walker wrote:
Hello,
Following up on several discussion over the last few weeks, I've placed an implementation of a polymorphic call wrapper (and associated utilities) similar to Boost.Function in the file polymorphic_function.zip in the Function Objects directory on vault at http://tinyurl.com/56zvo4.
For those of us just joining, can you please give the main motivation for use of this new type? I hear polymorphic function and think many things, none of which may be close to the truth.
Thanks.
Sure. The short answer is that for an instance of boost::function, the return and argument types are fixed, whereas for an instance of polymorphic_function, they may vary. So, polymorphic_function allows you to deal with arbitrary callable object types without being forced to fix the return and argument types if one of those arbitrary callable objects happens to have a templated or overloaded operator(). In other words, given an instance of a boost::function f and an argument x, the expression f(x) always has the same, single type for all types of x. By contrast, given an instance of polymorphic_function g and an argument x, the expression g(x) may have multiple types depending on the type of x. Going back to my example demonstration (which is included in polymorphic_function.zip): // Work with arbitrary callable objects without loss of polymorphism. template<class Signature> void do_division(polymorphic_function<Signature> f) { // Do floating point division. float one = 1, two = 2; assert(f(one, two) == 0.5); if(is_polymorphic_function<Signature>::value) { // Now, drop the remainder using integer division. int one = 1, two = 2; assert(f(one, two) == 0); // And now, use high precision floating point division to // compute the inverse product of a series of integers. int series[3] = { 1, 2, 3 }; double x = std::accumulate(&series[0], &series[3], 1.0, functional_cast<double(double,double)>(f)); assert(0.16666666 < x && x < 0.166666667); } } The second and third assertions would always fail for an instance of boost::function<float(float,float)>, for example, regardless of the type of the wrapped target object. However, with polynomial_function in this example, if the target object can take floats, ints and doubles, perform the correct calculation and return the correct type accordingly, then polynomial_function will behave just as polymorphically as its target object. So, with polynomial_function the first assertion succeeds as it would with boost::function, and additionally the second and third assertions succeed for polynomial target objects, which would be impossible using boost::function. To be more specific, I'll try to use the same terminology as the C++0x working draft. I'll give citations to N2606 as I go along. This is further explained in the comments in the code, if you'd like more info. A call wrapper (20.5.1.6) is useful for deferring calls to arbitrary callable objects (20.5.1.4) by associating a callable type to a value that can be assigned, passed to functions, returned from functions, and generally treated like any other value. For example, call wrapping can be used to implement callbacks, to employ standard library algorithms that take adaptable function objects, or to use functional programming techniques like currying. boost::function (which is being standardized in 20.5.15) is a polymorphic call wrapper in the sense that given a call signature (20.5.1.2) it can wrap arbitrary callable objects. However, once instantiated with a call signature, the type of invocations of the wrapper is fixed. Borrowing terminology from programming language theory (Note that my background is in natural language processing, but I did study a little lambda calculus and ML using Ravi Sethi's textbook. Unfortunately, that was 10 years ago, so I'm probably a little rusty - corrections are welcome!), boost::function could be called "rank-1 polymorphic." After instantiation, the type of invocations of polymorphic_function objects are not necessarily fixed and may vary according to the argument types. So, polymorphic_function could be called "rank-n polymorphic," with apologies to the ML experts out there. ;-) Basically, boost::function's operator() is always a function, but polymorphic_function's operator() may be a function template. So, boost::function must always know the argument types at the call site, but polymorphic_function doesn't always need to know. Daniel Walker