Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-04-10 09:22:26


--- In boost_at_y..., "Geurt Vos" <G.Vos_at_r...> wrote:
>
> I've been studying the code of any_function a bit, and came to
> the conclusion that at least the goals are like, quite different.
> For instance:
> * it's not really type-strict. My implementation is.
> * the implementation is particularly optimized for speed.

Others have replied, and I've not read their responses yet, but
hopefully they pointed out that neither of your bullets above is
accurate.

> > > > template<typename Result, typename Arg1, typename Arg2,
> > > > ..., typename ArgN>
> > >
> > > The main problem with this approach is that it isn't extendable.
> > > I mean, how many parameters to support? as I view the code, it's
> > > implemented for up to 6 parameters, which is in my opinion
enough
> > > (my 'previous version' had this approach for up to 5), but I
> > > guarantee you someone will require 7. The largest implementation
> > > I've seen so far is Alexandrescu's Functor class: a massive 15
> > > parameters (the one in Modern C++ Design).
> >
> > This argument has been made on this list in the past. Both sides
> > have valid arguments, IMHO, making this a very sticky design
decision
> > to deal with.
> >
>
> Hmm, the main problem with the current approach is that there's
> _no_ way to extended it with e.g. policies. For instance, whether
> or not to throw when no function attached. I mean, that would mess
> up the default Argx declaration. I wouldn't want to declare it like:
>
> function<
> void,
> int,
> detail::unusable,
> detail::unusable,
> detail::unusable,
> detail::unusable,
> nothrow
> > instance;
>
> Apart from undesirable, it now is impossible to add a new
> (a 7th) parameter.

There are other arguments against this approach as well. I'm not
sure that this topic is going to be resolved any time real soon,
though it sounds like we're making some progress towards a compromise.
 
> > > > bool empty() const; // true if no target
> > > > operator const void*() const; // evaluates true if non-
empty
> > > >
> > >
> > > Again, I'm not certain whether both are required.
> > > I personally would lose the operator...
> >
> > The operator allows for seemless replacement with traditional C
> > style "callbacks". I'm not at all sure that I'd lose the
operator
> > here.
> >
>
> You're probably right. I think it actually makes more sense
> to remove 'empty()', because function pointer don't have
> member functions, and empty() doesn't really add anything.
> Besides that one could mistakenly assume it can contain more
> than one function pointer (as with regular containers).

empty() just fits well with clear(). The any_function concept is
basically a container of one.
 
> > I also think it would be more
> > appropriate to throw than to return Result(),
>
> It really depends on what 'function' is used for. When it's used
> as e.g. predicate to whatever algorithm, it should throw. When
> it's used to hold callback functions for an event driven system,
> particularly when the return type is 'void', it would make sense
> to simply ignore the call. Then again, the first one might be a
> stronger argument. I mean, the 'even manager' or whatever could
> of course simply perform a check before calling the function.

Actually, I'd be in favor of having operator() not do a check and add
a method call() that does a check. This would be inline with operator
[] and at() for std::vector, giving the programmer the best of both
worlds depending on their own needs. However, I still think the
checked version needs to throw.
 
> > and can think of no
> > valid reason to ever use validate=false.
> >
>
> Efficiency. If it's used as predicate, it's more efficient to
> check ones before calling the algorithm, than check on every
> call.

That's why the approach I gave above would be a good idea. Changing
this behavior based on a template parameter just doesn't make sense
to me.
 
> For my implementation, I'll probably un-decide this one, because
> also provided is a non-checking function. Maybe I'll throw out
> the entire auto-checking system as a whole - make it an assertion.

Again, I like the approach used for std::vector and would suggest
using it here as well.
 
> > > Callback will inherit from CxParam<>, and CxParam will
> > > inherit from CallbackHolder<StoragexParam>. StoragexParam
> > > is a class template, which has two nested classes: one for
> > > the interface (with only operator() as virtual func) and
> > > one for storing a pointer to the actual function (which is
> > > either a function, function object or a function member).
> >
> > Ahh... this helps to figure out how you eliminated the fixed
number
> > of parameters, but I'd have to see all of the implementation to
fully
> > comprehend this. However, you've added in virtual methods here,
> > which was specifically avoided in any_function (again, read the
> > documentation). It's being used in a slightly different location
so
> > may not be prone to the same problems being avoided here, but it
> > still throws up red flags for me.
>
> It's a flexibility & extendibility vs. efficiency trade of. I could
> create an implementation similar to how it's solved for
any_function,
> but I still feel it's close to a hack, and at least harder to
> understand. From a user point of view it won't really harm type
> safety, though, and of course, there are easy ways to make it as
> type strict as my implementation is.

Efficiency is *very* important here. It's probably the most
important aspect of this concept. Also, I still contend that you're
wrong about type safety issues here.
 
> Another reason for choosing the virtual function approach is because
> 99% of the time the overhead generated by it is negligible. I don't
> think designing out this overhead will really have that much effect,
> unless you're really pushing the limits. In that case, I recommend
> using a faster CPU :), because it's highly doubtful that if it were
> designed out you won't run into performance problems elsewhere...

The overhead is in a lot more than just speed. I'd suggest you read
the list archives on this subject paying particular attention to the
libsigc++ discussions. It was found in real world applications that
virtual methods cause serious overhead issues for this particular
concept.
   
> > > The two criteria I mentioned at the beginning of this mail
> > > I think have been met quite nicely with this implementation.
> > > I can make it even easier by using virtual inheritance at
> > > two place, but I didn't want to touch that yet :)
> >
> > It appears that you've also made the interface more complex to
use,
> > which is a bad thing IMHO.
>
> On the contrary. It's basically the same. The one thing you could
> say is that it's scattered over three classes. Other than that, the
> only member function is 'IsAttached()'. There's of course one
> additional constructor for passing a memfunc/instance pair (not
> std::pair!), and for assignment only copy assignment is implemented
> (and required).
>
> If you look at instantiation, my implementation adds an
> indirection, which might be annoying at first. The good
> thing about it is that you don't have to count the number
> of parameters used because it's in the name.
>
> One last thing: assigning a function will cause memory allocation.
> copy construction/assignment doesn't, because reference counting
> is used...

You *really* need to read the list archives at this point. We went
down the ref-counting road, and I was one of the biggest supporters
of this initially. I was convinced by the discussion that this is a
bad idea, and believe you will be too if you read the archives.

Bill Kempf


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