Formal Review: Boost.RangeEx

Dear Developers and Users, It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009. What is it? +++++++++++ The library provide two very useful extensions to the range library 1. Range-based algorithms. E.g. boost::sort( rng ); which is a convenient wrapper of instead of std::sort( boost::begin(rng), boost::end(rng) ); But the new interface also allows for more expressive code because (on the fly) composition of algorithms suddenly is possible. 2. Range adaptors. E.g. std::vector<int> vec = ...; boost::copy( vec | boost::adaptors::reversed, std::ostream_iterator<int>( std::cout ) ); where the expression "vec | boost::adaptors::reversed" wraps the iterators of the range on the left in reverse iterators. The library provides a wide range (no pun intended) of Range adaptors, and they are a powerful way to create ranges on the fly and pass them to algorithms. Getting the library +++++++++++++++++++ The library may be downloaded from http://www.cs.aau.dk/~nesotto/boost/range_ex.zip or from the Boost vault under "Algorithms". The docs may be browsed online here http://www.cs.aau.dk/~nesotto/boost/libs/range/ Please note that the documentation is integrated with the current Range ilbrary. Therefore the relevant sections for the review is http://www.cs.aau.dk/~nesotto/boost/libs/range/doc/adaptors.html and http://www.cs.aau.dk/~nesotto/boost/libs/range/doc/algorithms.html The code may be browsed here: http://www.cs.aau.dk/~nesotto/boost/boost/range/ Notice the library is header only (exception: the adaptor tokenized() depends on Boost.Regex). Writing a review ++++++++++++++++ If you feel this is an interesting library, then please submit your review to the developer list (preferably), or to the review manager. Here are some questions you might want to answer in your review: - What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? - Did you try to use the library? With what compiler? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick - reading? In-depth study? - Are you knowledgeable about the problem domain? And finally, every review should answer this question: - Do you think the library should be accepted as a Boost library? Be sure to say this explicitly so that your other comments don't obscure your overall opinion. Special considerations ++++++++++++++++++++++ Various RangeEx like libraries have been implemented in the past. You might want to compare with those libraries when you form your oppinion: 1. John Torjo's range lib http://rangelib.synesis.com.au/libs/boost-rangelib-20040913.zip http://torjo.com/rangelib/index.html 2. Adobe's ASL libraries include range-based algorithms: http://stlab.adobe.com/group__algorithm.html I'm looking forward to your review. best regards Thorsten, Review Manager

On 20 Feb 2009, at 12:28, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
Writing a review ++++++++++++++++
If you feel this is an interesting library, then please submit your review to the developer list (preferably), or to the review manager.
The following review is based on reading the documentation, looking through the source, and mapping some code using an existing custom range library to this code.
Here are some questions you might want to answer in your review:
- What is your evaluation of the design?
The library seems to provide a firm foundation to an important area.
- What is your evaluation of the implementation?
The code seems very clean and well designed, there do not seem to be any surprises.
- What is your evaluation of the documentation?
The documentation is simple, but mostly complete. A few small comments: 1) I assume the intention is that most functions delegate to the library implementations (for example range sort -> std::sort). Explicitly stating if this is, or is not, the case would be useful. 2) overwrite's documentation could do with being longer (but I will comment on this further later). 3) The return types of prev_permutation and next_permutation are wrong (should be bool, not void)
- What is your evaluation of the potential usefulness of the library?
Initially very useful at just making functions smaller and neater, I believe the Range Adaptors (which I have not used) will provide further power later.
- Did you try to use the library? With what compiler? Did you have any problems?
I used g++ 4.3 without problems. There are a few areas I would like to see fixed, these are very minor changes. 1) There seems some unnecessary inconsistency in return values, for example generate returns the range, but fill returns void. 2) I would like to see partial_sort return the partially sorted range. 3) The *_heap functions should also return the original range. Certainly sort_heap should for consistency with sort. 4) I have found it useful to have a simple helper function that turns a single value into a range with one value. The larger area I would really like to see improvement in is copy / overwrite. I believe some bigger improvements, particularly in the area of safety, have been missed here. 1) overwrite should state what happens if the output range is overflowed. I would like an exception to be thrown. 2) Why isn't overwrite just an overload of copy? 3) copy_backward should also have a two-range overload. The obvious problem with bounds checking is efficiency. However from my experience, it is easy to check random access ranges by a couple of calls to 'distance', and for bidirectional and forward ranges, the overhead of checking for end of range is very small. If it isn't acceptable to adding checking to copy, I would like to see a checked_copy. I am happy to contribute code to this area.
- How much effort did you put into your evaluation? A glance? A quick - reading? In-depth study?
A quick reading, seeing if it implemented the features in our existing library.
- Are you knowledgeable about the problem domain?
Reasonably.
And finally, every review should answer this question:
- Do you think the library should be accepted as a Boost library?
Yes

Thank you for taking the time to review Boost.RangeEx. On Sat, Feb 21, 2009 at 11:50 AM, Christopher Jefferson < chris@bubblescope.net> wrote:
On 20 Feb 2009, at 12:28, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
Writing a review ++++++++++++++++
If you feel this is an interesting library, then please submit your review to the developer list (preferably), or to the review manager.
The following review is based on reading the documentation, looking through the source, and mapping some code using an existing custom range library to this code.
Here are some questions you might want to answer in your review:
- What is your evaluation of the design?
The library seems to provide a firm foundation to an important area.
- What is your evaluation of the implementation?
The code seems very clean and well designed, there do not seem to be any surprises.
- What is your evaluation of the documentation?
The documentation is simple, but mostly complete. A few small comments: 1) I assume the intention is that most functions delegate to the library implementations (for example range sort -> std::sort). Explicitly stating if this is, or is not, the case would be useful. 2) overwrite's documentation could do with being longer (but I will comment on this further later). 3) The return types of prev_permutation and next_permutation are wrong (should be bool, not void)
These are all valid points that I would like to address should the library be accepted. I am particularly grateful for your correct observation that my documentation contains defects with respect to prev_permutation and next_permutation.
- What is your evaluation of the potential usefulness of the library?
Initially very useful at just making functions smaller and neater, I believe the Range Adaptors (which I have not used) will provide further power later.
- Did you try to use the library? With what compiler? Did you have any problems?
I used g++ 4.3 without problems.
There are a few areas I would like to see fixed, these are very minor changes.
1) There seems some unnecessary inconsistency in return values, for example generate returns the range, but fill returns void.
I agree. This was the last feature to be added, and it appears that my intent to be consistent has not carried through to a consistent implementation. I will address this before release.
2) I would like to see partial_sort return the partially sorted range.
This will be done before release.
3) The *_heap functions should also return the original range. Certainly sort_heap should for consistency with sort.
Absolutely.
4) I have found it useful to have a simple helper function that turns a single value into a range with one value.
I can add this. I'm struggling to think of a good name. What name did you chose?
The larger area I would really like to see improvement in is copy / overwrite. I believe some bigger improvements, particularly in the area of safety, have been missed here.
I suspect that my documentation is not adequate to have communicated my intent. I am of the opinion that copy is rarely the best algorithm to use. Instead of: std::vector<int> v; boost::copy( input | filtered(pred), std::back_inserter(v) ); I prefer the algorithm_ext/push_back.hpp or algorithm_ext/insert.hpp, for example: boost::push_back( v, input | filtered ); I prefer the latter because: 1. It is safer since the insertion into the container is inherently safe and requires no runtime checking. 2. It is typically faster because boost::push_back uses range insertion into the target container. I believe that this addresses typical copy-esque safety nicely, but it does not address the safety of overwrite. The standard-like boost::copy has an output iterator which will often be checked in debug builds using the debugging iterator support of your standard library. With the current interface I cannot think of a way in which to improve the safety of boost::copy however the function exists because it could be that one needs to write to an OutputIterator and does not have an appropriate push_back-able, push_front-able or insert-able target. I am interested in other opinions, since this is an ideal time to re-asses my rationale and address any deficiencies. The overwrite algorithm has an assertion in debug builds that will fire, in release builds the behaviour is undefined. I am of the opinion that this fits the principle of not paying for what you don't use in release configuration, while providing additional safety in debug builds for a very small performance cost.
1) overwrite should state what happens if the output range is overflowed. I would like an exception to be thrown.
I respectfully disagree, although I think we can reach a compromise. I prefer for you to opt-in to something that will incur a performance and space cost. Therefore I am able to provide an overload with this behaviour, but prefer the default mechanism of assertions in debug builds. I invite other opinions on this design decision.
2) Why isn't overwrite just an overload of copy?
I assumed that overload resolution would be problematic since the range and the iterator overloads have the same number of parameters, and the iterator version has to handle raw pointers etc. I may be under-estimating the meta-programming techniques of enable_if. I will perform some experimentation on old compilers and see how well they handle the change.
3) copy_backward should also have a two-range overload.
This sounds like a good idea.
The obvious problem with bounds checking is efficiency. However from my experience, it is easy to check random access ranges by a couple of calls to 'distance', and for bidirectional and forward ranges, the overhead of checking for end of range is very small. If it isn't acceptable to adding checking to copy, I would like to see a checked_copy.
The check for overwrite doesn't even incur the additional cost of an O(N) distance for non-random access ranges. Often the range algorithms can check more cheaply and in most cases I already do so. Since copy uses an OutputIterator as a target, I know of no way to implement the requested check. The overwrite function already contains very cheap bounds checking in debug builds After evaluating my comments, do you still think a checked_copy is necessary? I'm not rejecting your ideas, but providing rationale (that should probably be put into the documentation).
I am happy to contribute code to this area.
Thanks, I'm even happier to take a look at it!
- How much effort did you put into your evaluation? A glance? A quick - reading? In-depth study?
A quick reading, seeing if it implemented the features in our existing library.
- Are you knowledgeable about the problem domain?
Reasonably.
And finally, every review should answer this question:
- Do you think the library should be accepted as a Boost library?
Yes
Thank you again for taking the time to review the library. Best Wishes, Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Neil Groves skrev:
Thank you for taking the time to review Boost.RangeEx.
On Sat, Feb 21, 2009 at 11:50 AM, Christopher Jefferson < chris@bubblescope.net> wrote:
2) Why isn't overwrite just an overload of copy?
I assumed that overload resolution would be problematic since the range and the iterator overloads have the same number of parameters, and the iterator version has to handle raw pointers etc. I may be under-estimating the meta-programming techniques of enable_if. I will perform some experimentation on old compilers and see how well they handle the change.
I guess there are two reasons: 1. overwrite is a more explicit name for what is going on. 2. without concepts, we can't tell if a template argument is a range or an iterator In addition, overwrite should use memcpy() when possible. -Thorsten

on Sun Feb 22 2009, Thorsten Ottosen <thorsten.ottosen-AT-dezide.com> wrote:
Neil Groves skrev:
I assumed that overload resolution would be problematic since the range and the iterator overloads have the same number of parameters, and the iterator version has to handle raw pointers etc. I may be under-estimating the meta-programming techniques of enable_if. I will perform some experimentation on old compilers and see how well they handle the change.
I guess there are two reasons:
1. overwrite is a more explicit name for what is going on.
2. without concepts, we can't tell if a template argument is a range or an iterator
Actually, I think we can automatically detect most of the syntactic requirements for both iterators and ranges. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Neil Groves wrote:
Thank you for taking the time to review Boost.RangeEx.
On Sat, Feb 21, 2009 at 11:50 AM, Christopher Jefferson < chris@bubblescope.net> wrote:
4) I have found it useful to have a simple helper function that turns a single value into a range with one value.
I can add this. I'm struggling to think of a good name. What name did you chose?
It doesn't need to be restricted to a single element. make_range(0, 1, 2, 3) could be a range of 4 elements, being 0, 1, 2 and 3.

Mathias Gaunard skrev:
Neil Groves wrote:
Thank you for taking the time to review Boost.RangeEx.
On Sat, Feb 21, 2009 at 11:50 AM, Christopher Jefferson < chris@bubblescope.net> wrote:
4) I have found it useful to have a simple helper function that turns a single value into a range with one value.
I can add this. I'm struggling to think of a good name. What name did you chose?
It doesn't need to be restricted to a single element.
make_range(0, 1, 2, 3) could be a range of 4 elements, being 0, 1, 2 and 3.
You mean like boost::assign::list_of(0)(1)(2)(3) ? -Thorsten

Thorsten Ottosen wrote:
Mathias Gaunard skrev:
make_range(0, 1, 2, 3) could be a range of 4 elements, being 0, 1, 2 and 3.
You mean like
boost::assign::list_of(0)(1)(2)(3)
?
I naively thought that was equivalent to std::list<int> l; l.push_back(0); l.push_back(1); l.push_back(2); l.push_back(3); but actually, this is exactly what is needed.

