enumeration interface for boost::signals

Does boost::signals have a public enumeration interface (like iterator concept) that can be used for accessing individual slots? There is some mention of it in the "Slot Call Iterator" section but not much detailed. For instance, what I'd like is to call a slot and depending on the result call some other slot. A related question is if I have an instance of boost::signals::connection, is it possible to get an access to the slot that this connection is referencing? Thanks, Eugene

On Sunday 07 March 2004 04:28 pm, E. Gladyshev wrote:
Does boost::signals have a public enumeration interface (like iterator concept) that can be used for accessing individual slots?
If you're looking for a way to access the slots (e.g., to see the underlying boost::function objects), there is no such interface, but...
This you can do. The slot call iterator is just an iterator, but the dereference operator actually invokes the slot. So, for instance, if you'd like to keep calling slots until one of them becomes true, you might write a combiner like this: struct until_true { typedef bool result_type; template<typename InputIterator> bool operator()(InputIterator first, InputIterator last) { while (first != last) { if (*first) return true; ++first; } return false; } };
No. Back when Signals was designed, there was no way to use this capability because boost::function objects were entirely opaque. Now they're a bit less opaque (in CVS, at least, because of operator==), so this could be seen as more of a hindrance. Doug

From: "Douglas Gregor" <gregod@cs.rpi.edu> To: <boost@lists.boost.org> Sent: Sunday, March 07, 2004 5:31 PM Subject: Re: [boost] enumeration interface for boost::signals Thanks a lot for the clarifications.
a
I must have missed it in the docs. Is InputIterator is a random access iterator? Can I do *(first+2)? When do iterators become invalid? For example can I store an InputIterator instance for later? How is InputIterator connected to groups? What is the order... is the 'last' iterator is the last connection? The doc says that the order of slot calls is unspecified, is it true for InputIterators? Is it possible to have stateful combiners? If so, how can I modify the combiner's state before generating a signal? For instance is it possible to do something like this: struct call_slot { bool b; typedef bool result_type; template<typename InputIterator> bool operator()(InputIterator first, InputIterator last) { if(b) do_something1( first, last ); else do_something2( first, last ); } };
A related question is if I have an instance of
boost::signals::connection,
Mybe there is another way to accomplish what I want? I'd like to have an efficient method for "capturing" signals, so that a slot can be temporarily designated as a sole signal destination. typedef boost::signals::signal< void (int) > my_signal; struct client {...}; main { my_signal sig; client c1, c2; boost::signals::connection con = sig.connect(c1); sig.connect(c2); set_capture( s, c1 /*or should it be con?*/ ); sig(1); //the signal is sent to c1 only remove_capture( s ); sig(1); //the signal is sent to all slots } Is there a natural way to implement set_capture/remove_capture? In other words, was such a feature considered during the Signals design? I am sorry for so many questions. If there are some references that I need to look at, I'd happy to do so. Eugene

On Sun, 7 Mar 2004, E. Gladyshev wrote:
I must have missed it in the docs. Is InputIterator is a random access iterator?
No, it's an input iterator.
Can I do *(first+2)? When do iterators become invalid?
Assume input iterator semantics, only.
For example can I store an InputIterator instance for later?
No.
How is InputIterator connected to groups?
Not at all :)
What is the order... is the 'last' iterator is the last connection?
Yes, but remember that the order is very loosely specified.
The doc says that the order of slot calls is unspecified, is it true for InputIterators?
Yes. The order of slot calls is unspecified within a group.
Is it possible to have stateful combiners?
Yes.
If so, how can I modify the combiner's state before generating a signal?
Hmmm, that's a problem.
Yes, but it seems that I've omitted any way to get to the combiner. Sounds like something we should add.
Not that I can think of.
In other words, was such a feature considered during the Signals design?
No, it wasn't. What sort of application do you need it for? It's interesting.
I am sorry for so many questions. If there are some references that I need to look at, I'd happy to do so.
Just the reference docs. Doug

