Boost logo

Boost :

From: Karl Nelson (kenelson_at_[hidden])
Date: 2001-12-27 16:49:56


> > a dependency with clean up info which can be removed at a later point.
> > It should not be specific to signal/slots. I can find no mention
> > of the mechanism anywhere in your docs.
>
> 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.

 
> > > > If you construct a functor from a trackable element
> > > > using any of those other libraries you name, you lose the ablity to
> > > > track the object.
> > >
> > > If they do not implement visit_each, yes.
> >
> > Then your argument about how SigC++ would require modification
> > of lambda would require modification is pointless. Every library
> > in boost which needs your tracking will need your tracking
> > modification as well!
>
> 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.
 

> > > std::map<boost::any, boost::connection,
> > > boost::function2<bool, boost::any, boost::any> > named_slots;
> >
> > Naming of slots as you have given above seems less like a necessary
> > feature of a signal than some extra bloat tacked on. What likely
> > use will it have and how often do you see it employed?
>
> It's use is to avoid holding onto connection objects when we want to deal
> with the connection lifetime ourselves. We then name each of our connections
> and use that name to disconnect. The alternative would be to have a data
> member for each connection, or a container of connections for the object;
> both of which are annoying to maintain. connect/disconnect is just like
> new/delete.

>
> > > The difference is that a "connect" method might look like:
> > > template<typename Slot> void connect(const Slot& slot);
> > >
> > > instead of:
> > >
> > > void connect(const slot_type& slot);
> >
> > There is a huge difference between those two statements. slot_type
> > can be in a virtual function, passed up or down the function stack
> > and easily stored on a list<slot_type>.
>
> ... and we can store an arbitrary slot in a list<boost::function<...> >. I'm
> still not seeing the huge difference.

Then try to compile these...

  template<typename Slot> virtual void connect(const Slot& slot);
  virtual void connect(const slot_type& slot);

Concrete slots are needed for many library types.

[...]
> Compare your own hand-coded sort on your own hand-coded array-like structure
> to std::sort with std::vector over the data type you choose. Use a real
> compiler, with a real inliner, and you won't see a difference.

Sort is a different problem.

> 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.
 
 
> > > I'm trying to generalize the wheel - take away the reliance on concrete
> > > classes and specific implementation behavior, and look at signals & slots
> > > from the abstract point of view.
> >
> > I don't know how ell this has been acheived. Unlike list, vector,
> > where I can see alternative implementations your code seems pretty much
> > one way. Though SigC++ is hardly a good model of allowing alternative
> > implementations.
>
> One needs to have a specification for alternative implementations to be
> comparable. list and vector have had agreed-upon specifications for a long
> time, but signals & slots have not.
>
> > > It's an orthogonal set conceptually, yes, so why is it in a single
> > > library? By orthogonal I mean that each piece of the puzzle is a separate
> > > entity that performs a single, well-defined task. The pieces can be
> > > assembled to create a unified signals & slots system. If one piece
> > > breaks, or some genius comes along with a better way to perform one of
> > > those tasks, we can just swap out the old piece and replace it with the
> > > new piece.
> >
> > In the end it must be a single library. To do so in the generalized
> > fashion just hasn't made a very practical industrial library. But
>
> 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. 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.

> > Containers should be standardized as they are implemented over and over
> > throughout current industry. Streams same. Smart pointers, definitly.
> > Signals/slots probably at this point given the number of implementations
> > now out there. Lambda? I have no experience which libraries like that
> > and how much use they get.
>
> They're gaining acceptance, but are somewhat stunted by the ubiquitousness of
> broken compilers. The C++ community is moving deeper into the so-called
> "STL-style" programming paradigm, which brings many aspects of functional
> programming into the fold. Look at, for instance, the Spirit parser to see
> how such function objects can be used to build Yacc-like grammars with
> semantic actions that can build abstract syntax trees on the fly. This type
> of functional composition is gaining momentum (there are often questions
> about it on comp.lang.c++.moderated and comp.std.c++).

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.

> > > These aren't covered by any binding library, because they are the result
> > > of merging the argument binding and signals & slots libraries. They can
> > > easily be added: class_slot is just a function object adaptor for which
> > > visit_each is specialized to do nothing, and auto_disconnect can be
> > > implemented as an adaptor containing a call count and a connection.
> >
> > If you didn't know these adaptors existed and you are forward thinking,
> > then how long did you look at existing practice??
>
> This is petty. There are an infinite number of adaptors one could implement,
> and it would be fruitless for us to name adaptors that may be missing from
> library X or Y.

Admittedly petty.

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

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.

  class A
    {
      A(const A&);
      A(A&);
    };

  A* a=generate<A>(a1);

Thus you need to specialize for const T& and T&. Stretch this
to 8 arguments and you need 256 overloads. I thought the construct
wonderful until I discovered it simply could not handle these
things gracefully. This is why sigc++ has went to strong typing.

> Here's my core disagreement with SigC++ design. It's not about which adaptor
> sets are used, or whether named slot connections exist, or even about
> exception safety considerations - it's about the interface. There are two
> areas for which I would like to see extensibility:
> - Functional composition to construct slots
> - Choice of callback types
>
> 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.

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.

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

--Karl


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