[range] rationale for transform_range() algorithm?

Background: I have been working on range-based algorithms and adaptors. The algorithms are variants of the std::algorithms that accept ranges instead of a [begin,end) iterator pair. The adaptors are like the iterator adaptors, except they adapt ranges. Following the principal of least surprise, I want to call my range adaptors filter_range, indirect_range, reverse_range and transform_range (to parallel filter_iterator, indirect_iterator, etc). But I can't do this, because there already is a transform_range, and it's a function! Problem: At the bottom of iterator_range.hpp is this: template< typename SeqT, typename Range, typename FuncT > inline SeqT transform_range( const Range& r, FuncT Func ) { SeqT Seq; std::transform( begin( r ), end( r ), std::back_inserter(Seq), Func ); return Seq; } I'm not sure why. It looks like a range-based algorithm, but ... why provide only one? (Two actually -- copy_range is there also.) And why call it transform_range() instead of just calling it "transform" and making it part of a complete range-based algorithm library? Solution: I suggest the functions copy_range() and transform_range() should be removed or moved into a "deprecated" namespace, freeing up the identifier "boost::transform_range" to be a type, analagous to boost::transform_iterator. Development should proceed on a range-based algorithms library to fill the need that the existing "transform_range()" function is meeting. Thoughts? -- Eric Niebler Boost Consulting www.boost-consulting.com

Hi, On Sun, Mar 13, 2005 at 10:04:05PM -0800, Eric Niebler wrote:
Background:
I have been working on range-based algorithms and adaptors. The algorithms are variants of the std::algorithms that accept ranges instead of a [begin,end) iterator pair. The adaptors are like the iterator adaptors, except they adapt ranges.
Following the principal of least surprise, I want to call my range adaptors filter_range, indirect_range, reverse_range and transform_range (to parallel filter_iterator, indirect_iterator, etc). But I can't do this, because there already is a transform_range, and it's a function!
Problem:
At the bottom of iterator_range.hpp is this:
template< typename SeqT, typename Range, typename FuncT > inline SeqT transform_range( const Range& r, FuncT Func ) { SeqT Seq; std::transform( begin( r ), end( r ), std::back_inserter(Seq), Func ); return Seq; }
I'm not sure why. It looks like a range-based algorithm, but ... why provide only one? (Two actually -- copy_range is there also.) And why call it transform_range() instead of just calling it "transform" and making it part of a complete range-based algorithm library?
Solution:
I suggest the functions copy_range() and transform_range() should be removed or moved into a "deprecated" namespace, freeing up the identifier "boost::transform_range" to be a type, analagous to boost::transform_iterator. Development should proceed on a range-based algorithms library to fill the need that the existing "transform_range()" function is meeting.
Thoughts?
Originaly I was the author of iterator_range class and the utilities. It was then taken to Boost.Range library as a more meaningful place. Rantionale for these two functions was to improve the usability of iterator_range class. transform_range is not so crucial, but the copy_range is very important. Without it you cannot easily convert iterator_range to a string for instance. You need to go through iterator-constructor. So instead of str=copy_range<std::string>(aRange); You need to write str=std::string(aRange.begin(), aRange.end()); The second one is not as nice, but what is more important, it disallows you to pass a range by value in a chain. In other words, you cannot write str=copy_range<std::string>(find_first(input,"helo")); To sumarize, copy_range is an essential utility, that provides a way of copying between different range types, especialy the legacy ones like std::string. Therefor it has an important place in the Boost.Range library. Regards, Pavol

"Pavol Droba" <droba@topmail.sk> wrote in message news:20050314072940.GE7893@lenin.felcer.sk... | Hi, | | On Sun, Mar 13, 2005 at 10:04:05PM -0800, Eric Niebler wrote: | > Solution: | > | > I suggest the functions copy_range() and transform_range() should be | > removed or moved into a "deprecated" namespace, freeing up the | > identifier "boost::transform_range" to be a type, analagous to | > boost::transform_iterator. Development should proceed on a range-based | > algorithms library to fill the need that the existing | > "transform_range()" function is meeting. | > | > Thoughts? | So instead of | | str=copy_range<std::string>(aRange); | | You need to write | str=std::string(aRange.begin(), aRange.end()); | | The second one is not as nice, but what is more important, it disallows | you to pass a range by value in a chain. In other words, you cannot | write | | str=copy_range<std::string>(find_first(input,"helo")); | | To sumarize, copy_range is an essential utility, that provides a way of | copying between different range types, especialy the legacy ones like | std::string. Therefor it has an important place in the Boost.Range | library. there is one alternative which might make copy_range less needed too. We could put this into sub_range<T>: operator T() const { return T( begin(), end() ); } So if you really want range2container conversion, you must use a sub_range<string>: str = sub_range<std::string>( find_first(intput, "jello" ) ); Anyway, I agree with Eric that the transform_range should go away. Eric, feel free to remove it or say if I should do it. -Thorsten

