7 Ranges
This chapter motivates ranges as a remedy for the friction of composing standard algorithms that take iterator pairs. Traditional STL use often forces temporary containers, local mutation, and awkward plumbing just to pass results from one algorithm to another. By treating a range as a single object that encapsulates begin and end, algorithms can return ranges directly, and programmers can chain transformations—often using a pipe syntax—so intent reads clearly while avoiding hand-written for loops.
The core idea is to distinguish lazy, read-only views from eager, mutating actions. Views such as filter, transform, and take don’t build new containers; they return lightweight proxy ranges whose iterators skip or transform elements on demand. Because evaluation is lazy, composed pipelines traverse the source data once and avoid intermediate allocations, yet remain as efficient as bespoke loops. When mutation or reordering is required, actions like sort and unique operate eagerly on real containers (often in place, especially when piping temporaries) and can be combined with views, using either the pipe or the |= operator for lvalues. This selective control over laziness versus eagerness yields concise, composable, and performant code.
Ranges also generalize the notion of “end” via sentinels, enabling delimited ranges (such as input streams or null-terminated strings) and even infinite ranges (like an unbounded sequence of integers). Sentinels simplify end checks and make it practical to zip data with indices, lazily enumerate results, and stop precisely when needed. The chapter illustrates these ideas with pipelines such as taking the first few matching items, enumerating top elements by zipping with an integer range, and a complete word-frequency program: read tokens as a range, normalize to lowercase, strip nonalphanumerics, drop empties, sort, group equal words, map to (count, word) pairs, sort by frequency, then reverse and take n. The result is succinct, expressive, and largely free of explicit loops and mutable intermediates.
The view created by filter stores an iterator to the source collection. The iterator points to only the elements that satisfy the filtering predicate. The user of this view will be able to use it as if it were a normal collection of people with only three elements in it: Martha, Jane, and Rose.
The view created by transform stores an iterator to the source collection. The iterator accesses all the elements in the source collection, but the view doesn’t return them directly; it first applies the transformation function to the element and returns its result.
When accessing an element from the view, the view proxies the request to the next view in the composite transformation, or to the collection. Depending on the type of the view, it may transform the result, skip elements, traverse them in a different order, and so on.
To get a list of words that appear in text, it’s sufficient to sort them and then remove the consecutive repeated values.
The range doesn’t have a notion of an item index. If you want to have the indices for the elements in a range, you can zip the range with the range of integers. You’ll get a range of pairs, and each pair will contain an item from the original range along with its index.
You have an input stream from which to read words. Before you can calculate the word frequencies, you need a list of words converted to lowercase with all punctuation removed.
You get a range of sorted words. You need to group the same words, count how many words you have in each group, and then sort them all by the number of occurrences.
Summary
- One frequent source of errors when using STL algorithms is passing incorrect iterators to them—sometimes even iterators belonging to separate collections.
- Some collection-like structures don’t know where they end. For those, it’s customary to provide sentinel-like iterators; these work but have unnecessary performance overhead.
- The ranges concept is an abstraction over any type of iterable data. It can model normal collections, input and output streams, database query result sets, and more.
- The ranges proposal is planned for inclusion in C++20, but libraries provide the same functionality today.
- Range views don’t own the data, and they can’t change it. If you want to operate on and change existing data, use actions instead.
- Infinite ranges are a nice measure of algorithm generality. If something works for infinite ranges, it’ll work for finite ones as well.
- By using ranges and thinking of program logic in terms of range transformations, you can decompose the program into highly reusable components.
Functional Programming in C++ ebook for free