Boost logo

Boost :

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


On Fri, May 16, 2008 at 7:10 AM, Marco Costalba <mcostalba_at_[hidden]> wrote:
> On Fri, May 16, 2008 at 12:45 PM, Marco Costalba <mcostalba_at_[hidden]> wrote:
>> On Fri, May 16, 2008 at 12:25 AM, Daniel Walker
>> <daniel.j.walker_at_[hidden]> wrote:
>>>
>>> If the overload_set were instead based on polymorphic_function, then
>>> after loading add_int for integers, you only need to specify plus once
>>> to handle all other types.
>>>
>>> overload_set<
>>> mpl::vector<int(int,int), plus(_1,_1)>
>>>> add(add_ints, plus);
>>>
>>
>
> This is fundamentally different from writing
>
> overload_set<
> mpl::vector<"comma separated function signatures">
>> add(add_ints, plus)
>
> In the latter case you instantiate plus for each signature in the
> overload_set (or multi signature function MSF, if you prefer).

I'm not convinced of what's the best name for this, but I am convinced
that multi_signature_function::function is definitely not a good name.
I think it could cause confusion with std::function, and though
they're both call-wrappers, this notion of aggregating callable
objects and then dispatching them based on their signatures is
significantly different from boost/std::function. It does something
very similar to what the compiler does internally with an overload
set, which is why I think it's fairly natural and more descriptive to
somehow use the term "overload." I noticed you even implement it in a
file called overload.hpp; I think that's the right direction to move
in.

>
> In your example you pass typename T where T = plus as template parameter.
>
> mpl::vector<int(int,int), plus(_1,_1)>
>
> Is no more a vector of signatures, it'a a vector of signatures + a
> class type. It is a vector of heterogeneous item types.

Nope. Try:

typedef mpl::vector<int(int,int), plus(_1, _1)> signature_types;
BOOST_MPL_ASSERT((
    is_function<
        mpl::at_c<signature_types, 1>::type
>
));

It's still a vector of signatures. There are two kinds of signature
here (by convention or protocol):

* int(int, int) is the usual C/C++ call signature
* plus(_1,_1) is our new emerging polymorphic signature by convention

The polymorphic signature uses the same syntax as the native call
signature but with different semantics. The type prior to the parens
denotes a callable object or reference_wrapped callable object. The
types inside the parens denote template parameters if they are MPL
placeholders, or they denote argument types otherwise. When the type
prior to the parens (let's call this the object position of the
expression) is a (possibly reference_wrapped) rank-n polymorphic
function object, the call-wrapper exhibits rank-n polymorphism. As an
aside, I prefer the term "functor" to "function object," since we're
talking about a type not an object, but the SGI STL concepts used the
term "function object" - I think it's also in the standard and
Boost.Fusion.

Also, I just realized I made a revealing mistake in nomenclature while
changing functional_cast to support reference_wrappers. The change
was:

- typedef typename callable_object<Signature>::type functor_type;
+ typedef typename callable_object<Signature>::type callable_object_type;
+ typedef typename unwrap_reference<callable_object_type>::type functor_type;

callable_object simply returns whatever is in the object position of
the Signature. However, when that object is a reference_wrapper it
isn't actually a callable object, so that's not a good name. (This is
exactly why result_of doesn't handle reference_wrapped function
objects - reference_wrapper is not a callable object.)

To help clarify things, I think it would be useful to further
formalize this signature protocol with a set of type traits similar to
boost::function_types. For example:

object_type<Signature> // same as as function_types::return_type
callable_object_type<Signature> // the actual unreference_wrapped
callable object
is_template_parameter<Signature, 0> // true for MPL placeholders

... and perhaps most importantly ...

is_polymorphic_function<Signature> // to distinguish from is_functon<>

>
> This is _really_ totally different also from an implementation point
> of view. Indeed a MSF boils down to a hierarchy of operator=() each
> one defined on a given signature and each one forwarding to a
> corresponding boost::function

I started looking more closely at your implementation, and I like the
way you do the class hierarchy. But the important part here is the
hierarchy of operator()s right? You could do assignment to the various
signature slots with an insert() or at() rather than operator=, right?
I mean you could do it in theory, if you refractored your
implementation accordingly.

>
> What you suggest is to have a fall back MSF defined as:
>
> template<typename Signatures, class fall_back_poly_class>
> class function : public fall_back_poly_class
> {
> ......good old multi sig. function implementation .....
> }
<snip>
> Is my interpretation correct?

No, I think this is incorrect due to the false assumption that
plus(_1,_1) is not a signature. From briefly looking at your
implementation, I think the simplest way to implement this would be to
replace the internal boost::function with polymorphic_function and add
a function_caller that isn't partially specialized on the decomposed
signature. This general function_caller would dispatch rank-n
polymorphic calls. This won't compile yet, but the change I'm think of
(starting from the msf_27_4_2008.zip version) would be something like
...

--- overload.hpp~ 2008-05-16 15:38:42.035000000 -0400
+++ overload.hpp 2008-05-16 15:55:33.261000000 -0400
@@ -37,7 +37,7 @@
     template <typename Base, typename Sig>
     struct function_holder : Base
     {
- typedef boost::function<Sig> boost_fun_type;
+ typedef polymorphic_function<Sig> boost_fun_type;

         boost_fun_type boost_fun;

@@ -163,6 +163,25 @@

 namespace boost { namespace multi_signature_function { namespace detail {

+ template <typename Base, typename Sig>
+ struct function_caller : function_holder<Base, Sig>
+ {
+ typename boost::result_of<boost_fun_type()>::type
+ operator()() { return this->boost_fun(); }
+
+ template <typename A0>
+ typename boost::result_of<boost_fun_type(A0)>::type
+ operator()(A0 a0) { return this->boost_fun(a0); }
+
+ template <typename A0, typename A1>
+ typename boost::result_of<boost_fun_type(A0,A1)>::type
+ operator()(A0 a0, A1 a1) { return this->boost_fun(a0, a1); }
+
+ template <typename A0, typename A1, typename A2>
+ typename boost::result_of<boost_fun_type(A0,A1,A2)>::type
+ operator()(A0 a0, A1 a1, A2 a2) { return this->boost_fun(a0, a1, a2); }
+ };
+
     /* Start of argument arity dependant part
      *
      * Define one function_caller specialization each signature arity to

I'm sure there's more to it, but that gives you an idea. Of course,
the first thing you need is an nary version of polymorphic_function.
If you're serious about this and that's the direction you'd like to go
in, I'd be glad to start working on it tomorrow!

Daniel Walker


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