Thorsten Ottosen wrote:
there is one alternative which might make copy_range less needed too. We could put this into sub_range<T>:
operator T() const { return T( begin(), end() ); }
So if you really want range2container conversion, you must use a sub_range<string>:
str = sub_range<std::string>( find_first(intput, "jello" ) );
Eeek, no! Implicit conversions should be avoided. But thinking of this as a range conversion is the right way to go, IMO, except that it should be called "range_cast" and it should be explicit.
Anyway, I agree with Eric that the transform_range should go away.
Eric, feel free to remove it or say if I should do it.
I'll do it. Thanks! -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> wrote in message news:4235B296.4050205@boost-consulting.com... | Thorsten Ottosen wrote: | | > | > there is one alternative which might make copy_range less needed too. | > We could put this into sub_range<T>: | > | > operator T() const | > { return T( begin(), end() ); } | > | > So if you really want range2container conversion, you must use a | > sub_range<string>: | > | > str = sub_range<std::string>( find_first(intput, "jello" ) ); | > | | | Eeek, no! Implicit conversions should be avoided. except when they shouldn't be (like in derived* to base*) :-) | But thinking of this | as a range conversion is the right way to go, IMO, except that it should | be called "range_cast" and it should be explicit. hm..I dunno, the cast stuff implies some kind of extra work to me. | I agree this functionality is useful, but this is a rather unfortunate | interface choice, in my opinion. For instance, what if I want to copy to | an array: | | int rg[3] = copy_range<int[3]>(aRange); // oops, can't return array if we had template< class T, class Range > T copy_to_seq( const Range& r ) { T t; t.assign(begin(r),end(r)); return t; } support of boost::array<T,size> could be ok. | All these problems are handled nicely by the std::copy algorithm, which | takes an output iterator as a parameter. A general, extensible | range-based algorithm library should take output /ranges/ as parameters, | or even just an output iterator, but this is all beside the point The iterator interface suffers from either being slow or being unsafe, so I don't think that is the way to go either. |. A | range-based copy algorithm should have a different interface and be part | of a complete range-based algorithms library. yes, agreed. We just need somebody to write those special range algorithms. -Thorsten

Thorsten Ottosen wrote:
"Eric Niebler" <eric@boost-consulting.com> wrote in message
| But thinking of this | as a range conversion is the right way to go, IMO, except that it should | be called "range_cast" and it should be explicit.
hm..I dunno, the cast stuff implies some kind of extra work to me.
Could you explain this a bit?
| I agree this functionality is useful, but this is a rather unfortunate | interface choice, in my opinion. For instance, what if I want to copy to | an array: | | int rg[3] = copy_range<int[3]>(aRange); // oops, can't return array
if we had
template< class T, class Range > T copy_to_seq( const Range& r ) { T t; t.assign(begin(r),end(r)); return t; }
support of boost::array<T,size> could be ok.
copy(from, to). That's the idiomatic way of writing a copy function. What you have above is a conversion function, and typically we call those things casts.
| All these problems are handled nicely by the std::copy algorithm, which | takes an output iterator as a parameter. A general, extensible | range-based algorithm library should take output /ranges/ as parameters, | or even just an output iterator, but this is all beside the point
The iterator interface suffers from either being slow or being unsafe, so I don't think that is the way to go either.
You're saying that either an output iterator is range-checked and it's slow, or it's not an it's unsafe. How is an output range any different? It's a sincere question -- I really don't know if output ranges have anything to offer above output iterators.
|. A | range-based copy algorithm should have a different interface and be part | of a complete range-based algorithms library.
yes, agreed.
We just need somebody to write those special range algorithms.
Have you missed it? I've got one in the sandbox, but it needs attention. -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> wrote in message news:4235E56D.2090109@boost-consulting.com... | Thorsten Ottosen wrote: | > "Eric Niebler" <eric@boost-consulting.com> wrote in message | | > | > | But thinking of this | > | as a range conversion is the right way to go, IMO, except that it should | > | be called "range_cast" and it should be explicit. | > | > hm..I dunno, the cast stuff implies some kind of extra work to me. | > | | Could you explain this a bit? well, I expect a "cast" to be more than a copy algorithm. | copy(from, to). That's the idiomatic way of writing a copy function. | What you have above is a conversion function, and typically we call | those things casts. Ok, I just read it a copy-like algorithm. | | > | > | All these problems are handled nicely by the std::copy algorithm, which | > | takes an output iterator as a parameter. A general, extensible | > | range-based algorithm library should take output /ranges/ as parameters, | > | or even just an output iterator, but this is all beside the point | > | > The iterator interface suffers | > from either being slow or being unsafe, so I don't think that is the way to go | > either. | | | You're saying that either an output iterator is range-checked and it's | slow, or it's not an it's unsafe. How is an output range any different? we know its size; we don't know how far an iterator is valid. take something as simple as copy( begin, end, std::back_inserter( a_vector ) ); this is safe, but super dum since we don't take advantage of the size of the range [begin, end) might be know for forward iterators. OTOH, If I pass copy() a naked iterator, it is fast, but not range checked. | It's a sincere question -- I really don't know if output ranges have | anything to offer above output iterators. There is no official Output Range concept, but you can have a Writeable Range to be overwritten. | > | > |. A | > | range-based copy algorithm should have a different interface and be part | > | of a complete range-based algorithms library. | > | > yes, agreed. | > | > We just need somebody to write those special range algorithms. | > | | Have you missed it? I've got one in the sandbox, but it needs attention. no, no, I know you have started from where I left off. And that is goooood :-) But I thought you meant more range algorithms; for example, std::copy is what I would call an iterator algorithm; an range algorithm would demand the output parameter to be be a writable forward range. A range find algorithm would look for a subsequence within a bigger sequence. (Like in the string algorithms) -Thorsten

