Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-05-14 22:06:08


On Wed, May 14, 2008 at 5:50 AM, Giovanni Piero Deretta wrote:
> On Wed, May 14, 2008 at 6:33 AM, Daniel Walker wrote:
>> As I understand it, the point of boost::function is that it can
>> defer/hold arbitrary callable objects; i.e. it promotes second-class
>> builtins to first-class. For consistency, it also holds function
>> objects, though there's no other point to it since function objects
>> are already first-class and already deferred.
>>
>
> Yep, exactly :). Their primary reason is to wrap function objects and
> let them "boldly go where no template has gone before".

I love that - "boldly go!" But just to be perfectly clear and to make
sure everyone's on the same page, when I said arbitrary callable
objects I meant any object f for which expressions like f() are well
formed; i.e. function references and function pointers in addition to
function objects. So, the primary reason is to wrap arbitrary callable
objects into function objects; i.e. to promote second class builtin
functions to first class. The light came on for me after reading
20.5.15.2 and 20.5.1 in N2588.

<snip>
>> template<class T>
>> T g(T t) { return t; }
>>
>> overload_set<
>> mpl::vector<int(int), float(float)>
>> > g0(g<int>, g<float>);
>> polymorphic_function<Magic> f0 = g0;
>>
>> call(f0);
>>
>> I have some ideas about the Magic involved with polymorphic_function,
>> but I'll save that for another thread. My point is just that Marco's
>> multi-signature function is a solution to one problem but not both.
>>
>
> Agree, see also my reply to Steven.
>
> About the Magic in poly_function, I'm having some thoughts about hijacking
> BOOST_TYPEOF type registration machinery or postprocessing linker errors...
> I'll have to think about it.

Actually, just for polymorphic_function, what I have in mind is much
simpler. It's so simple it doesn't really deserve to be called magic;
it's more of a mere technique. Nonetheless, I sometimes think it's
nothing less than the grace of God that I arrived at it at all! I was
going to discuss this in response to some comments Shunsuke made in
his Egg review (and I still plan on responding!), but since it came up
again, I'll just tell the whole story here.

First, Giovanni, I really do like your definition of polymorphic
function objects, but for a moment let's consider polymorphism from a
more abstract level. A more general definition of polymorphism could
be two types representing the same thing. For some polymorphic types
conversion is implicit, and for others, users are required to
explicitly convert objects from one type to another. For example, take
the relationship between the string "0" and the integer 0. These are
two types that both refer to the same number - zero. In some
languages, these types can be used completely interchangeably, but in
C++ we have lexical_cast.

Now, in a similar way, given a polymorphic version of std::plus, for example...

struct plus {
    template <class> struct result;
    template <class T>
    struct result<plus(T,T)> {
        typedef T type;
    };
    template<class T>
    T operator()(T t0, T t1) { return t0 + t1; }
};

function<int(int,int)> add_ints = plus();
function<float(float,float)> add_floats = plus();

... add_ints and add_floats are objects of two different types that
both refer to the same function - plus. To convert between them, what
is needed is a functional_cast.

Now, boost::function already has infrastructure to support this via
its target() member function. However, it does not retain the
underlying polymorphic functor type. We need some protocol to
encode/communicate this type. It seems to me that result_of's
signature template argument already does just what we need!

As a matter of explanation, call signatures, as I understand them,
come from C and are of the form:

return_type(first_argument_type, second_argument_type)

These are the sorts of signatures that boost::function must be
instantiated with.

However, by convention, result_of uses the return_type position of a
call signature to store the type of a callable object, which could be
a polymorphic function object. Let's call this protocol a
"polymorphic" signature. Comparing the two, in call signatures the
return type position denotes the return type of a function; in
"polymorphic" signatures the return type position denotes the type of
a potentially polymorphic callable object. Giovanni, note that this
corresponds nicely to your definition of polymorphic function objects
as having variable return types.

So, the "polymorphic" signatures used by result_of are of the form

function_type(first_argument_type, second_argument_type)

By using "polymorphic" signatures as the target of the cast,
implementing functional_cast is trivial and allows you to do
conversions like so:

function<int(int,int)> f0
    = functional_cast<plus(int,int)>(add_floats);
function<float(float,float)> f1
    = functional_cast<plus(float,float)>(add_ints);

With this notion of two different signature protocols -
boost::functions's call signature and result_of's "polymorphic"
signature - implementing polymorphic_function can be reduced to the
problem of writing a call-wrapper that supports both signature
protocols and dispatches to the appropriate cast.

Actually, it's even simpler than that. Since the call wrapper for
polymorphic function objects will have access to the actual type of
the wrapped object via the "polymorphic" signature, there's no need to
cast anything. functional_cast is only needed to recover from
boost::function's type erasure. polymorphic_function doesn't erase the
wrapped type specified in the "polymorphic" signature and can
therefore dispatch to the wrapped function object directly.

However, result_of's "polymorphic" signature protocol does not yet
encode all the information need to wrap a polymorphic function object.
For example, what should the signature be for the following functor?

struct f {
    template <class> struct result;
    template <class T>
    struct result<f(T,int)> {
        typedef T type;
    };
    template<class T>
    T operator()(T t, int i);
};

How do you specify that the first argument corresponds to a template
parameter while the second argument is an int? As a solution, I think
using MPL placeholders is adequate. So the fully polymorphic signature
for the function object above would be f(_1,int).

In other words, Magic = f(_1,int). ;-)

Now polymorphic_function can be used in place of boost::function to
wrap arbitrary callable objects without compromising polymorphism.

// A function.
int f(int i) { return i; }

// A polymorphic functor.
struct g {
    template <class> struct result;
    template <class T>
    struct result<g(T)> {
        typedef T type;
    };
    template<class T>
    T operator()(T t) { return t; }
};

// Treat call signatures the same as boost::function
polymorphic_function<int(int)> f0 = f;
polymorphic_function<int(int)> f1 = g();

// Treat polymorphic signatures polymorphically
polymorphic_function<g(_1)> f2 = g();

I've attached a file that provides a simple unary implementation of
all this as a proof of concept and to give an idea of what the
boilerplate would look like. It should work for both C++03 and 0x. I
tested with gcc 4.3. If there's interest, maybe this and Marco's work
and possible parts of Egg could be organized into something really
useful.

Daniel Walker




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