Range-v3: User Manual (original) (raw)

Preface


Range library for C++14/17/20. This code is the basis of the range support in C++20.

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.

A notable exception is anything found within the ranges::cpp20 namespace. Those components will change rarely or (preferably) never at all.

Installation


This library is header-only. You can get the source code from the range-v3 repository on github. To compile with Range-v3, just #include the individual headers you want.

This distribution actually contains three separate header-only libraries:

The Range-v3 library is physically structured in directories by feature group:

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: convenience, composability, and correctness.

Convenience

It's more convenient to pass a single range object to an algorithm than separate begin/end iterators. Compare:

std::vector v{};

_t< detail::sort_< L, Fn > > sort

Return a new meta::list that is sorted according to invocable predicate Fn.

Definition: meta.hpp:3277

with

Range-v3 contains full implementations 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.

For instance, the below uses views to filter a container using a predicate and transform the resulting range with a function. Note that the underlying data is const and is not mutated by the views.

std::vector const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

using namespace ranges;

auto rng = vi | views::remove_if([](int i){ return i % 2 == 1; })

constexpr I remove_if(I first, S last, C pred, P proj=P{})

function template remove_if

Definition: remove_if.hpp:42

_t< detail::transform_< Args... > > transform

Return a new meta::list constructed by transforming all the elements in L with the unary invocable Fn...

Definition: meta.hpp:1852

In the code above, rng simply stores a reference to the underlying data and the filter and transformation functions. No work is done until rng is iterated.

In contrast, actions do their work eagerly, but they also compose. Consider the code below, which reads some data into a vector, sorts it, and makes it unique.

extern std::vector read_data();

using namespace ranges;

constexpr action_closure< unique_fn > unique

Definition: unique.hpp:57

Unlike views, with actions each step in the pipeline (actions::sort and actions::unique) accepts a container by value, mutates it in place, and returns it.

Correctness

Whether you are using views or actions, you are operating on data in a pure functional, declarative style. You rarely need to trouble yourself with iterators, although they are there under the covers should you need them.

By operating declaratively and functionally instead of imperatively, we reduce the need for overt state manipulation and branches and loops. This brings down the number of states your program can be in, which brings down your bug counts.

In short, if you can find a way to express your solution as a composition of functional transformations on your data, you can make your code correct by construction.

Views


As described above, 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.

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 that use views:

Filter a container using a predicate and transform it.

std::vector const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

using namespace ranges;

Generate an infinite list of integers starting at 1, square them, take the first 10, and sum them:

using namespace ranges;

| views::take(10), 0);

fold< L, State, Fn > accumulate

An alias for meta::fold.

Definition: meta.hpp:1597

Generate a sequence on the fly with a range comprehension and initialize a vector with it:

using namespace ranges;

auto vi =

})

| tostd::vector();

repeat_n_c< N::type::value, T > repeat_n

Generate list<T,T,T...T> of size N arguments.

Definition: meta.hpp:1899

constexpr auto && for_each

for_each(List, UnaryFunction) calls the UnaryFunction for each argument in the List.

Definition: meta.hpp:2876

View const-ness

Logically, a view is a factory for iterators, but in practice a view is often implemented as a state machine, with the state stored within the view object itself (to keep iterators small) and mutated as the view is iterated. Because the view contains mutable state, many views lack a const-qualified [begin()](group%5F%5Fgroup-range.html#ga446b20253a26c93ef3004fcbfcbf3ec3)/[end()](group%5F%5Fgroup-range.html#ga80d92c391f5b5c0a50156af5f9c9d8c7). When const versions of [begin()](group%5F%5Fgroup-range.html#ga446b20253a26c93ef3004fcbfcbf3ec3)/[end()](group%5F%5Fgroup-range.html#ga80d92c391f5b5c0a50156af5f9c9d8c7) are provided, they are truly const; that is, thread-safe.

Since views present the same interface as containers, the temptation is to think they behave like containers with regard to const-ness. This is not the case. Their behavior with regards to const-ness is similar to iterators and pointers.

The const-ness of a view is not related to the const-ness of the underlying data. A non-const view may refer to elements that are themselves const, and vice versa. This is analogous to pointers; an int* const is a const pointer to a mutable int, and a int const* is a non-const pointer to a const int.

Use non-const views whenever possible. If you need thread-safety, work with view copies in threads; don't share.

View validity

Any operation on the underlying range that invalidates its iterators or sentinels will also invalidate any view that refers to any part of that range. Additionally, some views (e.g., views::filter), are invalidated when the underlying elements of the range are mutated. It is best to recreate a view after any operation that may have mutated the underlying range.

List of range views

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.

views::addressof

Given a source range of lvalue references, return a new view that is the result of taking std::addressof of each.

views::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.)