Pavol Droba wrote:
transform_range is not so crucial, but the copy_range is very important. Without it you cannot easily convert iterator_range to a string for instance. You need to go through iterator-constructor.
So instead of
str=copy_range<std::string>(aRange);
You need to write str=std::string(aRange.begin(), aRange.end());
The second one is not as nice, but what is more important, it disallows you to pass a range by value in a chain. In other words, you cannot write
str=copy_range<std::string>(find_first(input,"helo"));
I agree this functionality is useful, but this is a rather unfortunate interface choice, in my opinion. For instance, what if I want to copy to an array: int rg[3] = copy_range<int[3]>(aRange); // oops, can't return array And what if I want to write into pre-allocated space? What if I want to copy to a non-stl sequence, or one doesn't have a constructor that takes 2 iterators as parameters? This is not general or extensible. All these problems are handled nicely by the std::copy algorithm, which takes an output iterator as a parameter. A general, extensible range-based algorithm library should take output /ranges/ as parameters, or even just an output iterator, but this is all beside the point. A range-based copy algorithm should have a different interface and be part of a complete range-based algorithms library. And that if you *still* want a function that creates a new sequence from a range of a different type, then copy_range() is the wrong name. "copy" implies that you can specify space for the destination. What you have looks more like lexical_cast to me. Maybe this should be range_cast: str = range_cast<std::string>(aRange); -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> writes:
All these problems are handled nicely by the std::copy algorithm, which takes an output iterator as a parameter. A general, extensible range-based algorithm library should take output /ranges/ as parameters, or even just an output iterator, but this is all beside the point. A range-based copy algorithm should have a different interface and be part of a complete range-based algorithms library.
I am about to start writing something like that. In order handle these things generically you have to be able to have ranges defined by start and end iterators of differing types. See, for example, Fusion. The utility of that idea goes beyond tuples, though: iterators into an array can encode their positions and thereby enable full compile-time loop unrolling. For the case of output iterators, one could simply form a range accepting a "universal end iterator" object; a non-dereferenceable forward iterator type that can be compared with anything. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
"Eric Niebler" <eric@boost-consulting.com> writes:
All these problems are handled nicely by the std::copy algorithm, which takes an output iterator as a parameter. A general, extensible range-based algorithm library should take output /ranges/ as parameters, or even just an output iterator, but this is all beside the point. A range-based copy algorithm should have a different interface and be part of a complete range-based algorithms library.
I am about to start writing something like that.
By "something like that" do you mean a complete range-based algorithms library? I have started one at: http://tinyurl.com/4ql8w http://boost-sandbox.sourceforge.net/vault/index.php?action=downloadfile&filename=range_ex.zip&directory=eric_niebler& See http://boost-sandbox.sf.net/libs/range_ex for partial docs. In order handle
these things generically you have to be able to have ranges defined by start and end iterators of differing types. See, for example, Fusion. The utility of that idea goes beyond tuples, though: iterators into an array can encode their positions and thereby enable full compile-time loop unrolling. For the case of output iterators, one could simply form a range accepting a "universal end iterator" object; a non-dereferenceable forward iterator type that can be compared with anything.
Maybe we should put our heads together. -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> writes:
David Abrahams wrote:
"Eric Niebler" <eric@boost-consulting.com> writes:
All these problems are handled nicely by the std::copy algorithm, which takes an output iterator as a parameter. A general, extensible range-based algorithm library should take output /ranges/ as parameters, or even just an output iterator, but this is all beside the point. A range-based copy algorithm should have a different interface and be part of a complete range-based algorithms library. I am about to start writing something like that.
By "something like that" do you mean a complete range-based algorithms library? I have started one at:
http://tinyurl.com/4ql8w http://boost-sandbox.sourceforge.net/vault/index.php?action=downloadfile&filename=range_ex.zip&directory=eric_niebler&
See http://boost-sandbox.sf.net/libs/range_ex for partial docs.
In order handle
these things generically you have to be able to have ranges defined by start and end iterators of differing types. See, for example, Fusion. The utility of that idea goes beyond tuples, though: iterators into an array can encode their positions and thereby enable full compile-time loop unrolling. For the case of output iterators, one could simply form a range accepting a "universal end iterator" object; a non-dereferenceable forward iterator type that can be compared with anything.
Maybe we should put our heads together.
Yes, let's. I'll forward you some other thoughts. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (4)
-
David Abrahams
-
Eric Niebler
-
Pavol Droba
-
Thorsten Ottosen