Boost logo

Boost :

From: Doug Gregor (gregod_at_[hidden])
Date: 2001-04-10 07:55:49


On Tuesday 10 April 2001 05:00, you 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.

It is type-safe, however. All of the type information is encapsulated in the
functions used for invoking and/or managing (cloning and deleting) the
function object pointer/function pointer.

In any case, what definition of type-strictness are you using? In prior
discussions it was agreed that it is desirable that the target function
signatures need not match exactly - they need only be callable giving the
arguments and required return type. This also should not be an explicit
conversion because it is too hard on the user. This would get annoying:

static int f(long, long);
any_function<int, int, int> foo;
foo = any_function_adaptor<int, int, int>(&f);

> * the implementation is particularly optimized for speed.
> Mine for simplicity and extendibility. (I actually never
> intended to use it as e.g. predicate to std algorithms)
>
> I think I can came come up with some more, well, design
>
> differences. Still, I'll comment on a view things:
> > > > ------------------------------
> > > > Interface summary:
> > > >
> > > > 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.
>
> So I simply think doing it this way is wrong, but hey,
> that's just me...

I mentioned a possible resolution to this that I intend to try soon. The
policy/trait/etc classes would use named parameters and a generative
interface. The crux of the idea is that the above would be written as:

function<void, int>::nothrow instance;

The underlying implementation would (possibly) be based on function0,
function1, ... classes that do the actual work.

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

A normal function pointer would cause a segmentation violation, so I still
think it is reasonable to throw. However, this could be a matter of policy.

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

This depends on which 'overhead' you are refering to. The run-time overhead
difference between the virtual function approach and the approach taken in
any_function is negligible. However, having a virtual function requires the
compiler to emit type information. Karl Nelson performed some size tests on
an older version of any_function (Nov. or Dec. 2000, I believe) vs. libsigc++
(which does not use virtual functions) -the virtual function implementation
was much larger. When there are many callbacks/any_functions in the system,
it adds up very quickly. This is the reason for the obscured approach in
any_function.

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

This was also debated at length. There was an overall preference for the
cloning.

> > I'd love to see the actual implementation
> > so I could better evaluate it, though.
>
> I'll have it uploaded to the boost file area.

Link?

        Doug


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