Boost logo

Boost :

From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2008-05-14 05:50:26


On Wed, May 14, 2008 at 6:33 AM, Daniel Walker
<daniel.j.walker_at_[hidden]> wrote:
> 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:
>
> <snip>
<more 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.

Sure it is. Marco wanted a clear explanation and not knowing from
where to start,
I start from the beginning :)

>
> > [ some stuff which show twice being polymorphic]

> 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 was just an example. No need to make it polymorphic in that case, I can
directly use twice. But often you need to erase the type (because you
want a type
homogeneous collection of functors, because you want to do runtime
dispatching or many
more reasons) . I wasn't showing why I would want do it. I was just
showing what behavior
I would like, if I have to do it.

>
>
> >
> > 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!

my statement "That's where a multisignature function object would help"
was missing the "assuming that I need to erase the type of a polymorphic
function object".

If I can use twice directly fine, I'll do it, is faster and easier. But when
you can't, you just can't :)

>
> >
> > 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.
>

Without some sort of template virtual functions, I do not think we can
do better.

With extensive type registration you can sort of approximate it.

> 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.

IMHO they are. At least a multi signature function should be the most reasonable
approximation of a fully polymorphic function object with type erasure.

Renaming it to overload_set, as you proposed, might shift the focus on
multiple signatures of the same underlying function object to a collection
of multiple objects, but honestly, I do not think I have ever had a
need for the latter.

>
> 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".

> 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); }

Well, if I need to make 'call' templated on the signature set, I might as well
make 'call' templated on the whole function type. The only reason I
can think that
you might want to do that is to cut down template instantiations (you
need to instantiate
a 'call' for each signature instead of one for each function object type).

>
> 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.
>

I think that the important point is that it doesn't really matter what
types the function object support,
but it is a matter of what types the caller will pass to the function object;
The implementor of a function which takes a polymorphic callback knows
the types it will pass to the callback,
so it can easily encode them in its signature.

typedef boost::tuple<int, double, float> my_tuple ;

// defined in another translation unit
for_each(my_tuple&, boost::msf<void(int&), void(double&), void(float&)>);

struct printer {
     template<class T> void operator()(T x) {
         std::cout << x;
    }
};

my_type t = ...;
for_each(t, printer());

Printer in principle works with all OutputStreamable types, but we do
not need to encode this in the interface to for_each.
for_each only needs its callback to work with the set of elements in the tuple.

> 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.
>

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.

> <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.
>

Marco has shown that ref (plus some magic to store a copy of my functor in MSF)
is a solution for my problem, but IMHO with an inacceptable overhead
compared with
the straightforward solution.

There might be space in boost for different wrappers which implement
each behavior.

If it is deemed useful, It is probably better if Marco's MSF maintains
its current behavior.

-- 
gpd

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