Boost logo

Boost :

From: Karl Nelson (kenelson_at_[hidden])
Date: 2000-11-22 15:29:01


[....]
> > 2) Second it makes for really poor code clarity as the class will
> > have to implement all of the operator() even through it uses
> > only one of them...
> >
> > class callback {
> > ...
> > return_type operator()() { ... }
> > return_type operator()(P1 p1) { ... }
> > return_type operator()(P1 p1,P2 p2) { ... }
> > return_type operator()(P1 p1,P2 p2,P3 p3) { ... }
> > return_type operator()(P1 p1,P2 p2,P3 p3,P3 p3) { ... }
> > ...
> > };
>
> The callback class doesn't have to implement all operator() overloads, though the alternative requires deriving from a base class which has the proper overload. The first option is slightly more readable for the "callback" class itself, the second option gives the best error messages (since only one operator() exists).

I don't believe that deriving directly will work, or at least I haven't
seen one without the construction I showed above.

  
 
> > 3) It only saves one charactor. :-) The same things could be achieved
>
> Yes, it is only one character, but it's an extra parameter that is implied. How would a programmer new to the callback library interpret callback0, callback1, etc? Omitting the number makes all callbacks look the same, and excludes extra information which could be confusing.

I never have seen this as a valid point. The argument seems to
be that by hiding number of arguments it greatly simplifies the code
and thus makes things less confusing. However, if I look at
this class which is unified it looks like

  class callback
    {
      R operator()() { }
      R operator()(P1 p1) {...}
    };
With a large amount of meta programming thrown in to make things
compile. Looking at all the meta programming and unimplemented
functions is more likely to confuse the user than an extra argument
they can glean from an example in a minute.

If I have as set of classes I hope that each will have a distinct name.
As I frequently use the optional arguments to mean something else
this attempting hide that these are separate concepts seems pretty off.

How do I read this
  Signal2<void, int, int, MyIntMarsh> sig1;
  Signal1<void, SomeClass> sig2;

Specifying the number of arguments the callback takes is
really something I think the users could live with. C++ wasn't
really intended to have overloaded class names, that is what
hiding the number of arguments is trying to do.

The implementation of callback#<> is usually cleaner though
slightly longer in terms of headers.

   
> > with a base class with all the common stuff and then derive it to
> > add the arguments.
> >
> > class callback { // not a template
> > bool empty();
> > void clear();
> > ... // common stuff
> > };
> >
> > template <class R, class P1 >
> > class callback1 {

Doh forgot to derive....

class callback1: public callback {

> > ... // specific stuff
> > R operator()(P1 p1);
> > };
> >
> > Which insidentally extracts with VC++ and other code browsers
> > to make more sense.

 
> I apologize, but I'm not seeing how this would be achieved.

In VC++, it extracts the code to make a code browser. The
code browser shows classes like

   Callback0<R>
   Callback1<R,P1>
   ...

Clicking on the classes shows the methods in that class. Which in
this case would be

  Callback0<R>
    bool empty() const;
    void clear();
    R operator()();
    Callback0<R>();
    Callback0<R>(const Callback0<R>&)
    ...

and so forth.

    
> > 4) It bars adding additional optional arguments which may be
> > useful. Ie, since the callback may be unconnected how do we specify
> > what to return in case of there is nothing connected.
> >
> > imagine this
> >
> > template <class R, class P1, class P2, class RT=trait<R> >
> > class callback2
> > {
> > R operator()(P1 p1,P2 p2)
> > { if (empty) return R(RT::default_value); ... }
> > };
> >
> > (This also saves the default constructor argument.
> > SigC uses this for a number of classes. (Though I
> > avoid it for slots as then copy gets fun.)
>
> Default constructible isn't be a problem in general, it was just a result of that implementation.

It is a question which many people have asked in sigc. How
do I get a slot or signal to return some value if there isn't
something connected?

If you do nothing the return type must be default constructable.
If you use a trait for this then that value is fixed for all
callback types. This is not always what the user wants. If you place
it as a template trait (ala string), then you can't use these
wierd overloaded types.

 
> > Without the number you have to write something like...
> >
> > callback<void,int,unused,unused,unused,my_return_trait>
> > ^^^^^^^
> > (how many should I type? depends on the version of the library!)
>
> This is most definitely a problem, and the major drawback of this approach.

This also prevents the use of a macro generator, because if one
peice of code uses templates with 4 arguments and another uses
a callback with 5, they can't link. Thus you are stuck at the
number implemented in the library.

Gtk-- uses up to 6 parameters with callbacks. I have seen code use
more.

> > 5) It caps the number of callback which limits the application
> > somewaht. That is with the callback#, the user can always call
> > the macro generator to produce another set of classes for a
> > larger number of parameters.
>
> Not necessarily. In my original event library, I used this technique and had scripts to generate the event classes. The only reason this has yet to be done for any of the current callback library candidates is that it is less manageable in the prototype stage.

However, this won't link unless all the code is compiled against the
same number.

In the case of numbered callbacks this isn't true. Because
Callback6 can be introduced without changing the definition of
Callback2. In the case of unnumbered callbacks we get...

  template <class R,class P1, class P2, class P3>
  class callback
    {
      ...
    };

and then in other code they needed more so they call the macro and get

  template <class R,class P1, class P2, class P3,class P4, class P5, class P6>
  class callback
    {
      ...
    };

Suddenly they don't link. Since callbacks are a frequent way for
libraries to add additional functionality later, something which
prevents linking is a very bad idea.

Basically, only numbered callbacks have
  - very clear and clean code which the user may understand by simply
    reading it.
  - expandable with macros without breaking linking
  - allow optional arguments to solve problems like adding traits

This is counter weighed against unnumbered callbacks having
  - only one class (though possibly not very clear)
  - some minor reduction of class names giving a marginally more
    consistent look
 
--Karl


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