![]() |
Copyright © 2004 Eric Niebler
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt )
Table of Contents
The range of what we think and do is limited by what we fail to notice. And because we fail to notice that we fail to notice, there is little we can do to change; until we notice how failing to notice shapes our thoughts and deeds.
-- R. D. Laing
The Standard Template Library provides a framework for writing generic code: algorithms which operate on sequences, as defined by a pair of iterators. The Boost.Range library raises the abstraction level by defining a Range concept, which simplifies writing generic code over sequences and generalizes the concept to seamlessly handle arrays, iterator pairs, null-terminated strings and other sequence-like types. The range_ex library presented here extends Boost.Range by exploiting the composability of ranges, and by providing range-based versions of all the STL algorithms.
The key advantantage of range-based algorithms over iterator-based algorithms is that the former are composable -- a function can return a range, which is immediately used as a parameter to a range-based algorithm. This is not possible with iterator-based algorithms. Consider the std::equal_range algorithm, which returns a std::pair of iterators. If you wanted to pass the sequence represented by this pair to another std:: algorithm, you would first have to save the pair into a local variable, p. Only then could you call the algorithm, passing p.first and p.second, because the standard algorithms need iterators, not ranges. The following code illustrates the difficulty:
std::vector<int> integers; // call std::equal_range, and save the result. // Ugh, this return type is ugly! std::pair<std::vector<int>::iterator, std::vector<int>::iterator> p = std::equal_range(integers.begin(), integers.end(), 0); // do something to all the elements in the range std::for_each(p.begin, p.end, some_predicate());
Range-based algorithms make this easier by eliminating the need to declare local variables. Just call the algorithm, using the call to std::equal_range as the range argument. A std::pair of iterators satisfies the Range concept, so it just works. Even better, you can use boost::equal_range to avoid having to get the begin and end iterators for the sequence.
std::vector<int> integers; // call boost::equal_range, pass the result directly to boost::for_each boost::for_each(boost::equal_range(integers, 0), some_predicate());
A powerful application of this principle uses range adaptors (built using the Boost.Iterator library) to filter and/or transform a range before passing it to a range-based algorithm. In this way, you can specify that an algorithm only apply to elements satisfying a certain predicate. Or, you can make a range of X look like a range of Y by applying an X to Y transformation to each element on the fly. The range_ex library provides a handful of range adaptor primatives which you can use to easily filter and transform ranges without needing to declare local variables or fuss about complicated intermediate types. The following code uses a range filter to achieve a similar effect as the call to equal_range above.
std::vector<int> integers; // perform some action on all the integers that are 0 boost::for_each( integers | boost::adaptor::filter(std::bind1st(std::equal_to<int>(),0)) , some_predicate() );
In this code, notice how the pipe operator | is used to send the integers sequence through a filter, and that the resulting filtered range is immediately used in the call to boost::for_each. The filtered range has a complicated type, but because we are using range-based algorithms, we don't have to know it. Better yet, we don't have to type it. Hurray!
This code is less efficient than the above code using equal_range because
equal_range is O(log(N)), whereas iterating through a filtered range is O(N). This code is just
for illustration purposes. |
Last revised: March 16, 2005 at 00:26:39 GMT |