views::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.

views::all

Return a range containing all the elements in the source. Useful for converting containers to ranges.

any_view(rng)

Type-erased range of elements with value type T; can store any range with this value type.

views::c_str

View a \0-terminated C string (e.g. from a const char*) as a range.

views::cache1

Caches the most recent element within the view so that dereferencing the view's iterator multiple times doesn't incur any recomputation. This can be useful in adaptor pipelines that include combinations of view::filter and view::transform, for instance. views::cache1 is always single-pass.

views::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.

views::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.

views::common

Convert the source range to a common range, where the type of the end is the same as the begin. Useful for calling algorithms in the std:: namespace.

views::concat

Given N source ranges, produce a result range that is the concatenation of all of them.

views::const_

Present a const view of a source range.

views::counted

Given an iterator it and a count n, create a range that starts at it and includes the next n elements.

views::cycle

Returns an infinite range that endlessly repeats the source range.

views::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, views::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.

views::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.

views::drop_last

Given a source range and an integral count, return a range consisting of all but the last count elements from the source range, or an empty range if it has fewer elements.

views::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.

views::drop_while

Remove elements from the front of a range that satisfy a unary predicate.

views::empty

Create an empty range with a given value type.

views::enumerate

Pair each element of a range with its index.

views::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.)

views::for_each

Lazily applies an unary function to each element in the source range that returns another range (possibly empty), flattening the result.

views::generate

Given a nullary function, return an infinite range whose elements are generated with the function.

views::generate_n

Given a nullary function and a count, return a range that generates the requested number of elements by calling the function.

views::chunk_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 previous element are passed to the binary predicate, the result is true. In essence, views::chunk_by groups contiguous elements together with a binary predicate.

views::indirect

Given a source range of readable values (e.g. pointers or iterators), return a new view that is the result of dereferencing each.

views::intersperse

Given a source range and a value, return a new range where the value is inserted between contiguous elements from the source.

views::ints

Generate a range of monotonically increasing ints. 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.

views::iota

A generalization of views::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.

views::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.

views::keys

Given a range of pairs (like a std::map), return a new range consisting of just the first element of the pair.

views::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.

views::move

Given a source range, return a new range where each element has been cast to an rvalue reference.

views::partial_sum

Given a range and a binary function, return a new range where the _N_th element is the result of applying the function to the _N_th element from the source range and the (N-1)th element from the result range.

views::remove

Given a source range and a value, filter out those elements that do not equal value.

views::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.)

views::repeat

Given a value, create a range that is that value repeated infinitely.

views::repeat_n

Given a value and a count, create a range that is that value repeated count number of times.

views::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.

views::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.

views::reverse

Create a new range that traverses the source range in reverse order.

views::sample

Returns a random sample of a range of length size(range).

views::single

Given a value, create a range with exactly one element.

views::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.

views::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.

views::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 an element or a range of elements. The elements matching the delimiter are excluded from the resulting range of ranges.

views::split_when

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 predicate or a function. The predicate should take a 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 an iterator and sentinel indicating the current position and end of the source range and return std::make_pair(true, iterator_past_the_delimiter) if the current position is a boundary; otherwise std::make_pair(false, ignored_iterator_value). The elements matching the delimiter are excluded from the resulting range of ranges.