Mathias Gaunard skrev:
Thorsten Ottosen wrote:
Mathias Gaunard skrev:
make_range(0, 1, 2, 3) could be a range of 4 elements, being 0, 1, 2 and 3.
You mean like
boost::assign::list_of(0)(1)(2)(3)
?
I naively thought that was equivalent to
std::list<int> l; l.push_back(0); l.push_back(1); l.push_back(2); l.push_back(3);
but actually, this is exactly what is needed.
It is, but the constructed object is also a Range. It you want an efficient version, use cref_list_of<N>(). -Thorsten

Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
Great news! Many thanks to Neil for sticking in there and taking RangeEx to its completion, and to Torsten, who started it all! It's nice to see that it did happen, as I did a non-starter attempt a year or so ago to complete this library. I've looked at the code before when I tried to hack on it, and after some glancing at the docs now, it seems to be everything it could be, and more. The range adaptors are there, and the various return policies have a nice solution as well. It's definitely a library that should go into boost, as it should've been in STL from the start. I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it? Cheers, /Marcus

Dear Marcus, Thank you for evaluating the Boost.RangeEx submission. On Sat, Feb 21, 2009 at 2:47 PM, Marcus Lindblom <macke@yar.nu> wrote:
Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
Great news! Many thanks to Neil for sticking in there and taking RangeEx to its completion, and to Torsten, who started it all! It's nice to see that it did happen, as I did a non-starter attempt a year or so ago to complete this library.
The RangeEx submission has had inputs from many others, including Eric Neibler who is unfairly missing an acknowledgement in my current documentation. I need to address this. Thorsten has continually helped evaluate the various versions of RangeEx too.
I've looked at the code before when I tried to hack on it, and after some glancing at the docs now, it seems to be everything it could be, and more. The range adaptors are there, and the various return policies have a nice solution as well.
It's definitely a library that should go into boost, as it should've been in STL from the start.
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
These are definately missing. I have a bit of a problem here, there are several algorithms I would like to include that are already in <boost/detail/algorithm.hpp>. Before a release I will need to remove any ODR violations when these headers are included together with range. This currently defines container_contains that behaves like a 'contains' function would. I will coordinate and address a solution before release.
Cheers, /Marcus
Best Wishes, Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).

Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold. It doesn't follow that fold should be the only algorithm. I don't like requiring users to use range adaptors instead of providing algorithms like "find_if" and "contains" that just do what they say. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Hi Eric, On Sun, Feb 22, 2009 at 7:55 PM, Eric Niebler <eric@boost-consulting.com>wrote:
Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and
retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold. It doesn't follow that fold should be the only algorithm. I don't like requiring users to use range adaptors instead of providing algorithms like "find_if" and "contains" that just do what they say.
I'm wrestling with the idea of putting some of the "_if" algorithms back into the range algorithms. As soon as I start to allow the inclusion of some "_if" algorithms, I find it hard to come up with a consistent mental model for what should and should not be implemented as an algorithm. I appreciate that this makes some code easier to read. Do you think that all of the standard algorithm _if, _n versions should be present, or just for some of the most common? Are you of the opinion that for each algorithm one writes there should be _if , _n, _copy, _reverse counter-parts? At some point it seems to place too much burden on the algorithm developer. Any inconsistency in the _if availability starts to add burden to users of the library by requiring them to remember which combinations are available. I can only really see that for very common operations we might want to provide _if versions. I would really appreciate clarification of your views on this matter, since I deeply respect you knowledge of this domain, not to mention the work you have already put into this library.
-- Eric Niebler BoostPro Computing http://www.boostpro.com
Best Wishes, Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Eric Niebler wrote:
Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold. It doesn't follow that fold should be the only algorithm.
I assumed the problem with find is that you had to specify rng twice, which is fairly problematic if rng is a temporary with many adaptors in it. That is why I offered this solution, which should be just as efficient as a "contains" function unless I'm missing something, and where rng only appears once. I personally prefer have basic building blocks you can combine to do everything rather than having many functions. That's what languages are for, and RangeEx is kind of a DSEL. Using fold for everything wouldn't be very straightforward, however, nor would it be efficient (it's always a full iteration). It's hardly a building block, more like a general do-everything algorithm.

Eric Niebler skrev:
Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold.
Can you enlight us about what the fold algorithm is? Thansk -Thorsten

Thorsten Ottosen wrote:
Eric Niebler skrev:
Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold.
Can you enlight us about what the fold algorithm is?
Sorry, std::accumulate. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On Mon, Feb 23, 2009 at 11:45 AM, Eric Niebler <eric@boost-consulting.com>wrote:
Thorsten Ottosen wrote:
Eric Niebler skrev:
Mathias Gaunard wrote:
Marcus Lindblom wrote:
I'm just missing basic "contains/contains_if" function, that searches
and retursn a bool on whether an item is in a range. (i.e. find(rng, v) != end(rng) ). I've found those are quite useful. Perhaps this just has another name and I overlooked it?
What about !empty(rng | filtered(_1 == v)).
Sure, and most algorithms can be implemented in terms of fold.
Can you enlight us about what the fold algorithm is?
Sorry, std::accumulate.
Eric, I'm sorry if I'm being a bit slow here, but I've lost the thread somewhat! "...most algorithms can be implemented in terms of fold.", "...what is the fold algorithm?". "...std::accumulate." So most algorithms can be implemented in terms of std::accumulate? Have I understood that right? If so I think I can see your point, but surely empty() in terms of std:: accumulate would be a bit obtuse? Thanks, Rob.

The "fold" is originally a recursion primitive, so that many operations can be defined using this higher order function and appropriate functions. See "Expressiveness of fold" paper by Huttom, Journal of Functional Programming, 1993. -- Alp Mestan In charge of the C++ section on Developpez.com.

Robert Jones wrote:
I'm sorry if I'm being a bit slow here, but I've lost the thread somewhat!
"...most algorithms can be implemented in terms of fold.", "...what is the fold algorithm?". "...std::accumulate."
So most algorithms can be implemented in terms of std::accumulate?
std::accumulate is the name of the algorithm that is typically known as fold in functional programming languages. http://en.wikipedia.org/wiki/Fold_(higher-order_function)
If so I think I can see your point, but surely empty() in terms of std:: accumulate would be a bit obtuse?
It wouldn't be very complicated, just not efficient. accumulate(rng, true, phoenix::val(false));

