Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-12-27 18:35:31


On Thursday 27 December 2001 04:49 pm, you wrote:
> > Agreed, but we don't have such a library nor do we have a concept
> > specification that encompasses such behavior. The top-level signal/slot
> > interfaces would remain the same, but a Tracking library of some sort
> > could help us decouple that aspect for signals & slots. I have yet to see
> > anyone express an interest in writing or using a tracking library.
>
> Okay then there is more a place to start. What should be the specification
> for a trackable object and how should one make it so a class
> like functor1<int,int> would release resources when its tracked object
> dies automatically without resulting in a huge dependency mess.

Here's a first shot:

A type trait is_trackable so that we can determine if a given object meets
the Trackable concept requirements. For an object x of type X to meet the
Trackable requirements, and let c be a connection object of some sort. I
would say that we need:

A trait to determine if it is trackable, i.e.,
  is_trackable<X>::value
will be true if X is a trackable type and false otherwise.

A way to connect trackable objects, i.e.,
  make_connection(x, c)
This would connect x to c, so that if c is disconnected by anyone, x is
notified of this. It also adds c to x's internal list of connections, so if x
is destructed then c will be disconnected.

An implementation sketch:
  - a "connection" object just holds a list of commands to disconnect
objects. Basic implementation would be a thin wrapper over:
  boost::shared_ptr<std::list<boost::function<void> > >
  
  - a "trackable" subclass that users can derive from. It essentially
contains a:
  std::list<connection>

  - make_connection on a trackable object would:
      + add the connection to the list of connections
      + create a function object that would be able to remove the connection
from the list of connections, and add it to the list in the connection object.

> > visit_each is a reusable discovery tool, and it is non-invasive.
>
> Then you definitely need more examples in the documentation prior to
> review. No one would discern that from the current code without
> analyzing it for hours.

It'll be there.

> > It sounds like you're claiming that we cannot plug two generic components
> > together and come back with an efficient solution to our problem.
>
> Sometimes you can't. If you want to do tracking cleanly such that the
> class is opaque and not so transparent a new template needs to be
> built for each specific set of adaptors, then peicemealing generic
> components won't likely do it.

We'll just have to do it, then, or show that it isn't possible.

> > Is the C++ standard library a practical industrial library? I would say
> > "yes", but I would not say that it is a "single library" at all. It is
> > made up of many pieces - algorithms, function objects, data structures -
> > all separate little libraries of their own that can be combined in
> > powerful ways.
>
> This isn't true though. Streams library doesn't really play all that
> nice with the strings library. None of the interfaces take strings
> in native form.

Streams also isn't at all a concept-based framework. I meant to exclude
streams, so I should have said "C++ standard _template_ library."

> Some people like Qt would likely argue that C++
> standard library isn't practical for cross platform devel. Though
> fortunately, I will give you that point as I generally thing it
> reasonably useful.

This is hardly a fault of the library itself, or the standard, but of
implementors dragging their feet and users persisting in choosing compiler
and library vendors that drag their feet.

> The point remains should they be a standard at this point. Is it
> not entirely likely that there could be wildly different implementations.
> Should not boost pick and choose once something gets to be industry
> practice rather than trying to press something into industry practice
> by introducing some piece as of a standard.

How often is a new paradigm adopted as industry practice? I think STL is
still our best example here - that was not industry practice, and still isn't
industry practice, but it was accepted because it was a great leap forward in
library design.

> > I would think that greater importance would be placed on the question of
> > how easily one can create new adaptors, and what pitfalls are inherent in
> > the creation process.
> >
> > > You missed the point entirely. How is adaptor supposed to know
> > > what method to pull out of functor? Does it simply implement
> > > every operator()(....) for arguments 1 through 10?
> >
> > Yes, it's common practice amongst binding libraries.
>
> Then how sould such implementations choose the correct implementation
> of a function when overloaded to references and constant references?

I didn't say I liked the practice :)
Seriously, the fact that there isn't a good way to create pass-through
function objects without knowing the exact types of the arguments is a big
problem. I believe that people are looking into core language fixes to make
pass-through function objects possible.

> A very old design of mine was trying such a trick to make a new-like
> object. It simply passed the arguments to new T(....) and then
> called a function on the pointer. The problem aways became that it
> would choose the wrong function when overloading came.

The only real problem is with non-const references. I, personally, have never
run into the problem in practice.

> > The first enables the advances in the area of functional composition to
> > be immediately useful within a signals & slots framework. At some point
> > one library will likely dominate the functional composition arena, and
> > users will need to learn only one functional composition vocabulary that
> > they can apply to standard algorithms, callbacks, signals & slot,
> > semantic actions in parsing, etc. The vocabulary _must_ be the same for
> > all types of libraries or
> >
> > we will not be serving the user's needs.
>
> I am not sure that can't already be done in the sigc++ design. Part of
> the constraints that sigc++ work on todays compilers prevents much
> exploration of that type of design. Though I do see that it should at some
> point be worked in.

Good, but why would you compromise a library's usability because of old,
broken compilers? Perhaps it is helpful in the establishment of "existing
practice" if every compiler can handle the code, but it also forces the
"existing practice" to be behind the "best-known" practice.

> This portion is adaptable... After what difference is it to you if
>
> bind(foo, _1, 1) /* where foo can be anything */
>
> returns a functor2<int,int,int> (concrete type) or a
> _bi::internal::bind_type< some_functiontype<int,int>, int,int.....>
> (chain of abstract types.). Yes, it means that "good" adaptors
> will have to be written to return the generic component. But at the
> same time it controls the types the user needs to worry about.

To return a function<int, int, int>, one needs to know the exact argument and
return types of foo; this isn't so easy if foo is a function object with
multiple operator()'s. The difference, to me, is that returning a concrete
type means two things: we're erasing type information (since the actual
object that is called has a type that can't b exported through the
function<int, int, int>) and we're going to pay a price in efficiency if we
don't need the type erasure. bind(foo, _1, 1) passed directly to
std::transform is more efficient if it does not return a concrete type; more
flexible, too, with regard to multiple or templated operator()'s. If there is
going to be type erasure at some point in the system (which there must be),
do it as late as possible so we have the most chance to use the information.

> > The second is merely for user customization and optimization. A fully
> > general callback class is not necessary for all applications, and carries
> > with it more overhead than a simple callback class (e.g., a function
> > pointer).
>
> This point I disagree strongly on. The full general callback
> which can be concretely typed and used universally, carries some
> overhead, but that overhead caps out rather quickly in a large
> program. Usually around 64 bytes of dynamic and a very small amount
> of static. The peice meal approach will likely go well over that.

Concrete typing has to exist at some point, of course. But to say that the
overhead will be higher just because the concrete type itself is a template
parameter is misguided. What's the real difference between
std::list<my_callback_type> and std::list<your_callback_type> if
my_callback_type and your_callback_type are roughly equivalent in efficiency
and size?

        Doug


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