views::stride

Given a source range and an integral stride value, return a range consisting of every _N_th element, starting with the first.

views::tail

Given a source range, return a new range without the first element. The range must have at least one element.

views::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 views::take is not a sized_range.)

views::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 views::take_exactly is a sized_range.)

views::take_last

Given a source range and an integral count, return a range consisting of the last count elements from the source range. The source range must be a sized_range. If the source range does not have at least count elements, the full range is returned.

views::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.

views::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>.

views::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.

views::trim

Given a source bidirectional range and a unary predicate, return a new range without the front and back elements that satisfy the predicate.

views::unbounded

Given an iterator, return an infinite range that begins at that position.

views::unique

Given a range, return a new range where all consecutive elements that compare equal save the first have been filtered out.

views::values

Given a range of pairs (like a std::map), return a new range consisting of just the second element of the pair.

views::zip

Given N ranges, return a new range where _M_th element is the result of calling make_tuple on the _M_th elements of all N ranges.

views::zip_with

Given N ranges and a _N_-ary function, return a new range where _M_th element is the result of calling the function on the _M_th elements of all N ranges.

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 read_data();

using namespace ranges;

Do the same to a vector that already contains some data:

constexpr move_fn move

Definition: move.hpp:52

Mutate the container in-place:

Same as above, but with function-call syntax instead of pipe syntax:

List of 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.

actions::drop

Removes the first N elements of the source range.

actions::drop_while

Removes the first elements of the source range that satisfy the unary predicate.

actions::erase

Removes all elements in the sub-range of the source (range version) or all elements after position.

actions::insert

Inserts all elements of the range into the source at position.

actions::join

Flattens a range of ranges.

actions::push_back

Appends elements to the tail of the source.

actions::push_front

Appends elements before the head of the source.

actions::remove_if

Removes all elements from the source that satisfy the predicate.

actions::remove

Removes all elements from the source that are equal to value.

actions::reverse

Reverses all the elements in the container.

actions::shuffle

Shuffles the source range.

actions::slice

Removes all elements from the source that are not part of the sub-range.

actions::sort

Sorts the source range (unstable).

actions::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>).

actions::stable_sort

Sorts the source range (stable).

actions::stride

Removes all elements whose position does not match the stride.

actions::take

Keeps the first N-th elements of the range, removes the rest.

actions::take_while

Keeps the first elements that satisfy the predicate, removes the rest.

actions::transform

Replaces elements of the source with the result of the unary function.

actions::unique

Removes adjacent elements of the source that compare equal. If the source is sorted, removes all duplicate elements.

actions::unstable_remove_if

Much faster (each element remove has constant time complexity), unordered version of remove_if. Requires bidirectional container.

Utilities


Below we cover some utilities that range-v3 provides for creating your own view adaptors and iterators.

Create Custom Views with view_facade

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

{

friend ranges::range_access;

char const * sz_ = "";

char const & read() const { return *sz_; }

void next() { ++sz_; }

public:

c_string_range() = default;

explicit c_string_range(char const *sz) : sz_(sz)

{

assert(sz != nullptr);

}

};

constexpr bool equal(I0 begin0, S0 end0, I1 begin1, C pred=C{}, P0 proj0=P0{}, P1 proj1=P1{})

function template equal

Definition: equal.hpp:66

constexpr next_fn next

Definition: operations.hpp:316

Definition: default_sentinel.hpp:26

A utility for constructing a view from a (derived) type that implements begin and end cursors.

Definition: facade.hpp:66

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

int main()

{

c_string_range r("hello world");

std::cout << ch << ' ';

});

}

Create Custom Views with view_adaptor

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 views::remove_if and views::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>