Robert Jones wrote:
Eric,
I'm sorry if I'm being a bit slow here, but I've lost the thread somewhat!
"...most algorithms can be implemented in terms of fold.", "...what is the fold algorithm?". "...std::accumulate."
So most algorithms can be implemented in terms of std::accumulate?
Have I understood that right?
Yes.
If so I think I can see your point, but surely empty() in terms of std:: accumulate would be a bit obtuse?
And that's my point exactly. I feel that this: boost::find( rng | filtered(pred) ) is more obscure than this: boost::find_if( rng, pred ); Not only that, but I presume their return types are different, too. The first would return filtered iterators, the second the original iterators. Isn't that right? -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
And that's my point exactly. I feel that this:
boost::find( rng | filtered(pred) )
is more obscure than this:
boost::find_if( rng, pred );
Not only that, but I presume their return types are different, too. The first would return filtered iterators, the second the original iterators. Isn't that right?
Indeed. Which is why I suggested that boost::find return a [it, end[ range, rather than just the `it' iterator. That way you can do boost::empty(boost::find(rng | filtered(pred))); instead of auto rng2 = rng | filtered(pred); boost::find(rng2) != boost::end(rng2);

Mathias Gaunard skrev:
Eric Niebler wrote:
And that's my point exactly. I feel that this:
boost::find( rng | filtered(pred) )
is more obscure than this:
boost::find_if( rng, pred );
Not only that, but I presume their return types are different, too. The first would return filtered iterators, the second the original iterators. Isn't that right?
Indeed.
Which is why I suggested that boost::find return a [it, end[ range, rather than just the `it' iterator.
I was of the impression that find() already returned a range, like unique(). Or is the default just different?. IMO, most algorithms returning a single iterator would benefit from this, but I guess we might need to discuss each algorithms in isolation to be sure.
That way you can do
boost::empty(boost::find(rng | filtered(pred)));
instead of
auto rng2 = rng | filtered(pred); boost::find(rng2) != boost::end(rng2);
That would be find with me. I guess the crucial qustion is: - is the main purpose of std::find() to locate an element for the purpose of a. test containment b. partition the sequence somehow c. use the element For cases a. and b., a range return would be fine. For c. a single iterator would be best. As for the different return type, then calling .base() should not be too bad: iterator i = boost::find<return_found>( rng | filtered( pred ) ).base(). But this is still not as elegant as the find_if() overload. I'm wondering if the correct return type can somehow be inferred, or if we can define another adaptor that unwraps some arbitrary expression to the inner most iterators, or to some level defined by the user: iterator i = boost::find( rng | reversed | filtered( pred ) ) | unwrapped(2); // or just "| unwrapped" Having to name range return type would be slightly wierd iterator_range<iterator> r = find( rng | filtered( pred ) ) | unwrapped; but doable. -Thorsten

On Mon, Feb 23, 2009 at 6:54 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
Eric Niebler wrote:
And that's my point exactly. I feel that this:
boost::find( rng | filtered(pred) )
is more obscure than this:
boost::find_if( rng, pred );
Not only that, but I presume their return types are different, too. The first would return filtered iterators, the second the original iterators. Isn't that right?
Indeed.
Which is why I suggested that boost::find return a [it, end[ range, rather than just the `it' iterator.
That way you can do
boost::empty(boost::find(rng | filtered(pred)));
instead of
auto rng2 = rng | filtered(pred); boost::find(rng2) != boost::end(rng2);
If I understand the documentation correctly, you can do boost::find<boost::return_found_end>(rng | filtered(pred)) to get exactly what you want. I find this very useful, but the syntax is a bit verbose. Also, a 'base' adaptor that removes a level of adaptation (i.e. the base_type from a boost::iterator adaptor) would be very useful. BTW, I hope to find the time to write a review myself. -- gpd

Giovanni Piero Deretta skrev:
If I understand the documentation correctly, you can do boost::find<boost::return_found_end>(rng | filtered(pred)) to get exactly what you want. I find this very useful, but the syntax is a bit verbose.
Yeah, I guess getting the specification right is more important first. As for syntax, I guess we could maybe do something like boost::find[_f,_e]( rng | filtered ); Then, other return ranges could be specified in this EDSL as boost::find[_f+_1,e_]( rng | filtered ); boost::find[_f-_1,e_]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered ); boost::find[_b,_f]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered ); and perhaps even allow boost::find[_b+_next(n),_f]( rng | filtered ); etc. -Thorsten

Thorsten Ottosen wrote:
Giovanni Piero Deretta skrev:
If I understand the documentation correctly, you can do boost::find<boost::return_found_end>(rng | filtered(pred)) to get exactly what you want. I find this very useful, but the syntax is a bit verbose.
Me too.
Yeah, I guess getting the specification right is more important first.
As for syntax, I guess we could maybe do something like
boost::find[_f,_e]( rng | filtered );
Then, other return ranges could be specified in this EDSL as
boost::find[_f+_1,e_]( rng | filtered ); boost::find[_f-_1,e_]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered ); boost::find[_b,_f]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered );
Interesting! That's really not half bad.
and perhaps even allow
boost::find[_b+_next(n),_f]( rng | filtered );
Whoa. That could make it too easy to create invalid ranges, but way to think outside the box! -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler skrev:
Thorsten Ottosen wrote:
Then, other return ranges could be specified in this EDSL as
boost::find[_f+_1,e_]( rng | filtered ); boost::find[_f-_1,e_]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered ); boost::find[_b,_f]( rng | filtered ); boost::find[_b,_f+_1]( rng | filtered );
Interesting! That's really not half bad.
and perhaps even allow
boost::find[_b+_next(n),_f]( rng | filtered );
Whoa. That could make it too easy to create invalid ranges, but way to think outside the box!
Yeah, implementing e.g. boost::find[_f+_1,_e]( rng | filtered ); is easier because we only the expression _f+_1,_e to generate a type. With _b+_next(n),_f it would be a type and an embedded number. Notice, however, that one major motivation to let this knowledge be known to the algorithm is, perhaps surprising, that it makes it almost impossible to form invalid ranges --- something that is quite easy otherwise. The cannonical example is already in Neil's docs: """ For example, consider how easy we may erase the duplicates in a sorted container: std::vector<int> vec = ...; boost::erase( vec, boost::unique<boost::return_found_end>( boost::sort(vec) ) ); Notice the use of boost::return_found_end. What if we wanted to erase all the duplicates except one of them? In old-fashioned STL-programming we might write // assume 'vec' is already sorted std::vector<int>::iterator i = std::unique( vec.begin(), vec.end() ); // remember this check or you get into problems if( i != vec.end() ) ++i; vec.erase( i, vec.end() ); The same task may be accomplished simply with boost::erase( vec, boost::unique<boost::return_next_end>(vec) ); """ Now, as for the possibility that _b+_next(n),_f-_prev(m) would generate invalid ranges, then I guess that is true. But the algorithm should easily be able to catch this with an assertion. If you adjusted the range manually, afterwards, you would have the same possibility to generate invalid ranges and not get the assertion that the library provides (of course, the debug-iterators might catch this, if your library supports them). -Thorsten

On Fri, 2009-02-20 at 13:28 +0100, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
That's great news! I've much anticipated this library, since I'd hand-rolled similar components a long time ago. I hope to submit a formal review, but before I do, I hope to understand the motivation for the design better. I hope this is allowed within a formal review. This is related to my question at <http://lists.boost.org/Archives/boost/2008/07/140514.php>. I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly: I was hoping for more lazy views on ranges, especially after reading that the library allows for a "seamless funtional-style programming". Shouldn't there be a generated(), randomly_shuffled(), sorted(), intersected(), merged(), etc.? (I implemented lazy set operations implemented and I think it's quite possible.) In short, I seem to see unexploited symmetries. Am I missing things? Thanks for any replies, Rogier

Dear Sir, On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com>wrote:
On Fri, 2009-02-20 at 13:28 +0100, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
That's great news! I've much anticipated this library, since I'd hand-rolled similar components a long time ago. I hope to submit a formal review, but before I do, I hope to understand the motivation for the design better. I hope this is allowed within a formal review. This is related to my question at <http://lists.boost.org/Archives/boost/2008/07/140514.php>.
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion. I do provide both alternatives, for example boost::make_uniqued_range(rng) is equivalent to rng | uniqued
I was hoping for more lazy views on ranges, especially after reading that the library allows for a "seamless funtional-style programming". Shouldn't there be a generated(), randomly_shuffled(), sorted(), intersected(), merged(), etc.? (I implemented lazy set operations implemented and I think it's quite possible.)
The library is intended to be extended both by myself as core needs become clear, and by library users. The algorithms, supported range types and adaptors may be implemented by users of the library without changing it. Many lazy adaptors are desirable. I would continue to add more as time permits. My focus is on producing a good foundation for this version. The reviews are giving me a clearer need of what people want from the library.
In short, I seem to see unexploited symmetries. Am I missing things?
There is much unexploited because there is so much one can do. Once this is out in the wild, I hope to write more, and obtain submissions of adaptors and algorithms. As the review has continued I wonder if the algorithms should actually be in Boost.Algorithm and use Boost.Range.
Thanks for any replies, Rogier
Thank you for your time, Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Wed, Feb 25, 2009 at 5:00 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Sir,
On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com>wrote:
On Fri, 2009-02-20 at 13:28 +0100, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
That's great news! I've much anticipated this library, since I'd hand-rolled similar components a long time ago. I hope to submit a formal review, but before I do, I hope to understand the motivation for the design better. I hope this is allowed within a formal review. This is related to my question at <http://lists.boost.org/Archives/boost/2008/07/140514.php>.
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion.
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make 'some_functor(range) ' eqivalent to 'range | some_functor' (i would actually go for the syntax 'range | some_functor(_r)', where _r is a placeholder for the range; This is usefull for adaptors that actually have other parameters in addition to the range itself) -- gpd

Dear Giovanni, On Wed, Feb 25, 2009 at 4:21 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:00 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Sir,
On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com wrote:
On Fri, 2009-02-20 at 13:28 +0100, Thorsten Ottosen wrote:
Dear Developers and Users,
It's my pleasure to announce that the review of Neil Groves' RangeEx library starts today and lasts until March 3, 2009.
That's great news! I've much anticipated this library, since I'd hand-rolled similar components a long time ago. I hope to submit a formal review, but before I do, I hope to understand the motivation for the design better. I hope this is allowed within a formal review. This is related to my question at <http://lists.boost.org/Archives/boost/2008/07/140514.php>.
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion.
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different. I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision. I like the make_XXX_range because I instantly recognise that this creates a range adaptor. However I personally strongly prefer the '|' syntax. I'm trying to give everyone what they want. I am reluctant to use exactly the same name, but I am open to change from make_XXX_range. It is of course absolutely trivial for anyone to provide exactly their own wrapper function to please their own taste.
'some_functor(range) ' eqivalent to 'range | some_functor'
(i would actually go for the syntax 'range | some_functor(_r)', where _r is a placeholder for the range; This is usefull for adaptors that actually have other parameters in addition to the range itself)
I would always prefer the '|' syntax to any normal function invocation however it is spelled. In my opinion, it combines much more elegantly.
-- gpd
Thanks for the comments, Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Wed, Feb 25, 2009 at 5:32 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Giovanni,
On Wed, Feb 25, 2009 at 4:21 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:00 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Sir,
On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com wrote:
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion.
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
IMHO they are the same thing. I think of operator() in ' a | b' as 'b(a)', i.e as a generic apply operator.
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this: total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) ); i.e. I don't have a strong distinction between adaptor and algorithms. <snip>
'some_functor(range) ' eqivalent to 'range | some_functor'
(i would actually go for the syntax 'range | some_functor(_r)', where _r is a placeholder for the range; This is usefull for adaptors that actually have other parameters in addition to the range itself)
I would always prefer the '|' syntax to any normal function invocation however it is spelled. In my opinion, it combines much more elegantly.
I use the '|' syntax a lot when writing long pipelines because it avoids long nested expressions (it also reads better left to right). But when I need a simple map or filter before passing a range to an algorithm I usually just go for the function call variant. Also, if you have a range of ranges and want to map the nested ranges, having 'transformed' as a function object becomes very useful: range_of_ranges | transformed(transformed(f))); (and BTW, what about renaming 'transformed' -> 'map' and 'filtered' -> 'filter') -- gpd

Dear Giovanni, On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:32 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Giovanni,
On Wed, Feb 25, 2009 at 4:21 PM, Giovanni Piero Deretta < gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:00 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Sir,
On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com wrote:
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion.
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
IMHO they are the same thing. I think of operator() in ' a | b' as 'b(a)', i.e as a generic apply operator.
Yes, I think I might have over-emphasised the difference. Would you prefer the function overload to be in the boost::adaptors namespace, the boost namespace or something else?
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
<snip>
'some_functor(range) ' eqivalent to 'range | some_functor'
(i would actually go for the syntax 'range | some_functor(_r)', where _r is a placeholder for the range; This is usefull for adaptors that actually have other parameters in addition to the range itself)
I would always prefer the '|' syntax to any normal function invocation however it is spelled. In my opinion, it combines much more elegantly.
I use the '|' syntax a lot when writing long pipelines because it avoids long nested expressions (it also reads better left to right). But when I need a simple map or filter before passing a range to an algorithm I usually just go for the function call variant.
Also, if you have a range of ranges and want to map the nested ranges, having 'transformed' as a function object becomes very useful:
range_of_ranges | transformed(transformed(f)));
(and BTW, what about renaming 'transformed' -> 'map' and 'filtered'
-> 'filter')
I don't think the suggestion to change transformed is a good idea. transformed is more obviously related to transform, and map is already a standard container. filtered to filter doesn't sound bad, not does uniqued to unique, as long as it doesn't clash with other names. Perhaps this suggests that they should be kept in the boost::adaptors namespace.
-- gpd
Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Neil Groves skrev:
Dear Giovanni,
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision. FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
Hm. What is the utility of _r above? -Thorsten

On Wed, Feb 25, 2009 at 6:51 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Neil Groves skrev:
Dear Giovanni,
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
Hm. What is the utility of _r above?
Its a placeholder. If filter(range, f) returns a filtered iterator range, filter(_r, f) returns an unary function object from ranges to filtered iterator ranges. _r It is the more or less the equivalent of _1 in 'bind(filter, _1, f)'. -- gpd

Dear Giovanni,
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision. FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Also, what does map(-) do? -Thorsten

On Wed, Feb 25, 2009 at 6:53 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Dear Giovanni,
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Also, what does map(-) do?
just another name for transformed: returns a transform iterator range. I just like the 'map' name better. -- gpd

Giovanni Piero Deretta skrev:
On Wed, Feb 25, 2009 at 6:53 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Dear Giovanni,
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision. FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms. Also, what does map(-) do?
just another name for transformed: returns a transform iterator range. I just like the 'map' name better.
It's common in other languages, right? -Thorsten

Hi, ----- Original Message ----- From: "Neil Groves" <neil@grovescomputing.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 6:29 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
Dear Giovanni,
On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:32 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Giovanni,
On Wed, Feb 25, 2009 at 4:21 PM, Giovanni Piero Deretta < gpderetta@gmail.com
wrote:
On Wed, Feb 25, 2009 at 5:00 PM, Neil Groves <neil@grovescomputing.com> wrote:
Dear Sir,
On Wed, Feb 25, 2009 at 3:36 PM, Rogier van Dalen <rogiervd@gmail.com wrote:
I am not sure I understand the motivation for the operator| syntax. Why couldn't vec | boost::adaptors::reversed | boost::adaptors::unique be written boost::adaptors::unique (boost::adaptors::reversed (vec)) ? I think this contrast would be fairer than what the documentation currently has. Is there a reason why it is not possible to provide both syntaxes? The pipe syntax is nice, but sort of restricts operations to 1 range in, 1 range out. Relatedly:
In the adaptors section the preference for operator| is documented. It simply chains better in my opinion.
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
IMHO they are the same thing. I think of operator() in ' a | b' as 'b(a)', i.e as a generic apply operator.
Yes, I think I might have over-emphasised the difference. Would you prefer the function overload to be in the boost::adaptors namespace, the boost namespace or something else?
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
I agree, the introduction of the parameter (_r) makes the library homogeneus. An adaptor is a functor with a placeholder for the input parameter.
<snip>
'some_functor(range) ' eqivalent to 'range | some_functor'
(i would actually go for the syntax 'range | some_functor(_r)', where _r is a placeholder for the range; This is usefull for adaptors that actually have other parameters in addition to the range itself)
I would always prefer the '|' syntax to any normal function invocation however it is spelled. In my opinion, it combines much more elegantly.
I use the '|' syntax a lot when writing long pipelines because it avoids long nested expressions (it also reads better left to right). But when I need a simple map or filter before passing a range to an algorithm I usually just go for the function call variant.
Also, if you have a range of ranges and want to map the nested ranges, having 'transformed' as a function object becomes very useful:
range_of_ranges | transformed(transformed(f)));
(and BTW, what about renaming 'transformed' -> 'map' and 'filtered'
-> 'filter')
I don't think the suggestion to change transformed is a good idea. transformed is more obviously related to transform, and map is already a standard container.
What about simply 'transform'?
filtered to filter doesn't sound bad, not does uniqued to unique, as long as it doesn't clash with other names. Perhaps this suggests that they should be kept in the boost::adaptors namespace.
If you put then in a specific namespace you avoid clashing. If you put them in namespace boost, you need to use concepts or (enable_if). The library contains already some functions at the boost level as begin, end and nobody complains. The algorith library use three level namespace boost::algorithm::sequence for for_each_if. So the user needs boost::algorithm::sequence::for_each_if(range, f,cnd); In other words, should the transform function in Boost.Fusion and in Boost.Range and in Boost.Algorithm, and .. live in different namespaces? IMHO, Not. All these names must reside in the same namespace and the library must allows to make partial specialization of these functions. Waiting for partial function template specialization in Boost.Interthreads I have used the following workarround: namespace boost { namespace result_of { template <typename ACT> struct interrupt { typedef void type; }; } namespace partial_specialization_workaround { template< typename ACT > struct interrupt { static typename result_of::template interrupt<ACT>::type apply( ACT& act ) { return act.interrupt(); } }; } template <typename ACT> typename boost::enable_if<interthreads::has_interrupt_member<ACT>, void >::type interrupt(ACT& act) { return partial_specialization_workaround::interrupt<ACT>::apply(act); } } // namespace boost The user will just do boost::interrupt(act). If another boost library want to overload interrupt(T) for other types that do not provide a member interrupt function, the developer needs just to template <typename T> typename boost::enable_if<a_specific_condition<T>, void >::type interrupt(T& act) { // ... } Of course this will make the compiler to take more time, but at the end If I have an algorithm that works with let me say boost::interthreads:interrupt and boost::intertoto::interrupt how can I make this algorithm generic? Best, Vicente

On Wed, Feb 25, 2009 at 7:07 PM, Vicente Botet <vicente.botet@wanadoo.fr> wrote:
From: "Neil Groves" <neil@grovescomputing.com>
On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
(and BTW, what about renaming 'transformed' -> 'map' and 'filtered'
-> 'filter')
I don't think the suggestion to change transformed is a good idea. transformed is more obviously related to transform, and map is already a standard container.
What about simply 'transform'?
If the range variant of std::transform were named 'transform_inplace', it would work (I wouldn't want two 'transform' with different meaning in boost, even in different namespaces), but I do not know if anybody else like it.
filtered to filter doesn't sound bad, not does uniqued to unique, as long as it doesn't clash with other names. Perhaps this suggests that they should be kept in the boost::adaptors namespace.
again, the 'unique' name is already taken by the std::unique inplace semantics, unless the boost variant were to be called unique_inplace.
If you put then in a specific namespace you avoid clashing. If you put them in namespace boost, you need to use concepts or (enable_if). The library contains already some functions at the boost level as begin, end and nobody complains. The algorith library use three level namespace boost::algorithm::sequence for for_each_if. So the user needs
boost::algorithm::sequence::for_each_if(range, f,cnd);
In other words, should the transform function in Boost.Fusion and in Boost.Range and in Boost.Algorithm, and .. live in different namespaces?
Right, I forgot that fusion uses the 'transform' name for a non inplace map. And there is also mpl::transform with the same view semantics [*]. So there is already an existing practice in boost and maybe the inplace std::transform should really be called transform_inplace ;-) [*] of course, mpl doesn't really have a choice.
IMHO, Not. All these names must reside in the same namespace and the library must allows to make partial specialization of these functions. [snip sfinae example]
The user will just do boost::interrupt(act).
If another boost library want to overload interrupt(T) for other types that do not provide a member interrupt function, the developer needs just to
template <typename T> typename boost::enable_if<a_specific_condition<T>, void >::type interrupt(T& act) { // ... }
I think this would break horribly if two libraries define slightly overlapping conditions, so I do not think it is a great idea. It might work with non auto concepts in c++0x. -- gpd

----- Original Message ----- From: "Giovanni Piero Deretta" <gpderetta@gmail.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 7:59 PM Subject: Re: [boost] Formal Review: Boost.RangeEx On Wed, Feb 25, 2009 at 7:07 PM, Vicente Botet <vicente.botet@wanadoo.fr> wrote:
If another boost library want to overload interrupt(T) for other types that do not provide a member interrupt function, the developer needs just to
template <typename T> typename boost::enable_if<a_specific_condition<T>, void
::type interrupt(T& act) { // ... }
I think this would break horribly if two libraries define slightly overlapping conditions, so I do not think it is a great idea. It might work with non auto concepts in c++0x.
Do you prefer to have the same algorithm in different namespaces? Overlaping conditions are possible in the same way that overloads can make ambigous a call. The single difference is that instead of typing your arguments you are adding constraints respect to the concept the parameter is a model of. That is, Imagine we define a function working for Threads at the boost scope interrupt(boost::thread& act); Now I define a joiner that can also be interruped template <typename T> interrupt(boost::interthreads::joiner<T>& act); Both functions have a common semantic. Overloading should works well, but we need to do partial specialization. Every joiner<T> is interrupted the same way, no mater the type T. Am I missing something? Vicente

Vicente Botet skrev:
Dear Giovanni,
On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
Yes, I think I might have over-emphasised the difference. Would you prefer the function overload to be in the boost::adaptors namespace, the boost namespace or something else?
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision. FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
I agree, the introduction of the parameter (_r) makes the library homogeneus. An adaptor is a functor with a placeholder for the input parameter.
I my examples I removed the _r from the syntax. I don't see the point in having them, if the code can work without them. Are they really needed? -Thorsten

----- Original Message ----- From: "Thorsten Ottosen" <thorsten.ottosen@dezide.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 10:11 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
Vicente Botet skrev:
On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
I agree, the introduction of the parameter (_r) makes the library homogeneus. An adaptor is a functor with a placeholder for the input parameter.
I my examples I removed the _r from the syntax. I don't see the point in having them, if the code can work without them. Are they really needed?
Not if you don't need to use a different name to make it work. Vicente

On Wed, Feb 25, 2009 at 10:11 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Vicente Botet skrev:
Dear Giovanni,
On Wed, Feb 25, 2009 at 5:13 PM, Giovanni Piero Deretta <gpderetta@gmail.com
wrote:
Yes, I think I might have over-emphasised the difference. Would you prefer the function overload to be in the boost::adaptors namespace, the boost namespace or something else?
I had considered creating a range adaptor to be highly different to applying an algorithm, perhaps I over-emphasised this distinction when making the decision.
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
Yes, I'm sold!
I agree, the introduction of the parameter (_r) makes the library homogeneus. An adaptor is a functor with a placeholder for the input parameter.
I my examples I removed the _r from the syntax. I don't see the point in having them, if the code can work without them. Are they really needed?
No, not necessarily: 1: transformed_range<...> r = transformed(range, f); 2: transformed_range<...> r = range | transformed(f); you could have the single argument variant of transformed (and in general the variant with arity N-1 of an adaptor) return 'something' that can function in a pipe. I would expect that 'something' to be a full fledged unary function object (the equivalent of bind(transformed, _1, f)) that can also work outside of pipes or even stored: boost::function<transformed_range<range_t, f_t>(range_t)> mapper = transformed(f); // or alternatively boost::result_of<mapper_t(f_t)>::type mapper = transformed(f); BOOST_FOREACH( v_t v, range | mapper) { ... } Which implies that transformed (and other adaptors) should store 'f' (and other parameters) by value (modulo boost::ref and friends). ( BTW If you decide to adopt this solution, I suggest putting the range parameter last. This way It would sort of simulate the automatic currying syntax of some functional languages (where omitting the last m parameters of n-arity function makes it a higher order function that returns an (n-m) arity function. ) This approach has unfortunately a big problem. If you have adaptors with optional arguments, without concepts it is an iteresting problem figuring out which overload the user wants: merged(r1, r2); // uses operator< as comparator merged(r1, r2, cmp); // uses cmp as comparator r1 | merged(r2, cmp); // interesting problem here! r1 | merged(_r, r2, cmp); // much better r2 | merged(r1, _r, cmp); // we have a choice of the argument to bind r1 ^merged(_r1, _r2, cmp)^ r2; // probably beyond the scope of the library ;) In my code, I use '_' as a placeholder, instead of '_r'. -- gpd

On Wed, 2009-02-25 at 23:01 +0100, Giovanni Piero Deretta wrote:
( BTW If you decide to adopt this solution, I suggest putting the range parameter last. This way It would sort of simulate the automatic currying syntax of some functional languages (where omitting the last m parameters of n-arity function makes it a higher order function that returns an (n-m) arity function. )
Surely to get a currying effect, you'd put the range parameter first? Or am I misremembering my functional programming classes?
This approach has unfortunately a big problem. If you have adaptors with optional arguments, without concepts it is an iteresting problem figuring out which overload the user wants:
merged(r1, r2); // uses operator< as comparator merged(r1, r2, cmp); // uses cmp as comparator r1 | merged(r2, cmp); // interesting problem here! r1 | merged(_r, r2, cmp); // much better r2 | merged(r1, _r, cmp); // we have a choice of the argument to bind r1 ^merged(_r1, _r2, cmp)^ r2; // probably beyond the scope of the library ;)
In my code, I use '_' as a placeholder, instead of '_r'.
Much as I hate to say it, this is an argument for naming function-style and operator|-style adaptors differently. But isn't it possible to use SFINAE to disambiguate? Rng::iterator would exist and Cmp::iterator wouldn't, right? Cheers, Rogier

Giovanni Piero Deretta skrev:
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
The question is we should have weak or no distinction, or perhaps a little strong destinction. I must admit, the code looks nice. What about something like total = r | filter(f) | map(m) -> accumulate(zero, a); or total = r | filter(f) | map(m) >> accumulate(zero, a); ? The is a problem when we just want to apply several algorithms: boost::erase( cont, boost::unique( boost::sort(cont) ) ); How do we express this? Perhaps boost::erase( cont, cont >> sort >> unique ); ? -Thorsten

On Wed, Feb 25, 2009 at 7:04 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Giovanni Piero Deretta skrev:
FWIW, I have code like this:
total = ( r | filter(_r, f) | map(_r, m) | accumulate(_r, zero, a) );
i.e. I don't have a strong distinction between adaptor and algorithms.
The question is we should have weak or no distinction, or perhaps a little strong destinction. I must admit, the code looks nice.
What about something like
total = r | filter(f) | map(m) -> accumulate(zero, a);
how would you make this work (leaving the framework extensible to new algorithm)? Maybe you meant ->*.
or
total = r | filter(f) | map(m) >> accumulate(zero, a);
I sort of like the >> syntax (btw, I think that there are functional languages that use the same syntax for function chaining), but I do not see the necessity of distinguishing between '|' and '>>'. Just use one.
?
The is a problem when we just want to apply several algorithms:
boost::erase( cont, boost::unique( boost::sort(cont) ) );
I woudn't put many inplace algorithm in the same expression nor encourage users to do it. The unique->erase idiom is common, on the other hand sort has little reason being in that expression. -- gpd

Giovanni Piero Deretta skrev:
On Wed, Feb 25, 2009 at 7:04 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
What about something like
total = r | filter(f) | map(m) -> accumulate(zero, a);
how would you make this work (leaving the framework extensible to new algorithm)? Maybe you meant ->*.
or
total = r | filter(f) | map(m) >> accumulate(zero, a);
I sort of like the >> syntax (btw, I think that there are functional languages that use the same syntax for function chaining), but I do not see the necessity of distinguishing between '|' and '>>'. Just use one.
Well, since there is a semantical difference, and a large one, I'm leaning towards that we should use two operators. Especially is we will have both adaptor::transform and transform as an algorithm.
?
The is a problem when we just want to apply several algorithms:
boost::erase( cont, boost::unique( boost::sort(cont) ) );
I woudn't put many inplace algorithm in the same expression nor encourage users to do it. The unique->erase idiom is common, on the other hand sort has little reason being in that expression.
Unique won't work properly if you don'e have a sorted sequence, AFAIR -Thorsten

On Wed, Feb 25, 2009 at 7:39 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Giovanni Piero Deretta skrev:
On Wed, Feb 25, 2009 at 7:04 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
What about something like
total = r | filter(f) | map(m) -> accumulate(zero, a);
how would you make this work (leaving the framework extensible to new algorithm)? Maybe you meant ->*.
or
total = r | filter(f) | map(m) >> accumulate(zero, a);
I sort of like the >> syntax (btw, I think that there are functional languages that use the same syntax for function chaining), but I do not see the necessity of distinguishing between '|' and '>>'. Just use one.
Well, since there is a semantical difference, and a large one, I'm leaning towards that we should use two operators.
Still not convinced. You might have a case if the last algorithm in the above chain were a mutating one, like sort, but I used accumulate which is a pure function, like map and filter. I do not see much of a difference. Similarly for_each, which while not a pure fuction, it is often used in a way that doesn't mutate its arguments. So no, I do not think that there is a large difference between adaptors and algorithms. I only see the usual difference between mutating and non mutating algorithms.
Especially is we will have both adaptor::transform and transform as an algorithm.
Again, I think that the mutating and not mutating algorithm should have different names.
?
The is a problem when we just want to apply several algorithms:
boost::erase( cont, boost::unique( boost::sort(cont) ) );
I woudn't put many inplace algorithm in the same expression nor encourage users to do it. The unique->erase idiom is common, on the other hand sort has little reason being in that expression.
Unique won't work properly if you don'e have a sorted sequence, AFAIR
IIRC there is no requirement for the range to be sorted: unique will just remove all but one elements on a run of equivalent elements. But I agree that's not how it is often used. Anyways, probably that's more of a case for encapsulating the sort/unique/erase sequence in a single algorithm. -- gpd

Giovanni Piero Deretta skrev:
On Wed, Feb 25, 2009 at 7:39 PM, Thorsten Ottosen
Again, I think that the mutating and not mutating algorithm should have different names.
And how is that different from the way adaptors have been given different names from algorithm in the library's current implementation?
IIRC there is no requirement for the range to be sorted: unique will just remove all but one elements on a run of equivalent elements. But I agree that's not how it is often used.
Anyways, probably that's more of a case for encapsulating the sort/unique/erase sequence in a single algorithm.
erase_duplicates(-) ?

Neil Groves skrev:
Dear Giovanni,
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
There are very different. The past tense is used for adaptors that has O(1) complexity and merely wraps the range's iterators in a new type of iterators. An algorithm like boost::unique(-) really modifies the range, | boost::adaptors::uniqued, does not. -Thorsten

On Wed, Feb 25, 2009 at 6:46 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Neil Groves skrev:
Dear Giovanni,
I do provide both alternatives, for example
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
IMHO the make_... syntax for range adaptors is horrible (i don't even like the past tense, why not just unique?). But why use two different names in the first place? Why not make
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
There are very different. The past tense is used for adaptors that has O(1) complexity and merely wraps the range's iterators in a new type of iterators.
An algorithm like
boost::unique(-)
really modifies the range, | boost::adaptors::uniqued, does not.
Completely agree, unque (as in std::unique) and uniqued are two different functions and should be named differently. On the other hand uniqued and make_uniqued_range are the same thing and should have the same name. I just don't like either 'uniqued' or 'make_uniqued_range'. I think that the correct name for 'uniqued' should be 'unique' and the name for the std derived 'unique' should be 'unique_inplace', but I guess this would confuse people. -- gpd

----- Original Message ----- From: "Giovanni Piero Deretta" <gpderetta@gmail.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 7:06 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
On Wed, Feb 25, 2009 at 6:46 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
Neil Groves skrev:
I simply dislike lots of overloading particularly where the semantics are different. Perhaps the semantics aren't that different.
There are very different. The past tense is used for adaptors that has O(1) complexity and merely wraps the range's iterators in a new type of iterators.
An algorithm like
boost::unique(-)
really modifies the range, | boost::adaptors::uniqued, does not.
Completely agree, unque (as in std::unique) and uniqued are two different functions and should be named differently. On the other hand uniqued and make_uniqued_range are the same thing and should have the same name. I just don't like either 'uniqued' or 'make_uniqued_range'.
I think that the correct name for 'uniqued' should be 'unique' and the name for the std derived 'unique' should be 'unique_inplace', but I guess this would confuse people.
As unique exist already, what about unique_view. With the _view suffix we state clearly that the evaluation is lazy. Vicente

vicente.botet skrev:
As unique exist already, what about unique_view. With the _view suffix we state clearly that the evaluation is lazy.
Just to make sure there is no confusion on this point, then the orignal _ed suffix was supposed to mean "lazy". That doesn't mean it is perfect naming scheme, of course. -Thorsten

Thorsten, On Wed, Feb 25, 2009 at 9:07 PM, Thorsten Ottosen < thorsten.ottosen@dezide.com> wrote:
vicente.botet skrev:
As unique exist already, what about unique_view. With the _view suffix we
state clearly that the evaluation is lazy.
Just to make sure there is no confusion on this point, then the orignal _ed suffix was supposed to mean "lazy". That doesn't mean it is perfect naming scheme, of course.
I strongly prefer the _view suffix. I wish I had thought of it before the review!
-Thorsten
So is everyone happy if I: 1. Change the 'ed' adaptors to _view 2. name the operator | alternatives with the same name as the adaptor? Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Hello Neil, first of all thank you very much for RangeEx! It will replace a lot of code in our projects.
So is everyone happy if I: 1. Change the 'ed' adaptors to _view
I liked the _ed, but have no strong opinion.
2. name the operator | alternatives with the same name as the adaptor?
At the risk of being late and redundant, I have a pretty strong dislike for overloading operator| or any other operator. I prefer the straight function syntax, with arguments in front so chains can at least be read from right to left, with the adaptors close to their arguments: transformed( func, filtered( func2, uniqued( original_range ) ) ) The reason I don't like overloading is of fundamental nature. I don't see (unary) functions on ranges as vastly different from (unary) functions on anything else. It may also be nicer to write 3.41 | sqrt() | cos()| pow(3) to express pow( cos( sqrt( 3.41 ) ), 3 ) but C++ has the latter syntax for function invocations, and I am not sure whether a library like RangeEx is the place to change this. I think so in particular because RangeEx IMHO is a real candidate for inclusion into the standard, where any non-standard syntax may be frowned upon. The scope of streams, largely text processing, is arguably much narrower than that of ranges, so operator<< is easier to justify there, and the stream library is probably not the brightest spot of the STD library anyway. Long chains were particularly necessary as long as you could not give names to intermediate results because of their obscure types. But with the auto keywork in C++0x, this problem will soon disappear, and with it the justification for super-long chains. Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229

Arno Schödl skrev:
Hello Neil,
first of all thank you very much for RangeEx! It will replace a lot of code in our projects.
So is everyone happy if I: 1. Change the 'ed' adaptors to _view
I liked the _ed, but have no strong opinion.
2. name the operator | alternatives with the same name as the adaptor?
At the risk of being late and redundant,
Well, it a review, and as such I don't see how you views could be late. We don't make quick decisions, and I might gather special issues later so we can vote on how to resolve a particular issue.
Long chains were particularly necessary as long as you could not give names to intermediate results because of their obscure types. But with the auto keywork in C++0x, this problem will soon disappear, and with it the justification for super-long chains.
Well, maybe. There, however, a difference in that when you cache ni a local variable, you might have to move manually (by calling std::move/or using move iterators), whereas it might be done automatically if you don't cache. -Thorsten

----- Original Message ----- From: "Neil Groves" <neil@grovescomputing.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 10:15 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
Thorsten,
On Wed, Feb 25, 2009 at 9:07 PM, Thorsten Ottosen < thorsten.ottosen@dezide.com> wrote:
vicente.botet skrev:
As unique exist already, what about unique_view. With the _view suffix we
state clearly that the evaluation is lazy.
Just to make sure there is no confusion on this point, then the orignal _ed suffix was supposed to mean "lazy". That doesn't mean it is perfect naming scheme, of course.
I strongly prefer the _view suffix. I wish I had thought of it before the review!
-Thorsten
So is everyone happy if I: 1. Change the 'ed' adaptors to _view 2. name the operator | alternatives with the same name as the adaptor?
The _view suffix has been used by two very old functional libraries as the VTL, View Template Library (http://www.zib.de/weiser/vtl) and Views (http://www.zeta.org.au/~jon/STL/views/doc/views.html). Boost.Range shares a lot of with these libraries. I like this naming convention. It is OK from my side. Vicente

Neil Groves wrote:
So is everyone happy if I: 1. Change the 'ed' adaptors to _view
I wouldn't be too happy about it. The lazy adaptors being preferred over the eager in-place algorithms, the name of the adaptors should be as short as possible, especially since chaining of adaptors can lead to fairly long expressions.
2. name the operator | alternatives with the same name as the adaptor?
Sure, that's good.

Dear Mathias, On Thu, Feb 26, 2009 at 12:33 PM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
Neil Groves wrote:
So is everyone happy if I:
1. Change the 'ed' adaptors to _view
I wouldn't be too happy about it. The lazy adaptors being preferred over the eager in-place algorithms, the name of the adaptors should be as short as possible, especially since chaining of adaptors can lead to fairly long expressions.
I'm not going to do this change. It appears that more people are happy with the current 'ed' ending than a '_view' ending. I don't have a strong opinion at all on this issue, but it's clear the majority prefer 'ed'. It's great to have reviewers so I can stop guessing.
2. name the operator | alternatives with the same name as the adaptor?
Sure, that's good.
This is already done, but some people strongly dislike the make_XXX_range syntax. It has been proposed to make it the function that creates the adaptor the same name as the adaptor itself. This should work, but I can't quite get comfortable with this idea. Does anyone else share my concern that there might be confusion by sharing the same name? My current impression is that most reviewers would prefer the same name. I wonder if some people are being quiet. Perhaps using 'uniqued' for the adaptor and 'unique_view' for the function version would be acceptable? Please can I invite comments with regard to the operator | and alternative naming. I currently don't feel that we have reached a broadly acceptable agreement on this issue. Thank you all for your reviews. Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Neil Groves wrote:
2. name the operator | alternatives with the same name as the adaptor? Sure, that's good.
This is already done, but some people strongly dislike the make_XXX_range syntax. It has been proposed to make it the function that creates the adaptor the same name as the adaptor itself.
I thought make_transformed_range would be renamed to transformed.

Hi Neil, On Thu, Feb 26, 2009 at 1:41 PM, Neil Groves <neil@grovescomputing.com>wrote:
Dear Mathias,
On Thu, Feb 26, 2009 at 12:33 PM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
Neil Groves wrote:
So is everyone happy if I:
1. Change the 'ed' adaptors to _view
I wouldn't be too happy about it. The lazy adaptors being preferred over the eager in-place algorithms, the name of the adaptors should be as short as possible, especially since chaining of adaptors can lead to fairly long expressions.
I'm not going to do this change. It appears that more people are happy with the current 'ed' ending than a '_view' ending. I don't have a strong opinion at all on this issue, but it's clear the majority prefer 'ed'. It's great to have reviewers so I can stop guessing.
As nobody has mentioned it yet, I would like to point out that boost:gil already uses the _view ending. Quoting from the documentation: An image view is a generalization of STL's range concept to multiple dimensions. Similar to ranges (and iterators), image views are shallow, don't own the underlying data and don't propagate their constness over the data. For example, a constant image view cannot be resized, but may allow modifying the pixels. GIL uses names like greyscale_view, nth_channel_view, planar_rgb_view. Using 'ed' style naming one could use names like greyscale_converted, nth_channel_selected. I am not sure what naming scheme is better but some consistency within boost would be nice. Kai Schroeder

Kai Schroeder wrote:
Hi Neil,
On Thu, Feb 26, 2009 at 1:41 PM, Neil Groves <neil@grovescomputing.com>wrote:
Dear Mathias,
On Thu, Feb 26, 2009 at 12:33 PM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
Neil Groves wrote:
So is everyone happy if I:
1. Change the 'ed' adaptors to _view
I wouldn't be too happy about it. The lazy adaptors being preferred over the eager in-place algorithms, the name of the adaptors should be as short as possible, especially since chaining of adaptors can lead to fairly long expressions.
I'm not going to do this change. It appears that more people are happy with the current 'ed' ending than a '_view' ending. I don't have a strong opinion at all on this issue, but it's clear the majority prefer 'ed'. It's great to have reviewers so I can stop guessing.
As nobody has mentioned it yet, I would like to point out that boost:gil already uses the _view ending. Quoting from the documentation:
An image view is a generalization of STL's range concept to multiple dimensions. Similar to ranges (and iterators), image views are shallow, don't own the underlying data and don't propagate their constness over the data. For example, a constant image view cannot be resized, but may allow modifying the pixels.
GIL uses names like greyscale_view, nth_channel_view, planar_rgb_view. Using 'ed' style naming one could use names like greyscale_converted, nth_channel_selected. I am not sure what naming scheme is better but some consistency within boost would be nice.
Let me add that both Fusion and MPL also use _view to name views. It's so obvious and natural. OTOH, 'ed'? Duh! ;-) For the sake of consistency with Boost libraries, please name it _view. Regards, -- Joel de Guzman http://www.boostpro.com http://spirit.sf.net

Joel de Guzman wrote:
Kai Schroeder wrote:
As nobody has mentioned it yet, I would like to point out that boost:gil already uses the _view ending. Quoting from the documentation:
An image view is a generalization of STL's range concept to multiple dimensions. Similar to ranges (and iterators), image views are shallow, don't own the underlying data and don't propagate their constness over the data. For example, a constant image view cannot be resized, but may allow modifying the pixels.
GIL uses names like greyscale_view, nth_channel_view, planar_rgb_view. Using 'ed' style naming one could use names like greyscale_converted, nth_channel_selected. I am not sure what naming scheme is better but some consistency within boost would be nice.
Let me add that both Fusion and MPL also use _view to name views. It's so obvious and natural. OTOH, 'ed'? Duh! ;-)
For the sake of consistency with Boost libraries, please name it _view.
Here's a suggestion: In fusion, all algorithms are lazy, so algorithms' spellings are as-is. The _view suffix pertains to the object's class returned by the algorithm, not the algorithm itself. For example: transform(s, f) --> transform_view<S, F> If Fusion had both lazy and immediate algorithms (again Fusion has only the lazy variants), I'd place the lazy algorithms in a separate namespace. One doesn't usually combine lazy and immediate algorithms, right, no? I think I'd rather see something like: { using range_ex::transform; // immediate transform(s, f); } { using range_ex::view::transform; transform(s, f); } Or some such. Regards, -- Joel de Guzman http://www.boostpro.com http://spirit.sf.net

One doesn't usually combine lazy and immediate algorithms, right, no? I think I'd rather see something like:
{ using range_ex::transform; // immediate transform(s, f); }
{ using range_ex::view::transform; transform(s, f); }
I think lazy algorithms should be different in non-qualified name from the eager ones, so that dumb text searches work and casually glancing at the code tells the right story. Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229

on Mon Mar 02 2009, Arno Schödl <aschoedl-AT-think-cell.com> wrote:
One doesn't usually combine lazy and immediate algorithms, right, no? I think I'd rather see something like:
{ using range_ex::transform; // immediate transform(s, f); }
{ using range_ex::view::transform; transform(s, f); }
I think lazy algorithms should be different in non-qualified name from the eager ones, so that dumb text searches work and casually glancing at the code tells the right story.
s = s | transform(f) vs transform(s, f, s) I think the difference is quite obvious and the syntax needs no elaboration. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

{ using range_ex::transform; // immediate transform(s, f); }
{ using range_ex::view::transform; transform(s, f); }
I think lazy algorithms should be different in non-qualified name from the eager ones, so that dumb text searches work and casually glancing at the code tells the right story.
s = s | transform(f)
vs
transform(s, f, s)
I think the difference is quite obvious and the syntax needs no elaboration.
That's ok. Both having the same number of arguments, one doing work in-place, the other returning a result range, depending on namespace, as the OP proposed, is not, IMO. Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229

Arno Schödl wrote:
{ using range_ex::transform; // immediate transform(s, f); }
{ using range_ex::view::transform; transform(s, f); } I think lazy algorithms should be different in non-qualified name from the eager ones, so that dumb text searches work and casually glancing at the code tells the right story. s = s | transform(f)
vs
transform(s, f, s)
I think the difference is quite obvious and the syntax needs no elaboration.
That's ok. Both having the same number of arguments, one doing work in-place, the other returning a result range, depending on namespace, as the OP proposed, is not, IMO.
If it can be disambiguated by the arity, then yes, using the same name is the best choice, IMO. No need for different namespaces even. Even STL does that. Regards, -- Joel de Guzman http://www.boostpro.com http://spirit.sf.net

One doesn't usually combine lazy and immediate algorithms, right, no?
Isn't the distinction really about tertiary vs. binary operations (operator+ vs. operator+=) rather than laziness or eagerness? I can see that for tertiary (non-in-place) algorithms, you may always want laziness if the compiler supports it efficiently, ideally writing something like: vector<int> vecn; vector<double> vecf=transformed( vecn, func ); where the assignment forces evaluation. This is possibly just as efficient or even more efficient than vector<int> vecn; vector<double> vecf; std::transform( vecn.begin(), vecn.end(), back_inserter( vecf ), func ); But there may be cases where you want the binary (in-place) form, for example: vector<int> vecn; transform( vecn, func ); // func is int -> int sort( vecn ); // sort cannot be done lazily In this latter case, I don't see how you can do without an in-place algorithm, which IMO should be named differently. Or is the idea to do this: vector<int> vecn; vecn=transformed( vecn, func ); // overwriting the range that the adaptor is still working on? sort( vecn ); // sort cannot be done lazily I don't feel comfortable with it. Some adaptors are o.k. with it (transform), while others (say, duplicate each element) are not. For some (slice), it will depend on the implementation of operator=. And the compiler won't warn you. Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229

On Mon, Mar 2, 2009 at 10:19 AM, Arno Schödl <aschoedl@think-cell.com>wrote:
In this latter case, I don't see how you can do without an in-place algorithm, which IMO should be named differently. Or is the idea to do this:
vector<int> vecn; vecn=transformed( vecn, func ); // overwriting the range that the adaptor is still working on? sort( vecn ); // sort cannot be done lazily
I don't feel comfortable with it. Some adaptors are o.k. with it (transform), while others (say, duplicate each element) are not. For some (slice), it will depend on the implementation of operator=. And the compiler won't warn you.
AFAICS there's no issue in principle with something like vecn | boost::adaptors::sort as an expression. This would result in a range that could be iterated through lazily, although in total could not be as efficient as a traditional sort. Naturally the underlying range must not change in any way, or all bets are off. Regards, Rob.

on Mon Mar 02 2009, Robert Jones <robertgbjones-AT-gmail.com> wrote:
On Mon, Mar 2, 2009 at 10:19 AM, Arno Schödl <aschoedl@think-cell.com>wrote:
In this latter case, I don't see how you can do without an in-place algorithm, which IMO should be named differently. Or is the idea to do this:
vector<int> vecn; vecn=transformed( vecn, func ); // overwriting the range that the adaptor is still working on? sort( vecn ); // sort cannot be done lazily
I don't feel comfortable with it. Some adaptors are o.k. with it (transform), while others (say, duplicate each element) are not. For some (slice), it will depend on the implementation of operator=. And the compiler won't warn you.
AFAICS there's no issue in principle with something like
vecn | boost::adaptors::sort
as an expression. This would result in a range that could be iterated through lazily,
What does it mean to "iterate lazily?"
although in total could not be as efficient as a traditional sort.
What did you have in mind, using partial_sort?
Naturally the underlying range must not change in any way, or all bets are off.
The right answer may be to copy the range in that case. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Mon, Mar 2, 2009 at 15:36, David Abrahams <dave@boostpro.com> wrote:
on Mon Mar 02 2009, Robert Jones <robertgbjones-AT-gmail.com> wrote:
AFAICS there's no issue in principle with something like
vecn | boost::adaptors::sort
as an expression. This would result in a range that could be iterated through lazily,
What does it mean to "iterate lazily?"
although in total could not be as efficient as a traditional sort.
What did you have in mind, using partial_sort?
I can't speak for Robert, of course, but wouldn't a heap provide O(n) access to the first element and O(n log n) access to all elements, but in all but a few cases be a constant factor slower than introsort? Cheers, Rogier

David Abrahams wrote:
on Mon Mar 02 2009, Robert Jones <robertgbjones-AT-gmail.com> wrote:
AFAICS there's no issue in principle with something like
vecn | boost::adaptors::sort
[...] The right answer may be to copy the range in that case.
You may not want to copy the range if the elements aren't copiable or are costly to copy. An alternative solution is simply to create an index instead, and use that index to iterate the range.

on Mon Mar 02 2009, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
David Abrahams wrote:
on Mon Mar 02 2009, Robert Jones <robertgbjones-AT-gmail.com> wrote:
AFAICS there's no issue in principle with something like
vecn | boost::adaptors::sort
[...]
Grr... the important context got snipped! Here it is:
Naturally the underlying range must not change in any way, or all bets are off.
The right answer may be to copy the range in that case.
You may not want to copy the range if the elements aren't copiable or are costly to copy. An alternative solution is simply to create an index instead, and use that index to iterate the range.
Yes, I thought of that, but it doesn't help in the case where the underlying range changes, which is what we're discussing. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Mon, Mar 2, 2009 at 3:36 PM, David Abrahams <dave@boostpro.com> wrote:
on Mon Mar 02 2009, Robert Jones <robertgbjones-AT-gmail.com> wrote:
On Mon, Mar 2, 2009 at 10:19 AM, Arno Schödl <aschoedl@think-cell.com wrote:
In this latter case, I don't see how you can do without an in-place algorithm, which IMO should be named differently. Or is the idea to do
this:
vector<int> vecn; vecn=transformed( vecn, func ); // overwriting the range that the
adaptor
is still working on? sort( vecn ); // sort cannot be done lazily
I don't feel comfortable with it. Some adaptors are o.k. with it (transform), while others (say, duplicate each element) are not. For some (slice), it will depend on the implementation of operator=. And the compiler won't warn you.
AFAICS there's no issue in principle with something like
vecn | boost::adaptors::sort
as an expression. This would result in a range that could be iterated through lazily,
What does it mean to "iterate lazily?"
My understanding of an expression like rng | filter(pred); is that the range resulting from it never exists independently, but rather each element is determined by the application of the filter(s) from the source range on demand.
although in total could not be as efficient as a traditional sort.
What did you have in mind, using partial_sort?
The implentation of lazy iterators is easy for simple filters, but less obvious for things like sort, in which case presumably the whole of the source range must be available before any output iterator can be evaluated. I didn't really have any specific algorithm in mind, only that the result range need only support sequential iteration, and so each evaluation of the resulting range only needs to provide the next element. That's probably obvious. Sorry.
Naturally the underlying range must not change in any way, or all bets are off.
The right answer may be to copy the range in that case.
Maybe, that would remove the requirment of immutability of the source range, but OTOH might be expensive. Alternatively the result range might iterator through a range of pointers, which would be cheap, but course prone to failure if the pointed to element is destroyed. No answer really, just thoughts.
-- Dave Abrahams BoostPro Computing http://www.boostpro.com _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- ACCU - Professionalism in programming - http://www.accu.org

On Thu, 2009-02-26 at 13:33 +0100, Mathias Gaunard wrote:
Neil Groves wrote:
So is everyone happy if I: 1. Change the 'ed' adaptors to _view
I wouldn't be too happy about it. The lazy adaptors being preferred over the eager in-place algorithms,
That seems to be the question, which . The discussion has been about three classes of operations: 1. operator| lazy adaptors 2. Function-style lazy adaptors 3. In-place algorithms Is (2) preferred over (3)? Well, what would the consequences be? Many in-place algorithms can then be written in terms of lazy adaptors. E.g. it would be possible to write transform (rng1, output_it, fun); as copy (transform_view (rng1, fun), output_it); or copy (rng1 | transform_view (fun), output_it); (These can forward to fast versions where required) It might be harder, though, to phrase e.g. "remove" lazily. It would be good to be able to say rng = remove (rng, 3); to remove al elements equal to 3 from the range and reduce its length as required, but that's hard to get to work generally and make as fast as the original. If that is true, it makes sense to use infinitives ("remove") for (3), and use something else for (1) and (2). I don't find the past participle ('removed') worse than 'remove_view', just more concise. I still suggest removing any operation that uses output iterators (except "copy"), and just use lazy versions. Cheers, Rogier

Dear Thorsten, On Thu, 2009-02-26 at 14:38 +0100, Thorsten Ottosen wrote:
Rogier van Dalen skrev:
I still suggest removing any operation that uses output iterators (except "copy"), and just use lazy versions.
This you want to keep because of std::ostream_iterator?
I think output iterators can be useful, for example, in writing to files or forms of linear storage. If C++ had coroutines, output iterators would have been more generally useful. But algorithms that take output iterators often force the user to write to a temporary. They combine (in my mind) two operations: (1) processing or producing data, and (2) writing the output out to a sink. (1) can be written with lazy ranges; (2) can be written using copy(). I think all current STL algorithms that take output iterators could be written as copy()ing a lazy range to the output iterator. Looking at the "Mutating algorithms" section of the RangeEx docs, I believe that goes for copy_backward(), fill(), generate(), merge(), transform(), and the set algorithms. For efficiency, some combinations of copy() with lazy ranges could forward to STL implementations. I'd be happy to help out with lazy set algorithms since I've got implementations lying around. Cheers, Rogier

----- Original Message ----- From: "Thorsten Ottosen" <thorsten.ottosen@dezide.com> To: <boost@lists.boost.org> Sent: Wednesday, February 25, 2009 10:07 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
vicente.botet skrev:
As unique exist already, what about unique_view. With the _view suffix we state clearly that the evaluation is lazy.
Just to make sure there is no confusion on this point, then the orignal _ed suffix was supposed to mean "lazy". That doesn't mean it is perfect naming scheme, of course.
Of course, this don't mean it is perfect, but who is looking for perfection ;-) Vicente

On Wed, Feb 25, 2009 at 10:07 PM, Thorsten Ottosen <thorsten.ottosen@dezide.com> wrote:
vicente.botet skrev:
As unique exist already, what about unique_view. With the _view suffix we state clearly that the evaluation is lazy.
Just to make sure there is no confusion on this point, then the orignal _ed suffix was supposed to mean "lazy". That doesn't mean it is perfect naming scheme, of course.
No confusion, at least from my part. I think the -ed sufix does convey the lazyness of the algorithm. I'm just against it for purely aesthetic reasons. I could live with it (and in fact i prefer it to the _view variant, at least is shorter and easier to type). -- gpd

Dear Neil, I just found that an email I wrote earlier never made it to the mailing list. I'll re-send it, at the risk of being late at the party. On Wed, 2009-02-25 at 16:00 +0000, Neil Groves wrote:
boost::make_uniqued_range(rng) is equivalent to rng | uniqued
I'm sorry for having been unclear here; I meant to ask why the spelling "make_ _range" is necessary (see Giovanni's email). To me, RangeEx provides functions that take a range and return a range. How is it not most natural to make these functions look like functions? What is wrong with saying "uniqued(rng)"? Why is it vital that the name has "make_ _range" to remind me that this returns a lazy range rather than a range?
I was hoping for more lazy views on ranges, especially after reading that the library allows for a "seamless funtional-style programming". Shouldn't there be a generated(), randomly_shuffled(), sorted(), intersected(), merged(), etc.? (I implemented lazy set operations implemented and I think it's quite possible.)
The library is intended to be extended both by myself as core needs become clear, and by library users. The algorithms, supported range types and adaptors may be implemented by users of the library without changing it.
Many lazy adaptors are desirable. I would continue to add more as time permits. My focus is on producing a good foundation for this version. The reviews are giving me a clearer need of what people want from the library.
Fair enough. This will be fine if many reasonable algorithms can be implemented within range_ex. However, could you state how you would integrate lazy merge() or anything that takes two or more ranges in operator| syntax? The function syntax would be: merged(rng1, rng2) I believe that the '|' syntax, however cute (I agree there!) is less flexible and less obvious than the function syntax. Therefore, I think the function syntax should be the primary one. Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface. Thanks, Rogier

Rogier van Dalen skrev:
Dear Neil,
Many lazy adaptors are desirable. I would continue to add more as time permits. My focus is on producing a good foundation for this version. The reviews are giving me a clearer need of what people want from the library.
Fair enough. This will be fine if many reasonable algorithms can be implemented within range_ex. However, could you state how you would integrate lazy merge() or anything that takes two or more ranges in operator| syntax? The function syntax would be: merged(rng1, rng2) I believe that the '|' syntax, however cute (I agree there!) is less flexible and less obvious than the function syntax. Therefore, I think the function syntax should be the primary one.
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
I tend to agree. E.g. an algorithm like transform(-) should return a range. Or perhaps we need a new syntax to enable rng && rng2 | transformed(binary_fun()) -Thorsten

Hello! On Fri, Feb 27, 2009 at 4:33 PM, Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
Rogier van Dalen skrev:
Dear Neil,
Many lazy adaptors are desirable. I would continue to add more as time
permits. My focus is on producing a good foundation for this version. The reviews are giving me a clearer need of what people want from the library.
Fair enough. This will be fine if many reasonable algorithms can be implemented within range_ex. However, could you state how you would integrate lazy merge() or anything that takes two or more ranges in operator| syntax? The function syntax would be: merged(rng1, rng2) I believe that the '|' syntax, however cute (I agree there!) is less flexible and less obvious than the function syntax. Therefore, I think the function syntax should be the primary one.
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
I tend to agree.
I agree too, so this change shouldn't cause any disappointment to anyone!
E.g. an algorithm like transform(-) should return a range.
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
It seems that the '|' syntax is not much loved despite my deep affection for it. I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
-Thorsten
Neil Groves
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Fri, Feb 27, 2009 at 5:54 PM, Neil Groves <neil@grovescomputing.com> wrote:
Hello!
On Fri, Feb 27, 2009 at 4:33 PM, Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
E.g. an algorithm like transform(-) should return a range.
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
It seems that the '|' syntax is not much loved despite my deep affection for it. I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
I do like the '|' syntax, I just don't want it to be the only recommended syntax. -- gpd

Neil Groves wrote:
It seems that the '|' syntax is not much loved despite my deep affection for it. I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
I personally love it. I've written fairly complex chained adaptor expressions, with embedded lambdas using Boost.Phoenix. With a prefix syntax as with functions, it would have been unreadable.

On Fri, 2009-02-27 at 16:54 +0000, Neil Groves wrote:
On Fri, Feb 27, 2009 at 4:33 PM, Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
Rogier van Dalen skrev:
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
I tend to agree.
I agree too, so this change shouldn't cause any disappointment to anyone!
Great!
E.g. an algorithm like transform(-) should return a range.
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
It seems that the '|' syntax is not much loved despite my deep affection for it. I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
Speaking for myself, I do "love" the | syntax, but I think it's non-standard and can't be used easily for other cases than single chains of operations (1 range in, 1 range out). I therefore suggest using 'verbed()' as the primary interface, and operator| as a secondary interface. Two alternative spellings for merged(rng1, rng2) have been suggested. 1) (Thorsten) rng1 && rng2 | merged which I think is equivalent to rng1 && (rng2 | merged) It may be possible and useful to extend this to rng1 && rng2 && rng3 | merged I personally find the interaction between operator precedence and semantics scary, but maybe others feel different. 2) (Giovanni) rng1 ^merged^ rng2 which may be abuse, but it's nice. In the case of merged, rng1^merged^ rng2 ^merged^ rng3 would actually work. I'm not sure this works for all imaginable lazy range adaptors (zip?). I can see how either would work as an additional syntax, but I'm not sure there's reason enough to impose the element of surprise that they carries by default. I think something like zip() would be very useful, but for consistency should probably be spelled zipped(). zipped(rng1, rng2) | transformed (unpack(binary_fun()) seems more general for iterating through two ranges in parallel. zipped() would also alleviate the need for for_each over two ranges. Cheers, Rogier

----- Original Message ----- From: "Rogier van Dalen" <rogiervd@gmail.com> To: <boost@lists.boost.org> Sent: Friday, February 27, 2009 7:43 PM Subject: Re: [boost] Formal Review: Boost.RangeEx
On Fri, 2009-02-27 at 16:54 +0000, Neil Groves wrote:
On Fri, Feb 27, 2009 at 4:33 PM, Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
Rogier van Dalen skrev:
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
I tend to agree.
I agree too, so this change shouldn't cause any disappointment to anyone!
Great!
E.g. an algorithm like transform(-) should return a range.
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
It seems that the '|' syntax is not much loved despite my deep affection for it. I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
Speaking for myself, I do "love" the | syntax, but I think it's non-standard and can't be used easily for other cases than single chains of operations (1 range in, 1 range out). I therefore suggest using 'verbed()' as the primary interface, and operator| as a secondary interface.
Two alternative spellings for merged(rng1, rng2) have been suggested.
1) (Thorsten) rng1 && rng2 | merged which I think is equivalent to rng1 && (rng2 | merged) It may be possible and useful to extend this to rng1 && rng2 && rng3 | merged I personally find the interaction between operator precedence and semantics scary, but maybe others feel different.
2) (Giovanni) rng1 ^merged^ rng2 which may be abuse, but it's nice. In the case of merged, rng1^merged^ rng2 ^merged^ rng3 would actually work. I'm not sure this works for all imaginable lazy range adaptors (zip?).
I can see how either would work as an additional syntax, but I'm not sure there's reason enough to impose the element of surprise that they carries by default.
I think something like zip() would be very useful, but for consistency should probably be spelled zipped(). zipped(rng1, rng2) | transformed (unpack(binary_fun()) seems more general for iterating through two ranges in parallel. zipped() would also alleviate the need for for_each over two ranges.
For consistency, why not pack(rng1, rng2) | transformed (unpack(binary_fun()) Vicente

On Fri, Feb 27, 2009 at 8:07 PM, vicente.botet <vicente.botet@wanadoo.fr> wrote:
From: "Rogier van Dalen" <rogiervd@gmail.com>
I think something like zip() would be very useful, but for consistency should probably be spelled zipped(). zipped(rng1, rng2) | transformed (unpack(binary_fun()) seems more general for iterating through two ranges in parallel. zipped() would also alleviate the need for for_each over two ranges.
For consistency, why not
pack(rng1, rng2) | transformed (unpack(binary_fun())
Vicente
'zip' is a commonly used name for mapping a pair of ranges to ranges of pairs in other languages; I see no reason not to use it. Also, boost.iterator already has zip_iterator adapters. Another (arguably) good name might be transpose, but that is probably best left for ranges of ranges. BTW, unpack already exists in boost as fusion::fused. -- gpd

You are right. Vicente ----- Original Message ----- From: "Giovanni Piero Deretta" <gpderetta@gmail.com> To: <boost@lists.boost.org> Sent: Friday, February 27, 2009 8:21 PM Subject: Re: [boost] Formal Review: Boost.RangeEx On Fri, Feb 27, 2009 at 8:07 PM, vicente.botet <vicente.botet@wanadoo.fr> wrote:
From: "Rogier van Dalen" <rogiervd@gmail.com>
I think something like zip() would be very useful, but for consistency should probably be spelled zipped(). zipped(rng1, rng2) | transformed (unpack(binary_fun()) seems more general for iterating through two ranges in parallel. zipped() would also alleviate the need for for_each over two ranges.
For consistency, why not
pack(rng1, rng2) | transformed (unpack(binary_fun())
Vicente
'zip' is a commonly used name for mapping a pair of ranges to ranges of pairs in other languages; I see no reason not to use it. Also, boost.iterator already has zip_iterator adapters. Another (arguably) good name might be transpose, but that is probably best left for ranges of ranges. BTW, unpack already exists in boost as fusion::fused. -- gpd _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

----- Original Message ----- From: "Giovanni Piero Deretta" <gpderetta@gmail.com> To: <boost@lists.boost.org> Sent: Friday, February 27, 2009 8:21 PM Subject: Re: [boost] Formal Review: Boost.RangeEx On Fri, Feb 27, 2009 at 8:07 PM, vicente.botet <vicente.botet@wanadoo.fr> wrote:
From: "Rogier van Dalen" <rogiervd@gmail.com>
I think something like zip() would be very useful, but for consistency should probably be spelled zipped(). zipped(rng1, rng2) | transformed (unpack(binary_fun()) seems more general for iterating through two ranges in parallel. zipped() would also alleviate the need for for_each over two ranges.
For consistency, why not
pack(rng1, rng2) | transformed (unpack(binary_fun())
Vicente
'zip' is a commonly used name for mapping a pair of ranges to ranges of pairs in other languages; I see no reason not to use it. Also, boost.iterator already has zip_iterator adapters. Another (arguably) good name might be transpose, but that is probably best left for ranges of ranges. BTW, unpack already exists in boost as fusion::fused. -- gpd _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost Hi, As I already say in a preceding post you are right, pack should not be used to replace zipped. What about admiting a packet of arguments represented here as (p1, ..., pn) as the input of an adaptor. range3 = (range1, range2) | zipped | transformed( unpack(binary_fct) ) zipped will be an adaptor that takes an input pack with two elements (arguments) and give a single pack with the zipped view of both ranges. transformed is an adaptor taking a single pack with a range of pairs and return the result of unpacking these pairs and call to a binary function. I realy think that a generalization of the input and output parameters of these adaptors should be possible. And what about a 'tee' adaptor to retrieve the result a forward it to the next adaptor, and one 'tie' to retrieve the result. (range1, range2) | concat | tee(range3) | sorted | splited(pred) | tie(range4, range5) This expression will concatenate the ranges range1 and range2 store the result in range3 sort it and split the sequence respect to a given predicate and store the result in range4 and range5. How this pack of argument (p1, ..., pn) can be implemented is another question (fusion tuples?) IMHO this should be out of the scope of the Boost.Range library. I don't think the first release of Boost.Range should come with the operators interface and a deeper analisys about how to chain functors must be done in a separated library. Vicente

on Fri Feb 27 2009, Rogier van Dalen <rogiervd-AT-gmail.com> wrote:
Two alternative spellings for merged(rng1, rng2) have been suggested.
1) (Thorsten) rng1 && rng2 | merged which I think is equivalent to rng1 && (rng2 | merged) It may be possible and useful to extend this to rng1 && rng2 && rng3 | merged I personally find the interaction between operator precedence and semantics scary, but maybe others feel different.
It breaks down once there's any interesting chaining: (r1 | f1 | f2) && (r2 | f3 | f4) && (r4 | f5 | f6) | merged is no better than merge(r1|f1|f2, r2|f3|f4, r4|f5|f6) I don't think that sort of range argument packing makes any sense at all unless they have the same number of elements, in which case write zip(r1,r2,r3)
2) (Giovanni) rng1 ^merged^ rng2 which may be abuse, but it's nice. In the case of merged, rng1^merged^ rng2 ^merged^ rng3 would actually work. I'm not sure this works for all imaginable lazy range adaptors (zip?).
There appears to be a blind obsession with syntactic consistency at work here. Why else would we be groping about for infix ways to express such an operation? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Fri Feb 27 2009, Neil Groves <neil-AT-grovescomputing.com> wrote:
Hello!
E.g. an algorithm like transform(-) should return a range.
Agreed in that case. For transform in-place: r = transform(r, f)
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
It seems that the '|' syntax is not much loved despite my deep affection for it.
I love it I love it I love it I love it I love it I love it!
I am becoming inclined to concentrate more on the function syntax and just using the 'ed' convention.
nononono, please! I have no affection for 'ed,' neither. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Monday 02 March 2009 10:04:11 David Abrahams wrote:
It seems that the '|' syntax is not much loved despite my deep affection for it.
I love it I love it I love it I love it I love it I love it!
Me too. Sadly, I don't have time to produce a proper review, but I have been waiting for a while to see this in boost (along with signals2, extension and boost.bindings). Regards, Ravi

On Fri, Feb 27, 2009 at 5:33 PM, Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
Rogier van Dalen skrev:
Dear Neil,
Many lazy adaptors are desirable. I would continue to add more as time permits. My focus is on producing a good foundation for this version. The reviews are giving me a clearer need of what people want from the library.
Fair enough. This will be fine if many reasonable algorithms can be implemented within range_ex. However, could you state how you would integrate lazy merge() or anything that takes two or more ranges in operator| syntax? The function syntax would be: merged(rng1, rng2) I believe that the '|' syntax, however cute (I agree there!) is less flexible and less obvious than the function syntax. Therefore, I think the function syntax should be the primary one.
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
I tend to agree.
E.g. an algorithm like transform(-) should return a range.
Or perhaps we need a new syntax to enable
rng && rng2 | transformed(binary_fun())
you mean for the two ranges variant of transform? Well first you can always do this: zip(rng, rng2) | transformed (unpack(binary_fun()) obviating the need for a 2 range (or n-range) variant at all. Otherwise, may be an infix syntax would work (but it probably count as too much operator abuse [1]): rng1 ^transformed(binary_fun())^ rng2 ; Distinguishing from the 1 range and 2 range variants would be interesting. It might work better for 2-ranges only adaptors: (rng1 ^merge^ rng2) [1] FWIW, FC++ has generalized infix function object with the same syntax. -- gpd

on Fri Feb 27 2009, Giovanni Piero Deretta <gpderetta-AT-gmail.com> wrote:
rng && rng2 | transformed(binary_fun())
you mean for the two ranges variant of transform?
Well first you can always do this:
zip(rng, rng2) | transformed (unpack(binary_fun())
zip(rng, rng2) | transform( unzip_args(binary_fun) )
obviating the need for a 2 range (or n-range) variant at all. Otherwise, may be an infix syntax would work (but it probably count as too much operator abuse [1]):
rng1 ^transformed(binary_fun())^ rng2 ;
I'm sorry, I can't understand that one. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Fri Feb 27 2009, Rogier van Dalen <rogiervd-AT-gmail.com> wrote:
To me, RangeEx provides functions that take a range and return a range. How is it not most natural to make these functions look like functions?
Every bit of runtime functionality is most naturally-expressed as a function call, according to some people. Those people end up writing code with lots and lots of parentheses, and they cite this uniformity as a source of expressiveness. Personally, I don't get it. If there's a more evocative syntax, we should consider using it. Function call syntax (in general) expresses a tree structure. f( g( a( b, c ), d( e, z, h) ), i(j, k) ) f ______|______ / \ g i ___|___ / \ / \ j k a d / \ /|\ b c e z h What we're doing with RangeEx (in general) only expresses a degenerate tree structure (i.e. a linear structure): r | transform(f) | filter(g) | whatever(h) = whatever(filter(transform(r, f), g, h)) There's a _lot_ wrong with that way of expressing it, not least that oerations (such as filter) are separated from their essential parameters (g). Personally I think the top-down order in which the function call syntax reads is also much harder to understand.
What is wrong with saying "uniqued(rng)"? Why is it vital that the name has "make_ _range" to remind me that this returns a lazy range rather than a range?
I don't think "make_ _range" does anything to indicate laziness. Here's how I think of it: unique_range => a type. No need to say "lazy_unique_range" because if it weren't lazy, we wouldn't need a type. unique(r) => a function returning unique_range
could you state how you would integrate lazy merge() or anything that takes two or more ranges in operator| syntax?
There are lots of options, but merge(r1, r2) is not a bad one. The alternatives generally look like r1 # r2 where # is some operator (or pseudo-operator such as %merge%). In the end you will usually want to add parentheses as in (r1 | r2) # (r3 | r4) and then if you want to continue chaining, ((r1 | r2) # (r3 | r4)) | r5 which I don't think is an improvement over merge(r1 | r2, r3 | r4) | r5
I believe that the '|' syntax, however cute (I agree there!) is less flexible and less obvious than the function syntax.
Agreed w.r.t. flexibility; disagreed w.r.t. obviousness. As is often the case, a more limited and specific form of expression makes for clearer syntax (e.g. BNF illustrates syntax better than a hand-coded parser in a general-purpose programming languages)
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
While value semantics are wonderful, I have doubts that everything that operates on output iterators can be effectively reformulated to return a range without limiting expressiveness and efficiency. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Dear Dave, Do I correctly understand your opinion can be summarised as "for linear structures operator| is clearest; for complicated trees, function syntax; and combinations, a combination"? On Mon, 2009-03-02 at 10:01 -0500, David Abrahams wrote:
on Fri Feb 27 2009, Rogier van Dalen <rogiervd-AT-gmail.com> wrote:
To me, RangeEx provides functions that take a range and return a range. How is it not most natural to make these functions look like functions?
Every bit of runtime functionality is most naturally-expressed as a function call, according to some people. Those people end up writing code with lots and lots of parentheses, and they cite this uniformity as a source of expressiveness. Personally, I don't get it. If there's a more evocative syntax, we should consider using it.
You give a convincing argument. But is this different from ranges than for any expression? Taking the n-th power of a complicated expression is pow((... complicated expression ...), n) but might be more clearly expressed as (... complicated expression ...) #pow# n Shouldn't this go into a more general library? Shouldn't RangeEx by default offer the well-known syntax of function calls? (Even though I agree that also providing operator| is sensible, and not very hard at all.)
What we're doing with RangeEx (in general) only expresses a degenerate tree structure (i.e. a linear structure):
Is that true? I've used RangeEx-like facilities (that I wrote) mostly for more complicated trees, such as set operations.
What is wrong with saying "uniqued(rng)"? Why is it vital that the name has "make_ _range" to remind me that this returns a lazy range rather than a range?
I don't think "make_ _range" does anything to indicate laziness.
Sorry, I was trying (and clearly failed) to ask a Socratic question (echoing Neil's "I like the make_XXX_range because I instantly recognise that this creates a range adaptor"). We agree here.
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
While value semantics are wonderful, I have doubts that everything that operates on output iterators can be effectively reformulated to return a range without limiting expressiveness and efficiency.
Re efficiency, it would make sense to have copy (lazy_operation (rng), output_it) forward to the standard library operation. Re expressiveness, I am not sure what you mean. The output iterator versions of the operations I mention can be mimicked with "copy". I do know that I find that "merge()" returning ranges copy (merge (rng1, rng2) | transform (f), output_it); gives expressiveness than the implementation of the same using the standard library, which requires storing the intermediate values. To be fair, I have never compared performance, but I don't think the lazy range version is obviously slower. But maybe by "everything" you mean all possible operations, not just those currently in the standard library. In that case I'm sure you're right, but I don't see how that would make it undesirable to eliminate output iterators where possible, especially in a library that's bound to be used a lot. Cheers, Rogier

on Mon Mar 02 2009, Rogier van Dalen <rogiervd-AT-gmail.com> wrote:
Dear Dave,
Do I correctly understand your opinion can be summarised as "for linear structures operator| is clearest; for complicated trees, function syntax; and combinations, a combination"?
That's how it looks to me so far, yeah.
On Mon, 2009-03-02 at 10:01 -0500, David Abrahams wrote:
Every bit of runtime functionality is most naturally-expressed as a function call, according to some people. Those people end up writing code with lots and lots of parentheses, and they cite this uniformity as a source of expressiveness. Personally, I don't get it. If there's a more evocative syntax, we should consider using it.
You give a convincing argument. But is this different from ranges than for any expression?
Of course not.
Taking the n-th power of a complicated expression is pow((... complicated expression ...), n) but might be more clearly expressed as (... complicated expression ...) #pow# n
Yeah, but Xⁿ is already a well-established accepted syntax in standard math notation.
Shouldn't this go into a more general library? Shouldn't RangeEx by default offer the well-known syntax of function calls?
The only standard syntax I know for stringing together sequence modifications like that is "|"
(Even though I agree that also providing operator| is sensible, and not very hard at all.)
It should be the only syntax.
What we're doing with RangeEx (in general) only expresses a degenerate tree structure (i.e. a linear structure):
Is that true? I've used RangeEx-like facilities (that I wrote) mostly for more complicated trees, such as set operations.
"in general," meaning "most of the time."
What is wrong with saying "uniqued(rng)"? Why is it vital that the name has "make_ _range" to remind me that this returns a lazy range rather than a range?
I don't think "make_ _range" does anything to indicate laziness.
Sorry, I was trying (and clearly failed) to ask a Socratic question (echoing Neil's "I like the make_XXX_range because I instantly recognise that this creates a range adaptor"). We agree here.
I don't want to see that level of detail. What I want to get /instantly/ when I read the code is the high-level abstraction, e.g. "this transforms the range using that function." What kind of object is getting created is a secondary concern, and we shouldn't force into the foreground. Taken to an extreme, that approach leads to "Hungarian notation" naming.
Currently merge(), transform(), and the set algorithms use output iterators as a substitute for return values. Rephrasing them as functions would also get rid of output iterators, which would improve their interface.
While value semantics are wonderful, I have doubts that everything that operates on output iterators can be effectively reformulated to return a range without limiting expressiveness and efficiency.
Re efficiency, it would make sense to have copy (lazy_operation (rng), output_it) forward to the standard library operation.
Naturally.
Re expressiveness, I am not sure what you mean. The output iterator versions of the operations I mention can be mimicked with "copy".
Good, then I withdraw my objection.
I do know that I find that "merge()" returning ranges copy (merge (rng1, rng2) | transform (f), output_it); gives expressiveness than ^^^^^^^^^^^^^^^^^^^ Parse error. I don't understand what you're trying to say.
the implementation of the same using the standard library, which requires storing the intermediate values. To be fair, I have never compared performance, but I don't think the lazy range version is obviously slower.
But maybe by "everything" you mean all possible operations, not just those currently in the standard library.
That's what I meant.
In that case I'm sure you're right, but I don't see how that would make it undesirable to eliminate output iterators where possible, especially in a library that's bound to be used a lot.
OK -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Mon, 2009-03-02 at 13:14 -0500, David Abrahams wrote:
on Mon Mar 02 2009, Rogier van Dalen <rogiervd-AT-gmail.com> wrote:
Shouldn't this go into a more general library? Shouldn't RangeEx by default offer the well-known syntax of function calls?
The only standard syntax I know for stringing together sequence modifications like that is "|"
Do you mean the pipe operator in shell scripts?
(Even though I agree that also providing operator| is sensible, and not very hard at all.)
It should be the only syntax.
We're not only discussing "stringing together sequence modifications" though. The base case under discussion is a single operation. Implicitly you're saying that applying a transformation to all elements should be written as rng | transform (func) rather than transform (rng, func) The former is not standard syntax in C++, nor any other language language: http://en.wikipedia.org/wiki/Map_(higher-order_function) Could you give a rationale for centring your argument around chains of operations, rather than the base case of a single operation?
What we're doing with RangeEx (in general) only expresses a degenerate tree structure (i.e. a linear structure):
Is that true? I've used RangeEx-like facilities (that I wrote) mostly for more complicated trees, such as set operations.
"in general," meaning "most of the time."
What I meant to say was, are you expressing your personal experience with using RangeEx? Or a hunch about what most users will use it for? Or the lazy adaptors that the current version of RangeEx has? FWIW, My personal experience is different.
I do know that I find that "merge()" returning ranges copy (merge (rng1, rng2) | transform (f), output_it); gives expressiveness than ^^^^^^^^^^^^^^^^^^^ Parse error. I don't understand what you're trying to say.
Sorry about that. What I tried to say was that I find copy (transform (merge (rng1, rng2), func), output_it); or, if you wish copy (merge (rng1, rng2) | transform (func), output_it); more expressive than vector <T> intermediate; merge (rng1, rng2, back_inserter(intermediate)); transform (intermediate, output_it, func); Cheers, Rogier

Dave,
r | transform(f) | filter(g) | whatever(h) = whatever(filter(transform(r, f), g, h))
why this way around? Why not whatever( h, filter(g, transform(f, r) ) which I don't find much worse than the operator| notation, and it works consistently with anything that expects functions, boost::bind, boost::function, whatever other people have already implemented that invokes operator(). I agree with Rogier that I don't see why ranges are so different from doubles, say, that would warrant introducing a new syntax. And wouldn't concepts in C++0x allow augmenting Ranges with things that can be invoked method-style, like r.transform(f).filter(g).whatever(h) ? Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229

on Mon Mar 02 2009, Arno Schödl <aschoedl-AT-think-cell.com> wrote:
Dave,
r | transform(f) | filter(g) | whatever(h) = whatever(filter(transform(r, f), g, h))
why this way around? Why not
whatever( h, filter(g, transform(f, r) )
which I don't find much worse than the operator| notation,
Better than what I cited, but still inferior to operator|
and it works consistently with anything that expects functions, boost::bind, boost::function, whatever other people have already implemented that invokes operator().
That's indeed a real advantage to function call notation. On the other hand, we have libraries like lambda and phoenix that allow us to work with the operators in a similar way.
I agree with Rogier that I don't see why ranges are so different from doubles, say, that would warrant introducing a new syntax.
And wouldn't concepts in C++0x allow augmenting Ranges with things that can be invoked method-style, like
r.transform(f).filter(g).whatever(h)
You could do that today, but it's got the same coupling problem as named parameters in Boost.Graph and is solved by Boost.Parameter. Is there something in C++0x that causes decoupling? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

That's indeed a real advantage to function call notation. On the other hand, we have libraries like lambda and phoenix that allow us to work with the operators in a similar way.
IMO, something as basic as ranges, really embellished iterators, still pretty close to the std library, shouldn't need something as fancy as Phoenix to be used properly. C++ 0x lambdas will likely be a very popular feature, so we can expect a lot more code consuming functors in the future, all using operator() to invoke them. So in a way, operator() becomes even more special with C++0x than it used to be. Don't get me wrong, I don't particularly like the function syntax, and I wish C++ would treat methods and functions more uniformly, so I can write rng.transform(f).filter(g). But it is not standard, and I fear the "unknown unknowns":-) later if we decide now for a non-standard syntax.
You could do that today, but it's got the same coupling problem as named parameters in Boost.Graph and is solved by Boost.Parameter. Is there something in C++0x that causes decoupling?
You are right, doesn't work. Arno -- Dr. Arno Schoedl · aschoedl@think-cell.com Technical Director think-cell Software GmbH · Invalidenstr. 34 · 10115 Berlin, Germany http://www.think-cell.com · phone +49-30-666473-10 · toll-free (US) +1-800-891-8091 Directors: Dr. Markus Hannebauer, Dr. Arno Schoedl · Amtsgericht Charlottenburg, HRB 85229
participants (18)
-
Alp Mestan
-
Arno Schödl
-
Christopher Jefferson
-
David Abrahams
-
Eric Niebler
-
Giovanni Piero Deretta
-
Joel de Guzman
-
Kai Schroeder
-
Marcus Lindblom
-
Mathias Gaunard
-
Neil Groves
-
Ravi
-
Robert Jones
-
Rogier van Dalen
-
Thorsten Ottosen
-
Thorsten Ottosen
-
Vicente Botet
-
vicente.botet