[vault] polymorphic_function call wrapper

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. Here's a brief demonstration to give an idea of what's included: // 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); } } // A second class builtin function. float builtin_divide(float x, float y) { return x / y; } // A TR1 result_of compatible function object. struct divide { template<class Signature> struct result : argument_of<Signature, 0> { }; template<class T> T operator()(T x, T y) const { return x / y; } }; int main() { // Promote builtin functions to first class. do_division< float(float,float) >( builtin_divide ); // Perform type-erasure. do_division< float(float,float) >( std::divides<float>() ); // Use polymorphic function objects polymorphically. do_division< divide(_1,_1) >( divide() ); } The file polymorphic_function.hpp provides the following components: * signature/call_signature/erase are metafunctions used to describe polymorphic functions. They allow you to do things like erase the callable object type of a boost::result_of compatible signature so it can be used with boost::function. typedef signature<my_function(int)>::type signature_type; typedef result_of<signature_type>::type result_type; typedef function<erase<signature_type>::type> function_object_type; * is_polymorphic_function is a metafunction used to designate function prototypes that may have variable return types according to boost::result_of. These are a subset of the prototypes detected by boost::is_function. More pertinently, is_polymorphic_function distinguishes the boost::function protocol for specifying function signatures - a.k.a. call signatures - from the boost::result_of protocol - a.k.a. polymorphic signatures. * is_incomplete_function is a metafunction used to designate polymorphic functions with variable argument types. These are a subset of the prototypes detected by is_polymorphic_function. They are described using Boost.MPL placeholders as arguments in the signature. * argument_of is a metafunction used to deduce the types of arguments to functions. It's a more general, nary alternative to the C++98 argument_type member. For example, argument_of can be used to detect call-by-reference functions: template<class F> void g(F f) { int x = 0; int& y = f(x); if(is_reference<typename result_of<F(int)>::type>::value && is_reference<typename argument_of<F(int), 0>::type>::value) assert(&x == &y); } int& my_function(int& t) { return t; } int main() { g(my_function); } * polymorphic_function is a call wrapper, which can be used in place of boost::function to allow both builtin functions and function objects to be used interchangeably without the degradation of polymorphism incurred by wrapping polymorphic function objects in boost::function. Essentially, this is achieved by giving the user the option of either erasing the function object's type as one would with boost::function or retaining the function object's type as one would with boost::result_of. * functional_cast is used to change the signature of a call wrapper. It can be used with both std/boost::function and polymorphic_function. For example, by specifying a new signature one may change the arity or argument types of a call wrapper, so long as the underlying callable object can be described by the new signature. Also included is a test set. I tested on gcc 3.4, 4.3 and msvc 9. The code includes some documentation in the form of inline comments. As far as future work and known defects, this is the first time I have seriously played with C++0x forwarding conventions using std::forward and rvalue references. I'm not confident that I got it perfectly right, so any corrections/guidance would be greatly appreciated. In C++98, I use an argument forwarding technique similar to Boost.Bind and Boost.Lambda - arguments are taken as non-const references and forwarded as rvalue conversions of the types requested in the signature. Secondly, type deduction using argument_of could have better support for function objects in C++98 and currently only supports member template operator() in C++0x. Finally, I'm sure more boost::function-like features could be added to polymorphic_function including members for queryng capacity, comparison operators, an allocator template parameter, etc. Thanks for your time and consideration! Cordially, Daniel Walker

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. -- Sohail Somani http://uint32t.blogspot.com

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

Daniel Walker wrote:
On Wed, May 28, 2008 at 1:07 PM, Sohail Somani <sohail@taggedtype.net> 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
Daniel Walker wrote: 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().
Great explanation, thanks. So barring the do_division example, does it solve an existing problem? I'm not saying it isn't useful, I'm just trying to figure out if there is a problem I have currently that I could use to solve this or whether there are a greater class of problems we can now solve easily because this class exists. -- Sohail Somani http://uint32t.blogspot.com

On Wed, May 28, 2008 at 6:51 PM, Sohail Somani <sohail@taggedtype.net> wrote:
Daniel Walker wrote:
On Wed, May 28, 2008 at 1:07 PM, Sohail Somani <sohail@taggedtype.net> 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
Daniel Walker wrote: 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().
Great explanation, thanks.
So barring the do_division example, does it solve an existing problem? I'm not saying it isn't useful, I'm just trying to figure out if there is a problem I have currently that I could use to solve this or whether there are a greater class of problems we can now solve easily because this class exists.
Good question. I suppose you could say that using polymorphic function objects interchangeably with builtin functions is currently problematic, because you loose any benefit of having a templated operator() as soon as you call wrap it in boost::function. But really, since you could never invoke call wrappers polymorphically before, I suppose this opens up a new class of problems that can now be addressed. Probably a better way to put it is that boost::function gives you one category of call wrapper, and polymorphic_function introduces a new category of call wrapper that supports a larger set of use cases. For example, "overriding" the compiler's argument deduction with functional_cast is a new use for call wrappers. functional_cast may be easier to use than the current boost::function::target member, but when combined with polymorphic_function it also introduces new use cases: Essentially, it allows you to restrict argument type deduction or change restrictions multiple times. (Really, you're changing the types used in argument forwarding.) In the example of my demo, polymorphic_function and functional_cast were used to defer type erasure and could be used to re-erase a function object multiple times with multiple call signatures, because polymorphic_function, unlike boost::function, doesn't force the user to erase the callable object at instantiation. I think these are all new use cases. Daniel Walker
participants (2)
-
Daniel Walker
-
Sohail Somani