Preface
Range library for C++11/14/17. This code is the basis of a formal proposal to add range support to the C++ standard library.
Development Status:
This code is fairly stable, well-tested, and suitable for casual use, although currently lacking documentation. No promise is made about support or long-term stability. This code will evolve without regard to backwards compatibility.
Installation
This library is header-only. You can get the source code from the range-v3 repository on github. To compile with Range-v3, you can either #include
the entire library:
Or you can #include
only the core, and then the individual headers you want:
License
Most of the source code in this project are mine, and those are under the Boost Software License. Parts are taken from Alex Stepanov's Elements of Programming, Howard Hinnant's libc++, and from the SGI STL. Please see the attached LICENSE file and the CREDITS file for the licensing and acknowledgements.
Supported Compilers
The code is known to work on the following compilers:
Quick Start
Range v3 is a generic library that augments the existing standard library with facilities for working with ranges. A range can be loosely thought of a pair of iterators, although they need not be implemented that way. Bundling begin/end iterators into a single object brings several benefits.
Why Use Ranges?
Convenience
It's more convenient to pass a single range object to an algorithm than separate begin/end iterators. Compare:
with
Range v3 contains a full implementation of all the standard algorithms with range-based overloads for convenience.
Composability
Having a single range object permits pipelines of operations. In a pipeline, a range is lazily adapted or eagerly mutated in some way, with the result immediately available for further adaptation or mutation. Lazy adaption is handled by views, and eager mutation is handled by actions.
Views
A view is a lightweight wrapper that presents a view of an underlying sequence of elements in some custom way without mutating or copying it. Views are cheap to create and copy, and have non-owning reference semantics. Below are some examples:
Filter a container using a predicate and transform it.
std::vector<int> vi{1,2,3,4,5,6,7,8,9,10};
Generate an infinite list of integers starting at 1, square them, take the first 10, and sum them:
Generate a sequence on the fly with a range comprehension and initialize a vector with it:
std::vector<int> vi =
});
Actions
When you want to mutate a container in-place, or forward it through a chain of mutating operations, you can use actions. The following examples should make it clear.
Read data into a vector, sort it, and make it unique.
extern std::vector<int> read_data();
Do the same to a vector
that already contains some data:
Mutate the container in-place:
Same as above, but with function-call syntax instead of pipe syntax:
Create Custom Ranges
Range v3 provides a utility for easily creating your own range types, called ranges::view_facade
. The code below uses view_facade
to create a range that traverses a null-terminated string:
class c_string_range
: public ranges::view_facade<c_string_range>
{
friend ranges::range_access;
char const * sz_ = "";
char const & read() const { return *sz_; }
bool equal(ranges::default_sentinel)
const {
return *sz_ ==
'\0'; }
public:
c_string_range() = default;
explicit c_string_range(char const *sz) : sz_(sz)
{
assert(sz != nullptr);
}
};
The view_facade
class generates an iterator and begin/end member functions from the minimal interface provided by c_string_range
. This is an example of a very simple range for which it is not necessary to separate the range itself from the thing that iterates the range. Future examples will show examples of more sophisticated ranges.
With c_string_range
, you can now use algorithms to operate on null-terminated strings, as below:
#include <iostream>
int main()
{
c_string_range r("hello world");
std::cout << ch << ' ';
});
}
Adapting Ranges
Often, a new range type is most easily expressed by adapting an existing range type. That's the case for many of the range views provided by the Range v3 library; for example, the view::remove_if
and view::transform
views. These are rich types with many moving parts, but thanks to a helper class called ranges::view_adaptor
, they aren't hard to write.
Below in roughly 2 dozen lines of code is the transform
view, which takes one range and transforms all the elements with a unary function.
template<class Rng, class Fun>
class transform_view
: public ranges::view_adaptor<transform_view<Rng, Fun>, Rng>
{
friend ranges::range_access;
ranges::semiregular_t<Fun> fun_;
class adaptor : public ranges::adaptor_base
{
ranges::semiregular_t<Fun> fun_;
public:
adaptor() = default;
adaptor(ranges::semiregular_t<Fun> const &fun) : fun_(fun) {}
auto read(ranges::iterator_t<Rng> it) const -> decltype(fun_(*it))
{
return fun_(*it);
}
};
adaptor begin_adaptor() const { return {fun_}; }
adaptor end_adaptor() const { return {fun_}; }
public:
transform_view() = default;
transform_view(Rng && rng, Fun fun)
: transform_view::view_adaptor{std::forward<Rng>(rng)}
{}
};
template<class Rng, class Fun>
transform_view<Rng, Fun>
transform(Rng && rng, Fun fun)
{
return {std::forward<Rng>(rng),
std::move(fun)};
}
Range transformation is achieved by defining a nested adaptor
class that handles the transformation, and then defining begin_adaptor
and end_adaptor
members that return adaptors for the begin iterator and the end sentinel, respectively. The adaptor
class has a read
member that performs the transformation. It is passed an iterator to the current element. Other members are available for customization: equal
, next
, prev
, advance
, and distance_to
; but the transform adaptor accepts the defaults defined in ranges::adaptor_base
.
With transform_view
, we can print out the first 20 squares:
int main()
{
auto squares =
::transform(view::ints(1), [](
int i){
return i*i;});
for(int i : squares | view::take(20))
std::cout << i << ' ';
std::cout << '\n';
}
The transform_view
defined above is an InputRange when it's wrapping an InputRange, a ForwardRange when it's wrapping a ForwardRange, etc. That happens because of smart defaults defined in the adaptor_base
class. That frees you from having to deal with a host of niggly detail when implementing iterators.
*(Note: the above transform_view
always stores a copy of the function in the sentinel. That is only necessary if the underlying range's sentinel type models BidirectionalIterator. That's a finer point that you shouldn't worry about right now.)*
Constrain Functions with Concepts
The Range v3 library makes heavy use of concepts to constrain functions, control overloading, and check type constraints at compile-time. It achieves this with the help of a Concepts Lite emulation layer that works on any standard-conforming C++11 compiler. The library provides many useful concepts, both for the core language and for iterators and ranges. You can use the concepts framework to constrain your own code.
For instance, if you would like to write a function that takes an iterator/sentinel pair, you can write it like this:
template<class Iter, class Sent, class Comp = ,
CONCEPT_REQUIRES_(Sentinel<Sent, Iter>())>
void my_algorithm(Iter
first, Sent last, Comp comp = Comp{})
{
}
You can then add an overload that take a Range:
template<class Rng, class Comp = ,
CONCEPT_REQUIRES_(Range<Rng>())>
void my_algorithm(Rng && rng, Comp comp = Comp{})
{
return my_algorithm(ranges::begin(rng), ranges::end(rng));
}
With the type constraints expressed with the CONCEPTS_REQUIRES_
macro, these two overloads are guaranteed to not be ambiguous.
Range v3 and the Future
Range v3 forms the basis for a proposal to add ranges to the standard library (N4128), and is also be the basis for a Technical Specification on Ranges. The Technical Specification contains many of Range v3's concept definitions (translated into the actual syntax of C++20 Concepts) in addition to constrained versions of the STL algorithms overloaded both for iterator/sentinel pairs and for ranges. The Ranges TS has already been sent to ISO for publication.
The views and actions, as well as various utilities, have not yet been reviewed by the committee, although the basic direction has already passed an initial review. A proposal to add a subset of the views to the Ranges TS is in the early stages.
Range Views
The big advantage of ranges over iterators is their composability. They permit a functional style of programming where data is manipulated by passing it through a series of combinators. In addition, the combinators can be lazy, only doing work when the answer is requested, and purely functional, without mutating the original data. This makes it easier to reason about your code, especially when writing concurrent programs.
Below is a list of the lazy range combinators, or views, that Range v3 provides, and a blurb about how each is intended to be used.
view::adjacent_filter
- For each pair of adjacent elements in a source range, evaluate the specified binary predicate. If the predicate evaluates to false, the second element of the pair is removed from the result range; otherwise, it is included. The first element in the source range is always included. (For instance,
adjacent_filter
with std::not_equal_to
filters out all the non-unique elements.)
view::adjacent_remove_if
- For each pair of adjacent elements in a source range, evaluate the specified binary predicate. If the predicate evaluates to true, the first element of the pair is removed from the result range; otherwise, it is included. The last element in the source range is always included.
view::all
- Return a range containing all the elements in the source. Useful for converting containers to ranges.
any_view<T>(rng)
- Type-erased range of elements with value type
T
; can store any range with this value type.
view::bounded
- Convert the source range to a bounded range, where the type of the
end
is the same as the begin
. Useful for iterating over a range with C++'s range-based for
loop.
view::cartesian_product
- Enumerates the n-ary cartesian product of
n
ranges, i.e., generates all n
-tuples (e1, e2, ... , en)
where e1
is an element of the first range, e2
is an element of the second range, etc.
view::chunk
- Given a source range and an integer N, produce a range of contiguous ranges where each inner range has N contiguous elements. The final range may have fewer than N elements.
view::concat
- Given N source ranges, produce a result range that is the concatenation of all of them.
view::const_
- Present a
const
view of a source range.
view::counted
- Given an iterator
it
and a count n
, create a range that starts at it
and includes the next n
elements.
view::cycle
- Returns an infinite range that endlessly repeats the source range.
view::c_str
- View a
\0
-terminated C string (e.g. from a const char*
) as a range.
view::delimit
- Given a source range and a value, return a new range that ends either at the end of the source or at the first occurrence of the value, whichever comes first. Alternatively,
view::delimit
can be called with an iterator and a value, in which case it returns a range that starts at the specified position and ends at the first occurrence of the value.
view::drop
- Given a source range and an integral count, return a range consisting of all but the first count elements from the source range, or an empty range if it has fewer elements.
view::drop_exactly
- Given a source range and an integral count, return a range consisting of all but the first count elements from the source range. The source range must have at least that many elements.
view::drop_while
- Remove elements from the front of a range that satisfy a unary predicate.
view::empty
- Create an empty range with a given value type.
view::filter
- Given a source range and a unary predicate, filter the elements that satisfy the predicate. (For users of Boost.Range, this is like the
filter
adaptor.)
view::for_each
- Lazily applies an unary function to each element in the source range that returns another range (possibly empty), flattening the result.
view::generate
- Given a nullary function, return an infinite range whose elements are generated with the function.
view::generate_n
- Given a nullary function and a count, return a range that generates the requested number of elements by calling the function.
view::group_by
- Given a source range and a binary predicate, return a range of ranges where each range contains contiguous elements from the source range such that the following condition holds: for each element in the range apart from the first, when that element and the first element are passed to the binary predicate, the result is true. In essence,
view::group_by
groups contiguous elements together with a binary predicate.
view::indirect
- Given a source range of readable values (e.g. pointers or iterators), return a new view that is the result of dereferencing each.
view::intersperse
- Given a source range and a value, return a new range where the value is inserted between contiguous elements from the source.
view::ints
- Generate a range of monotonically increasing
int
s. When used without arguments, it generates the quasi-infinite range [0,1,2,3...]. It can also be called with a lower bound, or with a lower and upper bound (exclusive). An inclusive version is provided by closed_ints
.
view::iota
- A generalization of
view::ints
that generates a sequence of monotonically increasing values of any incrementable type. When specified with a single argument, the result is an infinite range beginning at the specified value. With two arguments, the values are assumed to denote a half-open range.
view::join
- Given a range of ranges, join them into a flattened sequence of elements. Optionally, you can specify a value or a range to be inserted between each source range.
view::keys
- Given a range of
pair
s (like a std::map
), return a new range consisting of just the first element of the pair
.
view::linear_distribute
- Distributes
n
values linearly in the closed interval [from, to]
(the end points are always included). If from == to
, returns n
-times to
, and if n == 1
it returns to
.
view::move
- Given a source range, return a new range where each element has been has been cast to an rvalue reference.
view::partial_sum
- Given a range and a binary function, return a new range where the Nth element is the result of applying the function to the Nth element from the source range and the (N-1)th element from the result range.
view::remove_if
- Given a source range and a unary predicate, filter out those elements that do not satisfy the predicate. (For users of Boost.Range, this is like the
filter
adaptor with the predicate negated.)
view::repeat
- Given a value, create a range that is that value repeated infinitely.
view::repeat_n
- Given a value and a count, create a range that is that value repeated count number of times.
view::replace
- Given a source range, a source value and a target value, create a new range where all elements equal to the source value are replaced with the target value.
view::replace_if
- Given a source range, a unary predicate and a target value, create a new range where all elements that satisfy the predicate are replaced with the target value.
view::reverse
- Create a new range that traverses the source range in reverse order.
view::sample
- Returns a random sample of a range of length
size(range)
.
view::single
- Given a value, create a range with exactly one element.
view::slice
- Give a source range a lower bound (inclusive) and an upper bound (exclusive), create a new range that begins and ends at the specified offsets. Both the begin and the end can be integers relative to the front, or relative to the end with "`end-2`" syntax.
view::sliding
- Given a range and a count
n
, place a window over the first n
elements of the underlying range. Return the contents of that window as the first element of the adapted range, then slide the window forward one element at a time until hitting the end of the underlying range.
view::split
- Given a source range and a delimiter specifier, split the source range into a range of ranges using the delimiter specifier to find the boundaries. The delimiter specifier can be a value, a subrange, a predicate, or a function. The predicate should take an single argument of the range's reference type and return true if and only if the element is part of a delimiter. The function should accept current/end iterators into the source range and return
make_pair(true, iterator_past_the_delimiter)
if the current position is a boundary; otherwise, make_pair(false, cur)
. The delimiter character(s) are excluded from the resulting range of ranges.
view::stride
- Given a source range and an integral stride value, return a range consisting of every Nth element, starting with the first.
view::tail
- Given a source range, return a new range without the first element. The range must have at least one element.
view::take
- Given a source range and an integral count, return a range consisting of the first count elements from the source range, or the complete range if it has fewer elements. (The result of
view::take
is not a SizedRange
.)
view::take_exactly
- Given a source range and an integral count, return a range consisting of the first count elements from the source range. The source range must have at least that many elements. (The result of
view::take_exactly
is a SizedRange
.)
view::take_while
- Given a source range and a unary predicate, return a new range consisting of the elements from the front that satisfy the predicate.
view::tokenize
- Given a source range and optionally a submatch specifier and a
std::regex_constants::match_flag_type
, return a std::regex_token_iterator
to step through the regex submatches of the source range. The submatch specifier may be either a plain int
, a std::vector<int>
, or a std::initializer_list<int>
.
view::transform
- Given a source range and a unary function, return a new range where each result element is the result of applying the unary function to a source element.
view::unbounded
- Given an iterator, return an infinite range that begins at that position.
view::unique
- Given a range, return a new range where all consecutive elements that compare equal save the first have been filtered out.
view::values
- Given a range of
pair
s (like a std::map
), return a new range consisting of just the second element of the pair
.
view::zip
- Given N ranges, return a new range where Mth element is the result of calling
make_tuple
on the Mth elements of all N ranges.
view::zip_with
- Given N ranges and a N-ary function, return a new range where Mth element is the result of calling the function on the Mth elements of all N ranges.
Range Actions
Below is a list of the eager range combinators, or actions, that Range v3 provides, and a blurb about how each is intended to be used.
action::drop
- Removes the first
N
elements of the source range.
action::drop_while
- Removes the first elements of the source range that satisfy the unary predicate.
action::erase
- Removes all elements in the sub-range of the source (range version) or all elements after position.
action::insert
- Inserts all elements of the range into the source at position.
action::join
- Flattens a range of ranges.
action::push_back
- Appends elements to the tail of the source.
action::push_front
- Appends elements before the head of the source.
action::remove_if
- Removes all elements from the source that satisfy the predicate.
action::shuffle
- Shuffles the source range.
action::slice
- Removes all elements from the source that are not part of the sub-range.
action::sort
- Sorts the source range (unstable).
action::split
- Split a range into a sequence of subranges using a delimiter (a value, a sequence of values, a predicate, or a binary function returning a
pair<bool, N>
).
action::stable_sort
- Sorts the source range (stable).
action::stride
- Removes all elements whose position does not match the stride.
action::take
- Keeps the first
N
-th elements of the range, removes the rest.
action::take_while
- Keeps the first elements that satisfy the predicate, removes the rest.
action::transform
- Replaces elements of the source with the result of the unary function.
action::unique
- Removes adjacent elements of the source that compare equal. If the source is sorted, removes all duplicate elements.
Examples
hello ranges
#include <iostream>
#include <string>
using std::cout;
int
main()
{
std::string s{"hello"};
cout << '\n';
}
any_of, all_of, none_of
#include <iostream>
#include <vector>
using std::cout;
auto is_six = [](int i) { return i == 6; };
int
main()
{
std::vector<int> v{6, 2, 3, 4, 5, 6};
cout << std::boolalpha;
cout << "vector: " << ranges::view::all(v) << '\n';
cout <<
"vector any_of is_six: " <<
ranges::any_of(v, is_six) <<
'\n';
cout <<
"vector all_of is_six: " <<
ranges::all_of(v, is_six) <<
'\n';
}
count
#include <iostream>
#include <vector>
using std::cout;
int
main()
{
std::vector<int> v{6, 2, 3, 4, 5, 6};
cout << "vector: " << c << '\n';
std::array<int, 6> a{6, 2, 3, 4, 5, 6};
cout << "array: " << c << '\n';
}
count_if
#include <array>
#include <iostream>
#include <vector>
using std::cout;
auto is_six = [](int i) -> bool { return i == 6; };
int
main()
{
std::vector<int> v{6, 2, 3, 4, 5, 6};
cout << "vector: " << c << '\n';
std::array<int, 6> a{6, 2, 3, 4, 5, 6};
cout << "array: " << c << '\n';
}
find, find_if, find_if_not on sequence containers
#include <array>
#include <deque>
#include <forward_list>
#include <iostream>
#include <list>
#include <vector>
using std::cout;
auto is_six = [](int i) -> bool { return i == 6; };
int
main()
{
cout << "vector: ";
std::vector<int> v{6, 2, 6, 4, 6, 1};
{
cout << "*i: " << *i << '\n';
}
{
if(i == ranges::end(v))
{
cout << "didn't find 10\n";
}
}
{
if(i != ranges::end(v))
{
cout << "*i: " << *i << '\n';
}
}
{
if(i != ranges::end(v))
{
cout << "*i: " << *i << '\n';
}
}
{
i++;
if(i != ranges::end(v))
{
cout << "*i after ++ (2 expected): " << *i;
}
}
cout << "\narray: ";
std::array<int, 6> a{6, 2, 3, 4, 5, 1};
{
if(i != ranges::end(a))
{
cout << "*i: " << *i;
}
}
cout << "\nlist: ";
std::list<int> li{6, 2, 3, 4, 5, 1};
{
if(i != ranges::end(li))
{
cout << "*i: " << *i;
}
}
cout << "\nfwd_list: ";
std::forward_list<int> fl{6, 2, 3, 4, 5, 1};
{
if(i != ranges::end(fl))
{
cout << "*i: " << *i;
}
}
cout << "\ndeque: ";
std::deque<int> d{6, 2, 3, 4, 5, 1};
{
if(i != ranges::end(d))
{
cout << "*i: " << *i;
}
}
cout << '\n';
}
for_each on sequence containers
#include <array>
#include <deque>
#include <forward_list>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <vector>
using std::cout;
auto print = [](int i) { cout << i << ' '; };
int
main()
{
cout << "vector: ";
std::vector<int> v{1, 2, 3, 4, 5, 6};
cout << "\narray: ";
std::array<int, 6> a{1, 2, 3, 4, 5, 6};
cout << "\nlist: ";
std::list<int> ll{1, 2, 3, 4, 5, 6};
cout << "\nfwd_list: ";
std::forward_list<int> fl{1, 2, 3, 4, 5, 6};
cout << "\ndeque: ";
std::deque<int> d{1, 2, 3, 4, 5, 6};
cout << '\n';
}
for_each on associative containers
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
using std::cout;
using std::string;
auto print = [](int i) { cout << i << ' '; };
auto printm = [](std::pair<string, int> p) {
cout << p.first << ":" << p.second << ' ';
};
int
main()
{
cout << "set: ";
std::set<int> si{1, 2, 3, 4, 5, 6};
cout << "\nmap: ";
std::map<string, int> msi{{"one", 1}, {"two", 2}, {"three", 3}};
cout << "\nunordered map: ";
std::unordered_map<string, int> umsi{{"one", 1}, {"two", 2}, {"three", 3}};
cout << "\nunordered set: ";
std::unordered_set<int> usi{1, 2, 3, 4, 5, 6};
cout << '\n';
}
is_sorted
#include <array>
#include <iostream>
#include <vector>
using std::cout;
int
main()
{
cout << std::boolalpha;
std::vector<int> v{1, 2, 3, 4, 5, 6};
std::array<int, 6> a{6, 2, 3, 4, 5, 6};
}