Overview

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.
c07_01.png
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.
c07_02.png
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.
c07_03.png
To get a list of words that appear in text, it’s sufficient to sort them and then remove the consecutive repeated values.
c07_04.png
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.
c07_05.png
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.
c07_06.png
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.
c07_07.png

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.

FAQ

What problems arise from passing iterator pairs to STL algorithms?Algorithms can’t return a collection directly; they mutate passed-in containers, encourage temporaries to call begin/end, and are error-prone (iterators can come from different containers or be passed in the wrong order), which can lead to undefined behavior.
What is a range and why is it useful?A range is a single object that represents a sequence by bundling its begin and end together. It can be returned from functions and passed directly to other operations, enabling composition without temporary variables and reducing errors from mismatched iterator pairs.
How do range views like filter and transform avoid extra allocations?They return lightweight proxy ranges whose iterators adapt behavior: filter’s iterator skips elements not matching a predicate; transform’s iterator applies a function on dereference. No intermediate containers are created; they form views over existing data.
What is the pipe syntax and why is it helpful?The pipe operator (|) lets you write left-to-right transformations (people | view::filter(pred) | view::transform(f)), which is easier to read and compose than nested calls. It mirrors the “pass through a transformation” mental model.
What’s the difference between views and actions in range-v3?Views (ranges::v3::view) are lazy, non-mutating adapters that create read-only projections over data (filter, transform, take). Actions (ranges::v3::action) are eager transformations that mutate or materialize data (sort, unique) and typically operate on real containers.
How does lazy evaluation affect performance?Composed views are evaluated only when the result is consumed. Even with multiple views (filter → transform → take), traversal happens in a single pass like a hand-written for loop, avoiding temporary containers and extra passes.
What are sentinels and how do they enable delimited and infinite ranges?A sentinel is an end marker that need not be the same type as the iterator. It allows ends defined by conditions (e.g., end of stream, '\0' in C-strings), improving input-range efficiency, and supports infinite ranges (e.g., view::ints) that have no natural end.
When should I use actions with |= versus piping temporaries?Use |= to apply actions in place to lvalues (words |= action::sort | action::unique). When piping temporaries (read_text() | action::sort), actions can reuse the temporary container and avoid extra copies. Views are preferred when you want laziness and no mutation.
How can I enumerate items (e.g., top 10) with ranges?Zip your data with an integer range: view::zip(items, view::ints(1)) pairs each element with its index; then transform to a printable form and view::take(10). Using an infinite ints range avoids needing to know the size upfront.
How do ranges simplify computing word frequencies from a stream?Build a pipeline: tokenize with istream_range(cin) → normalize (view::transform to lowercase, filter non-alnum, remove empty) → materialize to a vector → sort (action::sort) → group equal words (view::group_by) → transform to (count, word) pairs → sort by count → reverse and take(n) to print top results. The mix of views and actions minimizes mutation and extra allocations.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free