From: "Douglas Paul Gregor" <gregod@cs.rpi.edu> To: <boost@lists.boost.org> Sent: Sunday, March 07, 2004 8:22 PM Subject: Re: [boost] enumeration interface for boost::signals [...]
No, it wasn't. What sort of application do you need it for? It's interesting.
I am trying to use boost::signals in my GUI project. The capturing feature is needed when a window has many separate regions (each region is a slot) and a region wants to capture all mouse events after the mouse enters this region. It is a very typical situation. The ability to enumerate slots in a defined order is necessary for building something similar to callback chains. For instance, in Win32 you can insert your window callback function into the existing chain. Something like callback prev_cb; void my_callback() { if( some condition ) { prev_cb(); //propagate the signal futher do_something1(); } else { do_something2(); return; //stop signal propagation } } main() { window w; prev_cb = register_callback(w, my_callback) } I am not sure what's the best way to implement something like this with boost::signals. It seems to me that we need a finer control over the slots. Eugene

On Monday 08 March 2004 03:39 am, E. Gladyshev wrote:
Ah, interesting. Now that I think about it, yes you can do this... see at the end.
I agree. There was a great proposal here to define the ordering, but it's not implemented yet :( http://article.gmane.org/gmane.comp.lib.boost.devel/25152/
The ordering is a problem. The only way to guarantee an ordering is to use groups (which is not always feasible). However, the other part (including capturing) can be handled by groups and combiners. Use the "until_true" combiner I posted previously, and have your slot return "true" to stop propagating the signal. As for the capturing, you can use groups: const int capture = -100000; // big negative number { boost::signals::scoped_connected captured = sig.connect(capture, slot_to_capture); // slot_to_capture always executes first, so it gets all the events. } // end of scope, slot_to_capture disappears If slots had a well-defined order, everything would be peachy. Doug

----- Original Message ----- From: "Douglas Gregor" <gregod@cs.rpi.edu> To: <boost@lists.boost.org> Sent: Monday, March 08, 2004 6:42 AM Subject: Re: [boost] enumeration interface for boost::signals [...]
Good idea! However I don't want to require the slot itself to be aware whether it is capturing or not. I guess I can try something like this: struct server { typedef boost::signals::signal< void (event), until_true > signals; signals sig_; struct capture_wrapper { signals::slot_type s_; capture_wrapper( slot s ) : s_(s) {} bool operator()( event e ) { //how do I make the call? s_(e); //is it ok? return true; //stop propagation } } void capture( typename signals::slot_type s ) { capture_ = sig.connect( capture, capture_wrapper(s) ); } void remove_capture() { capture_.disconnect(); } boost::signals::scoped_connected capture_; };
If slots had a well-defined order, everything would be peachy.
Yep. As for the ordering, I think the SIGNALS_GROUP_TOP/SIGNALS_GROUP_BOTTOM flags should work just fine. We definitely need this feature. I also need an efficient way for accessing slots randomly. For instance some of the Win32 listbox messages have item indexes. I'd like to implement each item as a slot, but I need a way to access the slots based on the item index, so I could call just the right slot. listboxes can contain a lot of items so propagating the messages to all items is not an option. Eugene

On Monday 08 March 2004 10:46 pm, E. Gladyshev wrote:
I suggest making capture_wrapper a function object wrapper, not a slot_type (slot_types aren't actually invocable as function objects): template<typename F> struct capture_wrapper { F f; capture_wrapper(F f) : f(f) {} bool operator()(event e) { f(e); return true; } };
That'd be a much bigger change to Signals. For one, we'd be breaking the logarithmic complexity of inserting a new element in a particular group. Doug

On Tue, 9 Mar 2004 11:30:15 -0500 Douglas Gregor <gregod@cs.rpi.edu> wrote:
If I understand the original statement, I have a similar requirement, and have done it by implementing an addition class, I call multisignal (after multimap, multiset, and so forth). It basically holds an addition to the tuple library that I call dynamic_tuple, which provides direct access to any type, while preserving type safety both at run time and compile time. You can use a type as an index into a dynamic_tuple, and still get constant time lookup. This forms a basis for the multisignal, which provides a wrapper around Boost.Signal to allow multiple signals to be raised, using the same signal object. The algorithmic complexity can be minimized to constant time lookup of the signal object using another libary I call type_to_index, which provides a special zero-based index for a specific type, relative to the collection in which the type is used as an index. Sounds complicated, but it is quite simple. However, to get the type_to_index, you have to create explicit template instantiations, or allow static data members (which some are opposed to, depending on OS preference).

----- Original Message ----- From: "Jody Hagins" <jody-boost-011304@atdesk.com> To: <boost@lists.boost.org> Sent: Tuesday, March 09, 2004 9:37 AM Subject: Re: [boost] enumeration interface for boost::signals [...]
I don't think that it's what I was talking about. Using one signal object for propagating multiple events types is not a problem. You can use tuple or variant. My problem is in accessing the singnal slots randomly in an efficient way. Eugene

----- Original Message ----- From: "Douglas Gregor" <gregod@cs.rpi.edu> [...]
I see. [...]
That'd be a much bigger change to Signals. For one, we'd be breaking the logarithmic complexity of inserting a new element in a particular group.
Yes, it would need to be a template template parameter for defining the slot container type or a policy. Eugene

On Tuesday 09 March 2004 11:42 pm, E. Gladyshev wrote:
Right, but you also have to watch the iterator invalidation semantics much more carefully. For instance, if you connect a new slot to a signal A from within a slot called from signal B, there is no problem in the current multimap implementation because iterators are stable. For a vector, you have to queue the additions. In both cases you need to queue up deletions (that's already done, of course). But yes, it could be a policy <shudder>. Doug

On Tuesday 09 March 2004 11:42 pm, E. Gladyshev wrote:
That'd be a much bigger change to Signals. For one, we'd be breaking
From: "Douglas Gregor" <gregod@cs.rpi.edu> To: <boost@lists.boost.org> Sent: Tuesday, March 09, 2004 9:01 PM Subject: Re: [boost] enumeration interface for boost::signals the
I guess signal can be specialized on containers with "unstable" iterators such as vector and auto-generated uniq keys can be added to slots. Something like: struct signal { operator()(...) { for( size_t i = 0; i < slots.size(); ++i ) { key k = slots[i].get_key(); *slots[i]; //make the call if( k != slots[i].get_key() ) //something is changed i = find_key(k); //reset the index } } }; However I'd prefer queueing "nested" insertions and deletion in all cases. It is not consistent that signals is queueing deletions but not insertions now.
But yes, it could be a policy <shudder>.
It be nice. I think that it would make signals more suitable for large scale applications where optimizations are of a great importance. In a way, combiner is already a policy. Eugene

On Wednesday 10 March 2004 01:22 am, E. Gladyshev wrote:
That doesn't play so well with the slot call iterators, though. Anyway...
Oh, my apologies, I wasn't quite clear on what the queueing meant. Disconnecting a slot while in a nested call immediately disables the slot (so it will not be called again), but the slot is not physically removed from the slot list until we exit the top-level nest. So the insert/delete semantics are equivalent, because both occur immediately from the user's point of view. This would not be the case with a vector; we'd have to delay insertions.
Which operation's complexity are you concerned about? I've never heard of a performance problem... Doug

----- Original Message ----- From: "Douglas Gregor" <gregod@cs.rpi.edu> [...]
That doesn't play so well with the slot call iterators, though. Anyway...
No, it doesn't. There is another way, please read on... the
I see. As soon as the slot insertion happens at a consistent relative position, the semantic is consistent. Does it mean the all nested insertions put the new slot at the end of the slot list so that the new slot will be called during the current signal propagation call? If signals supported the slot ordering then what would the insertion semantic look like?
Which operation's complexity are you concerned about? I've never heard of
a
performance problem...
Sometimes, from the event property values, the signal source can tell what slot exactly or subset of slots need to be called and that other slots should not be bothered with this event instance. In what case the signal source should havea random access to the slots. Otherwise it will have to propagate the event to *all* slots and let them decide on what to do. If the number of slots is not too big, this works just fine. However if the number of slots is huge, the optimization is important and one of the optimized solutions will most likely require a random access to slots based on the slot index and slot ordering support, including slot insertions at specified positions in the slot list. Another way to optimize requires the ability to call the slot that is referenced by the signals::connection instance. In what case, the user can handle all the ordering and indexing stuff on her own. For instance, the user could have a separate ordered vector of signals::connection objects and call the connected slots as needed. I'd say that instead of changing the existing signals semantic to support the slot ordering and random slot access, I would make it possible to access and call slots that are referenced by signals::connection objects and leave the rest to the user. In a way, it is just an extension to the combiner semantic. Indeed with combiner, the user can propagate the event as she likes anyway except that she has no idea what slot is what (it is really strange to me). Are there any problems with giving the user an access to the slots referenced by signals::connection objects that don't see? I'll try to give a real life example once again. In Win32 the standard listbox control can generate messages that have the listbox item index as one of the message parameters. Imagine that each item in the listbox is implemented as a signal slot. How would you propagate the Win32 messages that are targeting separate listbox items Eugene

On Thursday 11 March 2004 10:18 pm, E. Gladyshev wrote:
This is the slot ordering issue again :) If you insert with a group that precedes the group being called, it will not be called during that invocation. If you insert with a group that follows the gorup being called, it will be called during that invocation. If you insert in the same group as the one being called, you don't know because there is no guaranteed ordering within a group [*].
Ok, I understand this. I thought you were referring to a different source of inefficiency. In any case, getting random access (traversal) iterators and fast ordered insertions/deletions is not a trivial task. Did you have some particular data structure in mind that could do this? Here is a question for you: would the ability to invoke only the slots within a particular group solve this problem? That is implementable within the current semantics, although it will not give random access.
We'd be adding a connection_type for each signal, which would derive from signals::connection. It can be done, of course, with a little internal shuffling and some slight user code breakage.
You might be better off with a vector of boost::function objects.
You usually can't tell what slots you're calling anyway, because type information is lost to the user once you've connected the slot. The point of a callback is that you don't know where you're calling back to.
Other than the proliferation of classes derived from signals::connection, not really.
If we had the ability to invoke only the slots that were part of a particular group, you would make the group the listbox item number. Slots that are interested in all item numbers would not have any group; slots interested in a particular item number would be in that group. Item numbers aren't all that great to use, because items can be inserted or deleted. You'd probably be better off using some other form of identifier for list items. It's a bit of a strange problem. I don't believe that random access traversal iterators are the right answer; calling via a signals::connection derived class is rather redundant, because you'd be cloning the signal's internal ordering. Invocation of slots in a particular group might do it for you, but only if you don't need to deal with, e.g., slots that accept a subrange of item index numbers. Doug [*] Unless someone can convince me that one can portably order elements with the same key within a multimap.

----- Original Message ----- From: "Douglas Gregor" <gregod@cs.rpi.edu> To: <boost@lists.boost.org> Sent: Wednesday, March 17, 2004 9:22 AM
called,
I see... makes sense to me.
Which operation's complexity are you concerned about? I've never heard
of
I guess it could be something like tree (balanced, Red-Black whatever). But, I'd let the user to define the container type.
Here is a question for you: would the ability to invoke only the slots
within
a particular group solve this problem? That is implementable within the current semantics, although it will not give random access.
Bingo. It'll give me a control over how the slots are called. Then, what I can do is associate my group numbers with the items. //physical list box callback void on_lisbox_event( event e ); listbox lb(on_lisbox_event); //physical listbox interface signal<event> sig; void connect_item( item i ) { int group = generate_uniq_group_number(); //add the item label to the listbox int index = lb.add(i.label()); //returns the new item index //set my user data associate with the label lb.set_user_data( index, group ); //make connection sig.connect( group, i ); } void on_lisbox_event( event e ) { //index of the item that generated this event int index = e.get_index(); //get the group int group = lb.get_user_data( index ); //send event to the item sig( e, group ); } The set_user_data()/get_user_data() function is the key. Except generate_uniq_group_number() (which is not going to be pretty), it should work. Will signals automatically free resources for groups that are empty? A question about combiner: struct combiner { template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const; }; Are [first,last] iterators ordered by the group number? [...]
You might be better off with a vector of boost::function objects.
Yes, I thought about it too. However I'd like to have the "trackable" feature.
of
a callback is that you don't know where you're calling back to.
It is not always the case. For example Win32 supports the so called callback chains so you know where in the chain you are. Even more... from your callback, you can make a call that will send the event down the chain then it will return and your callback can do something else. People use this technique all the time. To make signals usable for serious GUI development, I think that it should support something like this. [...]
particular
It's a bit of a strange problem. I don't believe that random access
Yes, something like this (see my example above). traversal
I will be imposing my own ordering, not cloning the signal's ordering that I don't know anyway.
I need to build something like callback chains as well. I guess that I'll have to do some not very pretty tricks... but yes, it should do it for me. Eugene

Douglas Paul Gregor wrote:
Violent agreement. I have another use case, I came across recently. Think of a container C that stores elements of type E. The container has a signal that is called whenever an element is added. The element has destroy method and a signal that is emitted when it is destroyed. The problem is that currently calling remove during a call to a slot connected to the add signal of the container leads to desaster. The easy solution would be to connect the slot combiner to the remove signal and stop slot calling in case the element is removed. Though this is really complicated now as there is no direct access to the combiner. Thomas -- Thomas Witt witt@acm.org

On Monday 08 March 2004 09:13 pm, Thomas Witt wrote:
That was me, actually; I'm already convinced, and the omission of the combiner was an unfortunate oversight.
Violent agreement. I have another use case, I came across recently.
Always appreciated.
This is a problem with the data structure storing the E elements, not Signals itself, right? Signals is designed to be resilient to this type of thing.
I'll check in the fix in a few hours. Doug

Douglas Gregor wrote:
Yep. FWIW the citation structure is correct it's just that the first line is bogus because all content you cited from E. Gladyshev had been removed. Though I agree it's really misleading. It was an oversight.
Yes, the problem is not a Signals problem.
I'll check in the fix in a few hours.
Thanks Thomas -- Thomas Witt witt@acm.org

On Tuesday 09 March 2004 01:48 pm, Douglas Gregor wrote:
I'll check in the fix in a few hours.
You can now get to the combiner of a signal with, surprise!, the combiner() method. Combiners are also now safe after deletion of the signal, e.g., if a signal A calls a slot that deletes signal A, we'll no longer be in the land of undefined behavior. Doug
participants (5)
-
Douglas Gregor
-
Douglas Paul Gregor
-
E. Gladyshev
-
Jody Hagins
-
Thomas Witt