[Iterator] Adaptors for accessing keys/values in associative containers

Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator? I'm thinking of 'key_iterator' and 'value_iterator' adaptors (along with convenience 'make_key_iterator' and 'make_value_iterator' functions) that allow any associative container (that follows standard library idioms) to be accessed as either a range of keys or as a range of values. They'd be used like this: std::map<std::string,int> m; std::for_each(make_value_iterator(m.begin()),make_value_iterator(m.end()),func); Where func() takes an int parameter rather than a std::pair<const std::string,int>. Using these adaptors is considerably easier than using boost.lambda for this purpose and they can be implemented to incur no (or negligable) runtime overhead when compared to iterating normally through an associative container. I find these useful - does anyone else?

On Thu, 9 Aug 2007, Joseph Gauterin wrote:
Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator?
I'm thinking of 'key_iterator' and 'value_iterator' adaptors (along with convenience 'make_key_iterator' and 'make_value_iterator' functions) that allow any associative container (that follows standard library idioms) to be accessed as either a range of keys or as a range of values.
They'd be used like this:
std::map<std::string,int> m; std::for_each(make_value_iterator(m.begin()),make_value_iterator(m.end()),func);
Where func() takes an int parameter rather than a std::pair<const std::string,int>.
Using these adaptors is considerably easier than using boost.lambda for this purpose and they can be implemented to incur no (or negligable) runtime overhead when compared to iterating normally through an associative container.
I find these useful - does anyone else?
Yes, I agree they are. But how about a generalization to tuples? Let's call it a get_iterator (any ideas for a better name?), i.e. template < typename BaseIterator , int NthElement > class get_iterator ... ; and define make_get_iterator accordingly and then, following your example: std::map< std::string , int > m ; std::for_each( make_get_iterator< 1 >( m.begin() ) , make_get_iterator< 1 >( m.end() ) , func ) ; That would require to either specialize boost::get for std::pair (easier; wasn't a similar specialization done with tr1::get?) or specialize that iterator for std::pairs (more code). And then, for convenience and/or readability, it could be possible to wrap make_get_iterator as make_key_iterator and make_value_iterator. I already have an implementation of this get_iterator (with all specializations for std::pair and boost::compressed_pair), if anyone is interested. -- François Duranleau LIGUM, Université de Montréal

I think the names 'key_iterator'/'value_iterator' are clearer than 'get_iterator', but I can see how the functionality of being able to select any element of a tuple could be useful. Perhaps we could merge the two concepts and have 'make_key_iterator'/'make_value_iterator' call 'make_get_iterator<1>'/'make_get_iterator<2> respectively. While we could do that with the helper functions, unfortunately for the classes, without template typedefs we can't have 'get_iterator<X,1>' be the same as 'key_iterator<X>' and having two classes doing the same thing seems like a bit of a cludge (I suppose std::pair/std::tr1::tuple<X,Y> gives us precendent, but it still doesn't seem right).

On Thu, 9 Aug 2007, Joseph Gauterin wrote:
I think the names 'key_iterator'/'value_iterator' are clearer than 'get_iterator', but I can see how the functionality of being able to select any element of a tuple could be useful.
Perhaps we could merge the two concepts and have 'make_key_iterator'/'make_value_iterator' call 'make_get_iterator<1>'/'make_get_iterator<2> respectively.
Yes, I said that.
While we could do that with the helper functions, unfortunately for the classes, without template typedefs we can't have 'get_iterator<X,1>' be the same as 'key_iterator<X>' and having two classes doing the same thing seems like a bit of a cludge (I suppose std::pair/std::tr1::tuple<X,Y> gives us precendent, but it still doesn't seem right).
How about: template < typename Iterator > class key_iterator : public get_iterator< Iterator , 0 > { public : key_iterator() {} key_iterator( const Iterator& i ) : get_iterator< Iterator , 0 >( i ) { } } ; And something similar for value_iterator? -- François Duranleau LIGUM, Université de Montréal

