Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2000-11-23 10:21:00


On Wed, 22 Nov 2000 12:29:01 -0800
Karl Nelson <kenelson_at_[hidden]> wrote:

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

I've tried it in my own implementation and it does work. There's some disagreement as to whether or not this is a better solution than listing all operator() overloads in the callback class itself. In any case, both methods have been tried and have worked, and it is a case of clarity of error messages vs. clarity of code - pulling an operator() from a class in boost::details can be seen as confusing.
   
>
> > > 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) {...}
> };

I'll try to upload a version where this is not the case, to illustrate that it is possible, but it will take me a day or so (holidays tend to rapidly decrease programming time available).

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

Nobody seems to disagree that overloading functions or operators is a problem, and I don't quite see that overloading class names is a problem, either. In any case, default parameters allow overloading class names.

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

The version of callback<> using partial specialization is very clean. It's the lack of partial specialization that uglifies the code, though the result is still very readable IMHO;
 
>
> > > 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.

I understand your point, but I would like to assess how often these tools are actually used (instead of using documentation, or looking directly at the callback header) before I can weigh this argument. I personally can't say that I've used this type of tool more than once, so it is a nonissue for me personally.

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

Throw an exception. Other options include a "default" callback function which can be supplied in the constructor to which the callback will revert if no other assignment has been made.

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

It's a matter of taste, but the default number of arguments for a callback will be between 7 and 10. I highly doubt that applications will move beyond that. If they do, I would argue that it is a good reason to redesign the function and/or group the arguments.

> Basically, only numbered callbacks have
> - very clear and clean code which the user may understand by simply
> reading it.

When partial specialization becomes reasonably portable, callback<> will have this as well. For now, the code is slightly less readable.

> - expandable with macros without breaking linking

Again, I don't see this as a problem.

> - allow optional arguments to solve problems like adding traits

This is the biggest issue with callback<> thus far. I'd like to find some more examples of traits that we'd like to include.
 
> 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

        Doug Gregor


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