Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-05-28 16:24:24


On Wed, May 28, 2008 at 1:07 PM, Sohail Somani <sohail_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk