Boost logo

Boost :

From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2008-05-12 13:39:32


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
> <gpderetta_at_[hidden]> wrote:
> >
> > Well my idea was that an unconstrained template function object would be
> > bound to all internal boost::functions
>
> You mean something like this
>
> template<int> struct end_seq_tag { typedef int type; };
> template<> struct end_seq_tag<-1> { typedef char type; };
>
> template<int n, typename T>
> void assign(T const& t, int*)
> {
> /* first convert to boost::function then add */
>
> *this += boost::function< at_c<n, Signatures>::type >(t);
>
> assign<n - 1>(t, (end_seq_tag<n - 1>::type*)0); /* iterate */
> }
>
> template<int n, typename T>
> void assign(T const&, char*) {} /* end condition here */
>
> template<typename T>
> function& operator=(T const& t)
> {
> assign<signatures_size - 1>(t, (end_seq_tag<signatures_size - 1>::type*)0);
> return *this;
> }
>
> I have written fast, probably there is a better way, but just to show
> the idea. Is this what you mean ?

Not sure if the code would compile, but, yes, that would be the idea,
except for the fact that in my ideal world
MSF would have single copy of F.

>
> > I think that in your case, using
> > operator= is confusing because you are not really reassigning the MSF but
> > just adding an overload. What if operator= tries to bind all signatures at
> > once (and thus allows for boost::function like adaptability) and operator+=
> > (or an explicit add member function) is strict and allows to replace a single
> > overload?
>
> operator+= is a nice idea, like an explicit set() or add() method. I
> have been stick to operator=() just to follow boost::function API, but
> I agree in case of MSF this could be confusing...
>

Well, in boost::function, operator= replaces the content of the
function, which is
not true in MSF case. Operator+= might qualify as operator abuse, so
an explicit
set or add would be better (but personally have no strong opinion on this)

>
> >>
> >> Where boost.function naturally models a function, multi-signature
> >> boost::function naturally models an overload set
> >
> > Actually it is a bit more, because you can have different state for
> > each overload. I would need something in the middle:
> > a single polymorphic closure which can be accessed dynamically
> > with multiple objects.
> >
>
> Sorry but the above sentence does not parse for me. I have to think
> again on what you meant, sorry, but could you better explain what you
> mean ? It's my fault to be dumb ;-)
>

Not sure of what you cannot parse, so I'll try to be as explicitly as possible.

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

You can think of boost::function as a wrapper that erases the exact
type of the function object and
leaves only the signature. Dispatching to the correct type is done at
runtime (via virtual functions or, equivalently, pointer to
functions). boost::function works for every monomorphic function
object as long as it is CopyConstructible.

If the function object has state, it will be correctly stored in the
boost::fuction:

struct sum_with_double_and_print {
  typedef void result_type;
  void operator()(int i) { std::cout << i + state << '\n'; }
  sum_with_double_and_print(double state_) : state(state_) {}
  double state;
};

boost::function<void(int)> f = sum_with_duble_and_print(0.7);
f(10); // prints 10.7

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

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

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.

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"

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

[ BTW, by a quick at the code of MSF you do not seem
to implement the result_of protocol. You definitely should, as MSF is
definitely a polymorphic function object,]

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

Still your current implementation has a problem.

You can of course have statefull polymorphic function objects:

struct frob {
      template<class Sig> struct result;
      template<class T>
      struct result<frob(T)> {
            // this is technically not correct, it should be
decltype(int() + T())
            // which for now should use some kind of promotion traits.
             typedef T type;
     };

     template<class T>
     T operator()(T i) {
          return i + state++;
     }

     fromb(int state_) : state(state_) {}
     int state;
};

frob(10) x;
x(10); // returns 20
x(10); // returns 21
x(10); // returns 22
x(10.42); // returns 23.42
x(my_big_int(231211251414whatever)); // returns.. well, you got it right?

If I put frob in a MSF :

msf_function<int(int), double(double), my_big_int(my_big_int)> x = frob(10);

it has two downsides ...
1) it does N dynamic allocations, one for each signature.
2) it has N copies of 'frob::state', which for a small object as an
int it is not a problem, but in principle it can be wasteful

... and a serious problem:
As there are n different states, the increment is separate for each
type, and not for all of them:

x(10); // returns 20
x(10); // returns 21
x(10); // returns 22
x(10.42); // returns 20.42 !!!
x(my_big_int(10)); // returns (my_big_int)21 !

In practice MSF acts as N different boost::function objects with a
think type based dispatcher.
I can of course fix the problem by adding another layer of indirection:

struct frobp {
   // forward
   template<class S> struct result : frob::result<S> {};

   frob(int i) : p(new frob(i)) {}
   boost::shared_ptr<frob> p;

   template<class T> T operator()(T i) {
        return (*p) (i);
  }
}
msf_function<int(int), double(double), my_big_int(my_big_int)> x = frobp(10);

Which is ugly, verbose, and ineficient.
What I want is MSF to hold a *single* instance of my function object
and add to the dynamic dispatching layer
one operator() for each of the registered functions (whether this is
done via function pointers or virtual functions doesn't matter).

In practice this means that you cannot use boost::function for the
implementation of MSF (but you might reuse some
of its implementation).

Note that the old behaviour (one instance for every signature type),
can be implemented easily
on top of my preferred design, without loss of efficiency.

HTH,

-- 
gpd
>
>
>  Thanks
>  Marco
>  _______________________________________________
>  Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
>

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