To Iterate Is Human, to Range Is Divine

To Iterate Is Human, to Range Is Divine

ost Standard Library algorithms operate on sequences whose boundaries are designated by a pair of iterators: one iterator marks the beginning of the sequence and another marks its end. Combining such a pair of iterators into a single object has several advantages in terms of code simplification, safety, and performance. The following sections will explain how to use the new C++09 range library and show some of its advantages.

The following Standard Library algorithms, which I have selected randomly from the header, exhibit a recurring pattern of verbosity and tedium:

template   void replace(ForwardIterator first,               ForwardIterator last,               const T& old_value,               const T& new_value);template   InputIterator find(InputIterator first,                      InputIterator last,                     const T& value);template  OutputIterator copy(InputIterator first,                       InputIterator last,			       OutputIterator result);

These algorithms, as well as std::sort, std::remove, and so forth, represent the beginning and the end of the sequence on which they operate by a pair of iterators.

Why not combine these two iterators into a single object? That’s exactly what a std::range does?it combines two iterators into a single compact and efficient object.

You can construct a range from a pair of iterators, by copying an existing range object or by default constructing an empty range whose iterators are singular. A singular iterator is similar to a NULL pointer; the only safe operation that you can perform on a singular iterator is assigning a new value to it.

Author’s Note: There are currently two range libraries. The Boost range library is ready for immediate download and use with any existing C++ compiler. However, this 10 Minute Solution will exclusively use the range library that is currently being incorporated into the C++09 standard. The differences between these two libraries aren’t great anyway. I prefer the standard version because that’s what most C++ users will have at their disposal in the near future.

The range classes and functions are declared in the augmented standard header . All range functions are preceded by range_ to make them distinct from the traditional iterator-based functions. A complete list of the range library functions is available in the official C++09 proposal. For the sake of brevity, I will show only a few illustrative examples here.

The range_make() helper function creates a range from a pair of iterators:

std::vector  results;//...populate the vectorstd::range results_range=std::make_range(myvec.begin(), myvec.end());

To copy a range object you can use a copy constructor as usual, or the copy_range() helper function:

template       CopyableRange copy_range(const Range& r);std::range another_range=std::copy_range(results_range);//copy ctor version:std::range yet_another_range(another_range); 

Finally, creating an empty range is trivial:

std::range empty_range; //fill it later

Accessing a Range
The range library defines functions for accessing a range’s iterators and modifying them. In addition, it includes a set of overloaded operators such as ==, != for comparing ranges and sub-ranges.

The beginning of a range is equivalent to the first of the two iterators of a range. To extract that iterator, use range_begin(). Notice that this function assumes two other C++09 proposals, namely rvalue-references, and auto and decltype:.

template< class Range > auto range_begin( Range&& r ) -> decltype( r.begin() );

This C++09 syntax is a bit unfamiliar yet but the meaning is self-evident: range_begin() takes an rvalue reference to a range object r (recall that Range in this context is a template parameter). It returns an object whose type is that of the expression r.begin(). In simpler words, it returns an iterator that designates the beginning of the range.

In a similar vein, call range_end() to extract the end of the range:

template< class Range > auto range_end( Range&& r ) -> decltype( r.end());vector::iterator vbeg=range_begin(results_range),                  vend=range_end(results_range);

Ease, Safety…
Bounds checking is a major problem with built-in arrays. It’s also one of the biggest code safety threats. The main cause of array-related bugs is that an array’s name decays into a pointer. That pointer tells you only where the array begins, but not where it ends. It’s so easy to lose track of the array’s end and when that happens, all hell breaks loose. Ranges eliminate this potential bug as they combine the beginning and the end of the array into a single range object. Overloads of range_begin() and range_end() generate iterators from a built-in array automatically:

int arr[4]={1,2,999,5};std::range arr_range(range_begin(arr), range_end(arr));

How does it work? The arguments of range_begin() is “reference to an array of N elements of type T“:

template< class T, size_t N >T* range_begin( T(&arr)[N]);

This function returns arr, the address of the first element, which makes a perfect iterator (recall that built-in arrays are valid STL sequences). The end of this sequence is obtained by calling:

template< class T, size_t N > T* range_end( T(&arr)[N]);

which returns arr+N.

STL algorithms that take pairs of iterators will be augmented with corresponding overloads that take ranges instead. In the meantime, you can use ranges even with traditional C++03 algorithms. For example, the find() algorithm locates a certain value within an array of integers like this:

int* p=find(arr_range.begin(), arr_range.end(),999);	

Indeed, this version doesn’t look simpler than the traditional iterator-based version. However, it’s much safer since the use of ranges prevents the inadvertent creation of an invalid iterator.

When range-based versions of Standard Library algorithms become available, the previous find() call will change to:

int* p=find(arr_range, 999);

Which is both safer and syntactically simpler.

…And Efficiency
With respect to performance, the range library introduces a new class called std::sub_range which encapsulates the notion of a range within another range. sub_range is particularly useful in implementing efficient string manipulation algorithms. A string is represented as a range, and the substring being modified is represented as a sub_range thereof.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist