Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-12-27 16:02:45


On Thursday 27 December 2001 12:40 pm, you wrote:
> > Not dropped at all. The numbered variants exist for binary compatibility
> > and to allow users to reduce the amount of code that needs to be compiled
> > (e.g., by including only the header for a signal/function with 2
> > arguments). The unnumbered classes still exist, and only day the
> > committee may decide that template typedefs should be added to the
> > language and then the binary compatibility argument will no longer hold.
> > For now, the best we can do is have signal (or function) derive the
> > appropriate signalN (or functionN) class and reimplement some
> > functionality that won't be found because of lookup rules.
>
> This was a major argument cited when I last proposed SigC++ and
> the argument I gave at the time was basically what you just gave
> above. Thus that objection has been dropped.

> The visit each mechanism seems grossly specific to signals in your
> implementation.

How so? visit_each applies each contained subobject to a visitor. If you're
looking at bound_objects_visitor in boost/signals/trackable.hpp, then yes -
that's intentionally specific to Signals, but it's just a visitor to be used
with visit_each.

> trackable objects shoiuld have the ablity to place
> 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.

> Can you describe clearly how some alternate implementation of a
> signal, or just a simple slot which binds to object which is freed
> would plug into your mechanism?

For an alternate signal implementation:
  - It should derive "trackable" so that it can be used in a slot safely
  - For each slot connection:
    + It should create a basic_connection and fill in the signal-related
fields (pointer to the signal, pointer to extra signal data associated with
this connection, and a pointer to a static/free disconnect notification
function).
    + It can use visit_each to discover trackable objects in a slot.

This is undocumented because it is an implementation detail that may very
well change if a good tracking library comes along.

I'm having a hard time decoding the second part of the question, I'll guess
that you mean "what happens if you create your own function object/adaptor to
be used as a slot," i.e.,

template<typename F>
struct add_two_to_result {
  int operator()() { return f() + 2; }
  F f;
};

Then one needs a visit_each function template to take care of the rest:

template<typename V, typename F>
void visit_each(V& v, const add_two_to_result<F>& attr, int)
{
  v(attr.f);
}

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

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

> > > Growing each concept separately, often creates a large set of not
> > > entirely integrated peices which in turn lead to horrible bloat and
> > > inefficency.
> >
> > If the underlying concepts are poorly specified, then perhaps bloat and
> > inefficiency would be the result. Callback and function object concepts
> > are quite trivial, and have been used efficiently for a quite a while.
> > Why would they fail here?
>
> Could you cite a specific large program implemented with these concpets
> and give relative performance measures you used to measure efficency?

std::sort(RandomAccessIterator, RandomAccessIterator, Compare)

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.

It sounds like you're claiming that we cannot plug two generic components
together and come back with an efficient solution to our problem.

> > Existing practice is an important thing for sure, but standards bodies
> > should also be forward-thinking: will the restrictions of the "existing
> > practice" limit users in the future? What's the point of having a great
> > binding library if you can't use it with signals & slots?
>
> How can one be forward thinking when one does not look at the past??
> You have skimmed the sigc++ library, but not used it in any apprecable
> way. You have not spoken with the users on the SigC++ list (300 of
> them) and asked what things work and what things don't in SigC++.

I do follow the sigc++ mailing list to some extent, have read the
documentation and used the library. Have I written a large application using
sigc++? No, I haven't, and I don't believe I'll have the opportunity to in
the near future.

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

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

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

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.

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.

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


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