[ptr_map] iterator changes, feedback welcome

Dear All, I've changed the inteface of ptr_map's iterators due to some user requests. The old interface supported typedef ptr_map<string,int> map_t; map_t m; m[ "foo" ] = 4; // insert pair m[ "bar" ] = 5; // ditto ... for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *i += 42; // add 42 to each value cout << "value=" << *i << ", key=" << i.key() << "n"; } The same code can now be written (#1) for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *i->second += 42; // add 42 to each value cout << "value=" << *i->second << ", key=" << i->first << "n"; } or (#2) for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { i.value() += 42; // add 42 to each value cout << "value=" << i.value() << ", key=" << i.key() << "n"; } or (#3) for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *(*i).second += 42; // add 42 to each value cout << "value=" << *(*i).second << ", key=" << (*i).first << "n"; } There are two issues here: 1. AFIICT it is not possible to let operator->() return a tuple of references s.t. ->second would yeild T& instead of T*. Reason: the pointer might be null. 2. Operator*() now returns a tuple [key,T*]; this is necessary because you may need access to both the key inside e.g predicates for algorithms. Operator->() also returns the same tuple, so that you can make the familiar loop in #1. The question is #1 is necessary/wanted given that it removes the current behavior where operator->() returns a T* s.t. you can more easily access the mapped object. Would it be preferable to keep operator-<() with a T* return type? Thanks for your feedback -Thorsten