François Duranleau wrote:
How about:
template < typename Iterator > class key_iterator : public get_iterator< Iterator , 0 > { public :
key_iterator() {}
key_iterator( const Iterator& i ) : get_iterator< Iterator , 0 >( i ) { } } ;
IIRC, a conforming iterator can't be implemented using inheritance. Overloaded operators would return the base type. -- Shunsuke Sogame

Quoting Joseph Gauterin <joseph.gauterin@googlemail.com>:
Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator?
Sounds like a great idea. Please add a make_value_range() too, that uses the range-library (boost::begin & boost::end). -- /Marcus

Marcus Lindblom wrote:
Please add a make_value_range() too, that uses the range-library (boost::begin & boost::end).
FWIW, oven(http://tinyurl.com/2axp2l) implemented that. :-) BOOST_FOREACH (string v, make_map_values(m)) { // ... } make_map_values(m) is synonym of make_elements<mpl::int_<1> >(m) that iterates over a range of FusionSequence. I agree no specific iterator is needed. make_elements is actually implemented using transform_iterator and a FunctionObject. Also note that Boost.Lambda can't be used here. A Boost.Lambda functor is not Assignable. Regards, -- Shunsuke Sogame

On 8/9/07, shunsuke <pstade.mb@gmail.com> wrote:
Marcus Lindblom wrote:
Please add a make_value_range() too, that uses the range-library (boost::begin & boost::end).
FWIW, oven(http://tinyurl.com/2axp2l) implemented that. :-)
BOOST_FOREACH (string v, make_map_values(m)) { // ... }
make_map_values(m) is synonym of make_elements<mpl::int_<1> >(m) that iterates over a range of FusionSequence.
I agree no specific iterator is needed. make_elements is actually implemented using transform_iterator and a FunctionObject.
Also note that Boost.Lambda can't be used here. A Boost.Lambda functor is not Assignable.
Yes, that is very unfortunate and I think it should be fixed. I saw that oven uses a special wrapper to make boost.lambda objects copiable. What does it exactly do? The wrapper I use simply does a placement new in its assignment operator. This is very exception unsafe, but I presume that most (all?) lambda objects won't throw on copy constructor. Using boost.optional would give the basic exception safetly, but it does increase the size of the wrapper. gpd

Giovanni Piero Deretta wrote:
Also note that Boost.Lambda can't be used here. A Boost.Lambda functor is not Assignable.
Yes, that is very unfortunate and I think it should be fixed.
I saw that oven uses a special wrapper to make boost.lambda objects copiable. What does it exactly do?
As you guess, the wrapper uses Boost.Optional. It's rather simple: http://tinyurl.com/2nvw59
The wrapper I use simply does a placement new in its assignment operator. This is very exception unsafe, but I presume that most (all?) lambda objects won't throw on copy constructor. Using boost.optional would give the basic exception safetly, but it does increase the size of the wrapper.
I'm not sure. Boost.Optional also seems to use placement-new. Anyway, Boost.Optional seems fundamental, because Boost.Lambda functors are not DefaultConstructible, which ForwardIterator requires. Regards, -- Shunsuke Sogame

On 8/10/07, shunsuke <pstade.mb@gmail.com> wrote:
Giovanni Piero Deretta wrote:
Also note that Boost.Lambda can't be used here. A Boost.Lambda functor is not Assignable.
Yes, that is very unfortunate and I think it should be fixed.
I saw that oven uses a special wrapper to make boost.lambda objects copiable. What does it exactly do?
As you guess, the wrapper uses Boost.Optional. It's rather simple: http://tinyurl.com/2nvw59
The wrapper I use simply does a placement new in its assignment operator. This is very exception unsafe, but I presume that most (all?) lambda objects won't throw on copy constructor. Using boost.optional would give the basic exception safetly, but it does increase the size of the wrapper.
I'm not sure. Boost.Optional also seems to use placement-new.
Yes, but it also need a boolean to record the initialized/non initialized state. In this case, if you assume that copyconstruction of a lambda never throws (a strong assumption, but it works in my case), you can do without the bolean. My range expressions are growing larger and larger even without the extra bool :)
Anyway, Boost.Optional seems fundamental, because Boost.Lambda functors are not DefaultConstructible, which ForwardIterator requires.
Hum,. you are right. Probably I've just been lucky and never used an algo that needed to default construction of my iterators so far. I'll probably revert my code to use optional. gpd

On 08/09/2007 04:10 PM, shunsuke wrote: [snip]
FWIW, oven(http://tinyurl.com/2axp2l) implemented that. :-) In .html file:
/libs/oven/doc/html/oven/concepts.html there's: For any Pipable Object p the following must be met: * x|p(x1,..,xN) is a valid expression if and only if make_p(x, x1,..xN) is a valid expression. If p is actually, my_p, does this mean make_p in the above constraint is actually make_my_p? I ask because, otherwise, I don't see why there's any need to mention p(x1,...,xN).

Larry Evans wrote:
On 08/09/2007 04:10 PM, shunsuke wrote: [snip]
FWIW, oven(http://tinyurl.com/2axp2l) implemented that. :-) In .html file:
/libs/oven/doc/html/oven/concepts.html
there's:
For any Pipable Object p the following must be met:
* x|p(x1,..,xN) is a valid expression if and only if make_p(x, x1,..xN) is a valid expression.
If p is actually, my_p, does this mean make_p in the above constraint is actually make_my_p?
Yes. For example, make_filtered(rng, pred) is a valid expression, because rng|filtered(pred) is stated as a valid expression.
I ask because, otherwise, I don't see why there's any need to mention p(x1,...,xN).
Sorry, I couldn't understand this sentence because of my poor english. What do "mention" exactly mean? -- Shunsuke Sogame

On 08/10/2007 09:21 PM, shunsuke wrote:
Larry Evans wrote: [snip]
For any Pipable Object p the following must be met:
* x|p(x1,..,xN) is a valid expression if and only if make_p(x, x1,..xN) is a valid expression.
If p is actually, my_p, does this mean make_p in the above constraint is actually make_my_p?
Yes. For example, make_filtered(rng, pred) is a valid expression, because rng|filtered(pred) is stated as a valid expression.
I ask because, otherwise, I don't see why there's any need to mention p(x1,...,xN).
Sorry, I couldn't understand this sentence because of my poor english. What do "mention" exactly mean?
Or maybe you couldn't understand because of *my* poor english or my poor way expressing what I meant. What I meant was that, using your example, the following: rng|filtered(pred) is a valid expression if and only if make_p(rng,pred) would be what you'd be saying if you'd meant make_p to be literally make_p instead of the p in make_p actually being the same sequence of characters as that naming the function on the rhs of the | operator. I know, it's a lot of nit-picking, but I just wanted to make sure.

Larry Evans wrote:
Or maybe you couldn't understand because of *my* poor english or my poor way expressing what I meant. What I meant was that, using your example, the following:
rng|filtered(pred) is a valid expression if and only if make_p(rng,pred)
would be what you'd be saying if you'd meant make_p to be literally make_p instead of the p in make_p actually being the same sequence of characters as that naming the function on the rhs of the | operator. I know, it's a lot of nit-picking, but I just wanted to make sure.
Understood. I should fix it using "make_ ## p" or something :-) Thank you for the feedback. Regards, -- Shunsuke Sogame

On 8/9/07, Joseph Gauterin <joseph.gauterin@googlemail.com> wrote:
Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator?
I'm thinking of 'key_iterator' and 'value_iterator' adaptors (along with convenience 'make_key_iterator' and 'make_value_iterator' functions) that allow any associative container (that follows standard library idioms) to be accessed as either a range of keys or as a range of values.
These would add very little that what transform_iterator already does. What would be more useful would be two function objects called first and second to access the first or the second element of a pair. Also function object wrappers for boost.tuple get<N> would really be useful. In addition to transform_iterator, they would be very useful in bind and boost.lambda expressions. BTW, I think that boost.bimap already provides them.
They'd be used like this:
std::map<std::string,int> m; std::for_each(make_value_iterator(m.begin()),make_value_iterator(m.end()),func);
With the above functors, you would spell that std::for_each( make_transform_iterator(m.begin(), second()), make_transform_iterator(m.end(), second())),func); At this point, is it really worth to have separate key/value_iterators? gpd

On Thu, 9 Aug 2007, Giovanni Piero Deretta wrote:
On 8/9/07, Joseph Gauterin <joseph.gauterin@googlemail.com> wrote:
Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator?
I'm thinking of 'key_iterator' and 'value_iterator' adaptors (along with convenience 'make_key_iterator' and 'make_value_iterator' functions) that allow any associative container (that follows standard library idioms) to be accessed as either a range of keys or as a range of values.
Yes, I always forget about that one. To generalize, we could use a functor wrapping boost::get<> (and/or tr1::get<>) (how to call it, 'getter'?). -- François Duranleau LIGUM, Université de Montréal

On Thu, 9 Aug 2007, François Duranleau wrote:
On Thu, 9 Aug 2007, Giovanni Piero Deretta wrote:
On 8/9/07, Joseph Gauterin <joseph.gauterin@googlemail.com> wrote:
Would anyone be interested in having a couple of extra specialized iterator adaptors in Boost.Iterator?
I'm thinking of 'key_iterator' and 'value_iterator' adaptors (along with convenience 'make_key_iterator' and 'make_value_iterator' functions) that allow any associative container (that follows standard library idioms) to be accessed as either a range of keys or as a range of values.
Yes, I always forget about that one. To generalize, we could use a functor wrapping boost::get<> (and/or tr1::get<>) (how to call it, 'getter'?).
Sorry for the extra noise, it seems I edited this post to quickly and I didn't notice I cut the wrong part. This was meant as a reply to Giovanni Piero Deretta's post about transform_iterator. Sorry again. -- François Duranleau LIGUM, Université de Montréal

The Boost.Iterator specialized adaptor 'indirect_iterator' allows iteration over the objects pointed-to by a sequence. This could easily be implemented using boost::transform_iterator and a dereferencing function object, but the case of iterating over pointed-to objects is so common that it deserves its own convenience iterator. In my own code I know that iterating over the values or keys of an associative container is a common thing to do. I think they deserve their own specialized iterator adaptors if it is a common thing to do amongst Boost users in general - if it's more of a niche requirement then I agree that boost::transform_iterator + function object would be a better way forward.
BTW, I think that boost.bimap already provides them. I'd say that's a good reason to provide equivilent functionality for std::(multi)map and std::tr1::unordered_(multi)map.
Also function object wrappers for boost.tuple get<N> would really be useful. There's already a free function (boost::tuples::get<N>) that would work here (it is an actual function, not a function object, so the usual optimization issues are present), but it only works on tuples, not on std::pair - so it won't work with std::(multi)map and std::tr1::unordered_(multi)map.

BTW, I think that boost.bimap already provides them. I'd say that's a good reason to provide equivilent functionality for std::(multi)map and std::tr1::unordered_(multi)map.
Boost.bimap provides the first and second function objects. They will work (when used with transform iterators) with all map-like objects.
Also function object wrappers for boost.tuple get<N> would really be useful. There's already a free function (boost::tuples::get<N>) that would work here (it is an actual function, not a function object, so the usual optimization issues are present), but it only works on tuples, not on std::pair - so it won't work with std::(multi)map and std::tr1::unordered_(multi)map.
It is very hard to use overloaded functions with boost.bind or lambda, or as parameters to transform iterators. This is why I explicitly said function object. About get<N> not working with pairs, fusion at_c works just fine for pairs, tuples and all fusion sequences. gpd
participants (6)
-
François Duranleau
-
Giovanni Piero Deretta
-
Joseph Gauterin
-
Larry Evans
-
Marcus Lindblom
-
shunsuke