Boost logo

Boost :

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


> > I really don't agree with this.
>
> It's good to hear counter arguments.

Always happy to provide argument fodder. :-)

  
> > 1) All of the classes written to
> > hide the extra arguments inevitably show up when an error occures
> > thus spitting something like this to the user...
> >
> > an error has occured in class
> callback<void,int,float,unused,unused,unused>
> >
> > Thus it really isn't transparent to the user.
> >
> > (See the horrible errors spit by basic_string as to how annoying
> this
> > can be.)
>
> I don't find the errors to be "horrible" or unreadable. Nor do I
> think it's bad that the "unused" types appear in the error text. The
> point isn't to completely hide the details, it's to make useage
> simpler and more intuitive.

Well, given errors like

  error no match for callback2<void, int, int>& operator =(
     callback2<void, int, int>, void (*)(int) );
  possible matches are
    callback2<void, int, int>& callback2::<void,int,int>::operator =
      (void (*)(int,int) );
   ...

verses what an unnumbered callback will yield, I generally
feel that numbered callbacks yield far better clarity of errors.
(Not that this is a reason to not use unnumbered ones, just
that the argument unnumbered improves clarity isn't really that
clear cut.)

> > 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) { ... }
> > ...
> > };
>
> I'm not sure that "code clarity" is impacted by much, and with proper
> documentation and code comments I think this a non-issue.

Well, for users of that horrible VC++, that would likely impact
the clarity considerably after all the code extractor would still
think all of these overloads were there.

This is again a minor issue. But again this detracts from the
argument that a unified class is more clear when the user is likely
less sure of what the above means. (I know that the extra operators
won't get implemented unless they are used, but that is subtle.)

> > 3) It only saves one charactor. :-)
>
> The point isn't the amount of typing, but rather of intuitive useage
> (which is admittedly debatable). Repeating information generally
> just makes comprehension more difficult. This is made worse by the
> fact that callback1 takes 2 template parameters. For someone
> unfamiliar with the class this could lead to confusion.

I generally find it added clarity because the user can see this

  callback4< MyClass, Centeral_Dispatch&, list<int>&, const char**,
    bool>

and grab that it requires 4 arguments more easily. (as opposed
to counting them.)

> > The same things could be achieved
> > 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 {
> > ... // specific stuff
> > R operator()(P1 p1);
> > };
>
> I assume you forgot the derivation in the above?

Yes.
 
> This does not illustrate "the same things being achieved".

Same in that you can give a base set of functionality without
having to implement an empty(), clear() and all the rest in
each callback# class. This also makes the typedefs of
what sort of function the thing takes a lot less meta programming.

Ie.

  template <class R, class P1, class P2>
  class callback2
    {
      typedef R (*function_type)(P1,P2);
      Self& operator=(function_type f);
      ...
   };

 
> > Which insidentally extracts with VC++ and other code browsers
> > to make more sense.
>
> I don't follow what you're saying here.

See comment in other reply to this point.

  
> > 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.)
> >
> > 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!)
>
> Well, we weren't discussing having any optional template parameters.
> There's alternatives to this idiom that could be applied if it were
> deemed appropriate to have such optional parameters. Of the
> arguments you've given, this is the most persuasive, but I'm not sure
> it's enough to sway my opinion.

This is the key argument of why the signal/slot system of sigc++ must
have the number as they require this to add the Marshal trait. Of
course I could have coded it as Signal# with Slot (no number) but
then I was leaving the option open for Slots to have traits as well.

Obviously, some consideration of whether optional template parameters
should come into this discussion as this feature could easily decide
one way or the other.

> > 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.
>
> The "macro generator" can just generate a new class with more
> optional parameters. I don't think this is an issue either.

Not so. See previous reply. Attempting to expand the
unified approach prevents linking. Thus if you use the
unified approach you pretty much have ruled out the use of
callbacks in a library.

  
> > 6) It encourages gobs of template land meta programming which can
> slow
> > some compilers to a grinding halt and is some of the least portable
> code
> > back to the weaker compilers. ( Read we will get something which
> > VC++ likes but is kludgy as all hell.) Then again implementing
> > many levels of class can do the same.
>
> This may be a problem today (in this case, I don't think it is, since
> the code in question works fine with VC++, the weakest of compilers,
> and is not very "kludgy" IMHO), but tomorrow it could be a non-
> issue. For that reason, I'm not sure this is a valid argument either.

Okay, well here I have to admit it is weak. Technically SigC
which uses numbered arguments can port all the way back to gcc 2.7.2
which is a far weaker compiler than VC++. Though boost is really
only interested in largely standard compilers. I still
think that numbered callbacks reduce the problems of portablity considerably.

> > For all of these reasons SigC went with numbered classes.
> > I hope you consider carefully these reasons before chosing to do
> > differently.
>
> I should note that this practice is not something new. The Lambda
> tuples use this idiom for much the same reasons that I would prefer
> it to be used here. The one argument you made about optional
> parameters being added on the end is the only one that might sway me
> to think otherwise, but currently we don't have any such parameters.
> So, I'm not convinced this is not the way to go.

If you choose to use unnumbered you are largely ruling out
uniformity on a future signal/slot library. The linking and
optional arguments also need to be thought through. Not
that this necessarily convince you, but at least make sure these
problems are well though out now.

Hope it helps.

--Karl


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