{

friend ranges::range_access;

ranges::semiregular_box_t fun_;

{

ranges::semiregular_box_t fun_;

public:

adaptor() = default;

adaptor(ranges::semiregular_box_t const &fun) : fun_(fun) {}

auto read(ranges::iterator_t it) const -> decltype(fun_(*it))

{

return fun_(*it);

}

};

adaptor begin_adaptor() const { return {fun_}; }

adaptor end_adaptor() const { return {fun_}; }

public:

{}

};

template<class Rng, class Fun>

{

return {std::forward(rng), std::move(fun)};

}

@ forward

satisfies ranges::concepts::forward_range

Definition: adaptor.hpp:110

Definition: transform.hpp:187

Definition: adaptor.hpp:451

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(views::ints(1), [](int i){return i*i;});

for(int i : squares | views::take(20))

std::cout << i << ' ';

std::cout << '\n';

}

The transform_view defined above is an input range when it is wrapping an input range, a forward range when it's wrapping a forward range, 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 bidirectional_iterator. That's a finer point that you shouldn't worry about right now.)*

view_adaptor in details

Each view_adaptor contains base() member in view and iterator. base() - allow to access "adapted" range/iterator:

std::vector vec;

auto list = vec | views::transfom([](int i){ return i+1; });

assert( vec.begin() == list.begin().base() );

assert( vec.begin() == list.base().begin() );

Like basic_iterator's cursor, view_adaptor's adaptor can contain mixin class too, to inject things into the public interface of the iterator:

{

template

struct mixin : BaseMixin

{

using BaseMixin::BaseMixin;

auto& base_value() const

{

return *this->base();

}

int get_i() const

{

return this->get().i;

}

};

int i = 100;

};

From within mixin you can call:

Iterator/sentinel adaptor may "override" the following members:

{

template

constexpr auto begin(Rng &rng)

{

}

template

constexpr auto end(Rng &rng)

{

}

template

bool equal(I const &this_iter, I const &that_iter) const

{

return this_iter == that_iter;

}

template

bool equal(I const &this_iter, I const &that_iter, adaptor const &that_adapt) const

{

return

*this.some_value == that_adapt.some_value

&& this_iter == that_iter;

}

template<typename I, typename S>

constexpr bool empty(I const &it, S const &end) const

{

return it == end;

}

template<typename I, typename S, typename SA>

constexpr bool empty(I const &it, S const &end, SA const &end_adapt) const

{

return

*this.some_value == end_adapt.some_value

&& it == end;

}

template

reference_t read(I const &it)

{

return *it;

}

template

{

++it;

}

template

{

--it;

}

template

void advance(I &it, difference_type_t n)

{

it += n;

}

template

difference_type_t distance_to(I const &this_iter, I const &that_iter)

{

return that_iter - this_iter;

}

template

difference_type_t distance_to

(I const &this_iter, I const &that_iter, adaptor const &that_adapt)

{

return that_iter - this_iter;

}

}

constexpr prev_fn prev

Definition: operations.hpp:337

constexpr advance_fn advance

Definition: operations.hpp:198

constexpr _begin_::fn begin

Definition: access.hpp:182

constexpr _end_::fn end

Definition: access.hpp:313

bool_< 0==size< L >::type::value > empty

An Boolean integral constant wrapper around true if L is an empty type list; false,...

Definition: meta.hpp:2231

As you can see, some "overrides" have effect only for begin_adaptor or end_adaptor. In order to use full potential of adaptor, you need to have separate adaptors for begin and end:

{

int n = 0;

void next(iterator_t& it)

{

++n;

++it;

}

};

{

int stop_at;

bool empty(const iterator_t&, const adaptor& ia, const sentinel_t& s) const

{

return ia.n == stop_at;

}

};

adaptor begin_adaptor() const { return {}; }

sentinel_adaptor end_adaptor() const { return {100}; }

Sometimes, you can use the same adaptor for both begin_adaptor and end_adaptor:

{

int n = 0;

void next(iterator_t& it)

{

++n;

++it;

}

template

bool equal(I const &this_iter, I const &that_iter, adaptor const &that_adapt) const

{

return *this.n == that_adapt.n;

}

};

