Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-05-14 00:33:03


On Mon, May 12, 2008 at 1:39 PM, Giovanni Piero Deretta
<gpderetta_at_[hidden]> wrote:
> On Sat, May 10, 2008 at 7:39 PM, Marco Costalba <mcostalba_at_[hidden]> wrote:
> > On Sat, May 10, 2008 at 6:08 PM, Giovanni Piero Deretta wrote:
> > >Marco Costalba <mcostalba_at_[hidden]> wrote:
> > >> Where boost.function naturally models a function, multi-signature
> > >> boost::function naturally models an overload set

I think this is a great explanation of your function object, Marco,
and you should keep this focus. I would encourage you to consider
seriously making this purpose explicit by renaming/refactoring your
function object into something along the lines of overload_set - give
it a name and interface that describes what the function object does
(stores and dispatches callable objects based on call signatures
similarly to the compiler's "overload set" for a function) rather than
naming it according to how it does it (by maintaining multiple
boost::functions).

This is an interesting and potentially very useful utility by itself -
a call-wrapper for multiple callable objects; a cross between a
call-wrapper and container, really.

<snip>
> * A monomorphic function object is a function object which has a
> single type for each argument (if any) and a single
> result type. Think for example of the standard (C++03) binders. In
> practice the argument types are not strictly fixed,
> as conversions are allowed, or even are fully templated (for example
> boost::bind). The important part is that the
> result type is fixed.
>
<snip>
> * A polymorphic function object is a functor which works for multiple argument
> types, and for which the return value type is (meta)function of the
> argument types.

That's a good comparison/explanation of poly- vs mono-morphic function
objects. By this definition, Marco's multi-signature function is
already polymorphic. Well, it is now that it supports result_of, which
I thought it already did, but I must have misread something in another
thread.

>
> To let client code deduce the result type you should implement the
> result_of protocol.
>
> // a stateless polymorphic function object:
> struct twice {
> template<class Sig> struct result;
>
> // the result type is simply the argument type, but in principle it
> can be an
> // arbitrary function of the argument type.
> template<class T> struct result<twice(T)> { typedef T type; };
>
> template<class T>
> T operator()(T f) { return f + f; }
> };
>
> std::cout << twice()(10); // print 20
> std::cout << twice()(std::string("hello")); // print "hellohello"
>
> You can (meta)compute the result type of a polymorphic function object
> using boost::result_of:
>
> boost::result_of<twice(int)>::type i; // i is an int
> boost::result_of<twice(std::string)>::type j ;// j is a string.
>
> i = twice()(10);
> j = twice()(std::string("hello");

This seems normal to me. But I don't see the point of your next step, Giovanni.

>
> Now, what if I want to erase the actual type of the function object?
> If I use boost::function I have to fix the argument type:
>
> boost::function<int(int)>f1= twice();
>
> f1(10); // ok, returns 20
> f1(10.7); // bad, returns 20 again!
> f2(std::string("hello")); // doesn't even compile!
>
> which means that the function object is no longer polymorphic. Well, i
> can pass an int to f1
> and it will (kind of) work, but it will certainly no longer work for
> std::string.

twice() is already polymorphic. If you want to use it polymorphically,
you don't need to do any additional work. Why wrap it in the first
place?

>
> That's where a multisignature function object would help:
>
> boost::msf_function<double(double), int(int),
> std::string(std::string)> f2 = twice(); // magic here!
>
> f2(10); // ok: 20
> f2(10.7); // ok: 21.4
> f2(std::string("hello")); // great: "hellohello"

How does that help? You could just have written:

twice f2; // no need for magic here!

f2(10); // ok: 20
f2(10.7); // ok: 21.4
f2(std::string("hello")); // great: "hellohello"

I don't see the point of wrapping twice().

>
> I.e. MSF is the natural extension of boost::function in the realm of
> polymorphic function objects (well, you still
> have to choose beforehand the types you want to work with, but it is
> still much better than boost::function).

If you have to choose the types beforehand, then I'm not sure that
this extension is so natural.

I mean, I see what you're getting at - there is a need for a function
object (call it polymorphic_function, for example) whose operator()
would be templated. But I don't think this multi-signature function
should be coerced into that function object. They're not the same
thing.

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.

It would be nice to have a call-wrapper, like polymorphic_function,
that would do the same thing as boost::function (promote builtins,
single interface for builtins and functors, etc.) but without "fixing"
the types of polymorphic function objects. For example, with
boost::function we can write:

template<class T0, class T1>
T0 call(function<T0(T1)> f, T1 t1) { return f(t1); }

int g(int) { return 0; }

struct h {
    int operator()(int) { return 0; }
};

function<int(int)> f0 = g;
function<int(int)> f1 = h();

call(f0, 0);
call(f1, 0);

With polymorphic_function we could write:

template<class Magic>
typename result_of<
    polymorphic_function<Magic>(int)
>::type
call(polymorphic_function<Magic> f) { return f(0); }

int g(int) { return 0; }

struct h {
    template<class T>
    T operator()(T) { return 0; }
};

polymorphic_function<Magic> f0 = g;
polymorphic_function<Magic> f1 = h();

call(f0);
call(f1);

In other words, by some Magic, the types of the parameters at the call
site do not need to be known when polymorphic_function defers/wraps a
callable object. However, no matter how powerful the Magic, the
following is not possible.

template<class T>
T g(T t) { return t; }

// assigning an unresolved function
polymorphic_function<Magic> f0 = g; // error

call(f0);

This is because the overload set generated by the function template
g() has no type. Actually, an overload can't even be generated without
knowing the types at the call site. You cannot promote a builtin
function template to first-class and completely preserve the
original's polymorphism.

However, in some use-cases, we can get around this problem with
something like a boost::overload_set that wraps multiple callable
objects and can be used to approximate a "deferral" of the compiler's
overload resolution. This is where the multi-signature function could
help.

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.

<snip>
> Currently your interface does not support directly binding a
> polymorphic function object to all signatures, but
> as you shown, it can be done easily.

I know this conversation has progressed elsewhere in this thread, and
I'm not completely caught up. I think that binding multiple signatures
to a single function object by default, without the user explicitly
requesting it, may not be a good thing. My initial preference is to
allow ref() for these situations, as Marco has suggested, but I'll
reserve judgement until I can better see where y'all are going with
it.

Daniel Walker


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