If at all reasonably possible I would prefer to keep operator-> to return T*. It certainly makes it easy to write something like "mapit->member". And it looks natural as it treats the iterator like a smart object pointer. The only problem was accessing the key from a predicate function.
From #3 are you suggesting that operator-> and operator* return different types. i.e. Instead of T*, and T& respectively, but T* and tuple<key,T*>? That seems like a reasonable idea. It might look a little odd at first, but would certainly get the job done and would still be easy to get used to.
The only point of #1 is so that the iterators behave exactly like std::map iterators, right? One the one hand, that would be nice for consistency with std::map, but then this is a different class with slightly different uses, so I personally don't mind if it has slightly different characteristics. And the usefulness of operator-> as T* outweighs the need for identical behavior, IMO. More and more as I think about it, I like this approach (i.e. operator-> as T* and operator* as tuple<key,T*>). Method #1 is not supported, but it doesn't seem that important to me anyway. You would still be able to accomplish the same thing with i.key() and i.value(). -- Bill -- "Thorsten Ottosen" <tottosen@dezide.com> wrote in message news:dt8968$cvg$1@sea.gmane.org...
Dear All,
I've changed the inteface of ptr_map's iterators due to some user requests. The old interface supported
typedef ptr_map<string,int> map_t; map_t m; m[ "foo" ] = 4; // insert pair m[ "bar" ] = 5; // ditto ... for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *i += 42; // add 42 to each value cout << "value=" << *i << ", key=" << i.key() << "n"; }
The same code can now be written (#1)
for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *i->second += 42; // add 42 to each value cout << "value=" << *i->second << ", key=" << i->first << "n"; }
or (#2)
for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { i.value() += 42; // add 42 to each value cout << "value=" << i.value() << ", key=" << i.key() << "n"; }
or (#3)
for( map_t::iterator i = m.begin(); i != m.end(); ++i ) { *(*i).second += 42; // add 42 to each value cout << "value=" << *(*i).second << ", key=" << (*i).first << "n"; }
There are two issues here:
1. AFIICT it is not possible to let operator->() return a tuple of references s.t. ->second would yeild T& instead of T*. Reason: the pointer might be null.
2. Operator*() now returns a tuple [key,T*]; this is necessary because you may need access to both the key inside e.g predicates for algorithms. Operator->() also returns the same tuple, so that you can make the familiar loop in #1. The question is #1 is necessary/wanted given that it removes the current behavior where operator->() returns a T* s.t. you can more easily access the mapped object. Would it be preferable to keep operator-<() with a T* return type?
Thanks for your feedback
-Thorsten
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On 2/20/06, Bill Buklis <boostuser@pbjzone.com> wrote: [snip]
The only point of #1 is so that the iterators behave exactly like std::map iterators, right? One the one hand, that would be nice for consistency with std::map, but then this is a different class with slightly different uses, so I personally don't mind if it has slightly different characteristics. And the usefulness of operator-> as T* outweighs the need for identical behavior, IMO.
I had some issues with using std::ptr_map in generic algorithms because of this. I think that the inner typedef's of std::ptr_map, wrt iterator concept modelling, must be carefully choosed too. That can break a lot of generic code to not work on ptr_map or other ptr_containers. [snip] -- Felipe Magno de Almeida

On 2/20/06, Bill Buklis <boostuser@pbjzone.com> wrote:
[snip]
The only point of #1 is so that the iterators behave exactly like std::map iterators, right? One the one hand, that would be nice for consistency with std::map, but then this is a different class with slightly different uses, so I personally don't mind if it has slightly different characteristics. And the usefulness of operator-> as T* outweighs the need for identical behavior, IMO.
I had some issues with using std::ptr_map in generic algorithms because of
Felipe Magno de Almeida wrote: this.
I think that the inner typedef's of std::ptr_map, wrt iterator concept modelling, must be carefully choosed too. That can break a lot of generic code to not work on ptr_map or other ptr_containers.
So you want to ensure that map::value_type v = *iter; map::reference r = *iter; map::pointer p = &*iter; works and return something with first and second members? This is almost in place ... I still need to change the internals of map iterators to precompute the tuple of references ... right now I compute them on demand. I suspect this will make iteration somewhat slower. -Thorsten

Felipe Magno de Almeida <felipe.m.almeida <at> gmail.com> writes:
On 2/20/06, Bill Buklis <boostuser <at> pbjzone.com> wrote:
[snip]
The only point of #1 is so that the iterators behave exactly like std::map iterators, right? One the one hand, that would be nice for consistency with std::map, but then this is a different class with slightly different uses, so I personally don't mind if it has slightly different characteristics. And the usefulness of operator-> as T* outweighs the need for identical behavior, IMO.
I had some issues with using std::ptr_map in generic algorithms because of
this.
I think that the inner typedef's of std::ptr_map, wrt iterator concept modelling, must be carefully choosed too. That can break a lot of generic code to not work on ptr_map or other ptr_containers.
What specific problems did you have? Let's assume I simply base the implementation on std::ptr<key,T*> so that I can just reuse the existing iterators. It worries me that the pointer now becomes exposed. One might write a copy-algorithm for std::map<key,T*>, std::map<key,shared_ptr<T>> and ptr_map<key,T>. Trying to do that leads major problems. Of course, what if the algorithm only searches and call members, but does copy anything it works ok. -Thorsten

Thorsten Wrote :
1. AFIICT it is not possible to let operator->() return a tuple of references s.t. ->second would yeild T& instead of T*. Reason: the pointer might be null.
Well, you could throw a bad_pointer here, but....
2. Operator*() now returns a tuple [key,T*]; this is necessary because you may need access to both the key inside e.g predicates for algorithms. Operator->() also returns the same tuple, so that you can make the familiar loop in #1. The question is #1 is necessary/wanted given that it removes the current behavior where operator->() returns a T* s.t. you can more easily access the mapped object. Would it be preferable to keep operator-<() with a T* return type?
I think the important thing here is to provide a consistent behaviour. ptr_map is an extension to std::map, and so should behave like std::map. As Felipe says, any deviations from the interface of std::map will make it harder to write generic code, and should be avoided where possible. I think it would be surprising to users if the following two operations returned different types : (*i).second i->second On a std::map they are the same, so I think they should be the same for ptr_map. Sam

Sam Partington <sam.partington <at> gmail.com> writes:
I think the important thing here is to provide a consistent behaviour. ptr_map is an extension to std::map, and so should behave like std::map.
As Felipe says, any deviations from the interface of std::map will make it harder to write generic code, and should be avoided where possible.
I think it would be surprising to users if the following two operations returned different types :
(*i).second i->second
I tend to agree. The current implementation of the iterators kinda sucks...it woeks, but I suspect it to be fairly slow. I tend to think that I should simply use std::map<key,T*> instead of std::map<key,void*> underneith and thus don't change the iterators at all. This of course exposes the pointers moer than we would like. -Thorsten
participants (5)
-
Bill Buklis
-
Felipe Magno de Almeida
-
Sam Partington
-
Thorsten Ottosen
-
Thorsten Ottosen