adaptor begin_adaptor() const { return {}; }

adaptor end_adaptor() const { return {100}; }

Note that all the data you store in the adaptor will become part of the iterator.

If you will not "override" begin_adaptor() or/and end_adaptor() in your view_adaptor, default ones will be used.

Create Custom Iterators with basic_iterator

Here is an example of Range-v3 compatible random access proxy iterator. The iterator returns a key/value pair, like the zip view.

using KeyIter = typename std::vector::iterator;

using ValueIter = typename std::vector::iterator;

struct cursor

{

struct mixin;

using value_type = std::pair<Key, Value>;

{

return { *key_iterator, *value_iterator };

}

bool equal(const cursor& other) const

{

return key_iterator == other.key_iterator;

}

{

++key_iterator;

++value_iterator;

}

{

--key_iterator;

--value_iterator;

}

void advance(std::ptrdiff_t n)

{

key_iterator += n;

value_iterator += n;

}

std::ptrdiff_t distance_to(const cursor& other) const

{

return other.key_iterator - this->key_iterator;

}

cursor() = default;

cursor(KeyIter key_iterator, ValueIter value_iterator)

: key_iterator(key_iterator)

, value_iterator(value_iterator)

{}

KeyIter key_iterator;

ValueIter value_iterator;

};

{

mixin(KeyIter key_iterator, ValueIter value_iterator)

: mixin{ cursor(key_iterator, value_iterator) }

{}

KeyIter key_iterator()

{

return this->get().key_iterator;

}

ValueIter value_iterator()

{

return this->get().value_iterator;

}

};

void test()

{

std::vector keys = {1};

std::vector values = {10};

iterator iter(keys.begin(), values.begin());

Key& key = pair.first;

Value& value = pair.second;

assert(&key == &keys[0]);

assert(&value == &values[0]);

auto key_iter = iter.key_iterator();

assert(key_iter == keys.begin());

}

list< F, S > pair

A list with exactly two elements.

Definition: meta.hpp:2246

Definition: basic_iterator.hpp:532

Definition: basic_iterator.hpp:47

Definition: common_tuple.hpp:278

read() returns references. But the default for value_type, which is decay_t<decltype(read())>, is common_pair<Key&, Value&>. That is not correct in our case. It should be pair<Key, Value>, so we explicitly specify value_type.

[ranges::common_pair](structranges%5F1%5F1common%5F%5Fpair.html) has conversions:

[ranges::common_pair](structranges%5F1%5F1common%5F%5Fpair.html)<Key&, Value&>[ranges::common_pair](structranges%5F1%5F1common%5F%5Fpair.html)<Key, Value>.

All [ranges::common_pair](structranges%5F1%5F1common%5F%5Fpair.html)s converts to their std::pair equivalents, also.

For more information, see http://wg21.link/P0186#basic-iterators-iterators.basic

Concept Checking


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 emulation layer that works on any standard-conforming C++14 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:

CPP_template(class Iter, class Sent, class Comp = )

(requires sentinel_for<Sent, Iter>)

void my_algorithm(Iter first, Sent last, Comp comp = Comp{})

{

}

front< Pair > first

Retrieve the first element of the pair Pair.

Definition: meta.hpp:2251

You can then add an overload that take a Range:

CPP_template(class Rng, class Comp = )

(requires range)

void my_algorithm(Rng && rng, Comp comp = Comp{})

{

}

With the type constraints expressed with the CPP_template macro, these two overloads are guaranteed to not be ambiguous. When compiling with C++20 concepts support, this uses real concept checks. On legacy compilers, it falls back to using std::enable_if.

Range-v3 and the Future


Range-v3 formed the basis for the Technical Specification on Ranges, which has since been merged into the working draft and shipped with C++20 in the std::ranges namespace.

More range adaptors are slated for inclusion in C++23 and beyond.

The actions, as well as various utilities, have not yet been reviewed by the Committee, although the basic direction has already passed an initial review.