Range Properties of the Standard Range Adaptors
C++, Library, Ranges ·Enumerate the basic usages and properties of C++20-26 Range Adaptors.
Contents
- Contents
- C++20 Range Adaptors
- Factories
- Real Adaptors
views::all(r: [T]) -> [T]views::filter(r: [T], f: T -> bool) -> [T]views::transform(r: [T], f: T -> U) -> [U]views::take(r: [T], n: N) -> [T]views::take_while(r: [T], f: T -> bool) -> [T]views::drop(r: [T], n: N) -> [T]views::drop_while(r: [T], f: T -> bool) -> [T]views::join(r: [[T]]) -> [T]views::lazy_split(r: [T], p: T | [T]) -> [[T]]views::split(r: [T], p: T | [T]) -> [[T]]views::common(r: [T]) -> [T]views::reverse(r: [T]) -> [T]views::elements<I: size_t>(r: [(T1, T2, ..., TN)]) -> [TI]
- Other Standard Views
- C++23 Range Adaptors
- Factories
- Real Adaptors
views::as_rvalue(r: [T]) -> [T&&]views::join_with(r: [[T]], p: U | [U]) -> [common_reference_t<T, U>]views::as_const(r: [T]) -> [T]views::enumerate(r: [T]) -> [(N, T)]views::adjacent<N: size_t>(r: [T]) -> [(T, T, ...)]views::adjacent_transform<N: size_t>(f: (T, T, ...) -> U, r: [T]) -> [U]views::chunk(r: [T], n: N) -> [[T]]views::slide(r: [T], n: N) -> [[T]]views::chunk_by(r: [T], f: (T, T) -> bool) -> [[T]]views::stride(r: [T], n: N) -> [T]
- Other Standard Views
- C++26 Range Adaptors
- Future Range Adaptors In Review
- Factories
- Real Adaptors
views::flat_map(r: [T], f: T -> [U]) -> [U]views::slice(r: [T], m: N, n: N) -> [T]views::unchecked_take(r: [T], n: N) -> [T]views::unchecked_drop(r: [T], n: N) -> [T]views::take_before(r: [T] | It, p: U) -> [T]views::input_filter(r: [T], f: T -> bool) -> [T]views::set_{difference,intersection,union,symmetric_difference}(r1: [T], r2: [U]) -> [T] | [U]views::cycle(r: [T]) -> [T]views::scan(r: [T], f: (Acc/T | U, T) -> U[, init: Acc]) -> [U]
- Other Standard Views
In this post, “range adaptors” refer to both range factories (algorithm that produce range, can only be the starting point of a pipeline, like views::single)
and (real) range adaptors (algorithm that takes a range and return an adapted range, like views::filter).
In C++20 standard, following the adoption of Ranges TS, the standard adopted 18 range adaptors:
- 5 factories:
empty,single,iota,istream,counted - 13 (real) adaptors:
all,filter,transform,take,take_while,drop,drop_while,join,lazy_split,split,common,reverse,elements(keys/valuesare aliases)
Of course, this is only a small subset of what is provided in range-v3 (over 100 adaptors). C++23 greatly expanded range support in multiple ways, including the addition of 14 more adaptors:
- 4 new factories:
zip,zip_transform,cartesian_product,repeat - 10 new (real) adaptors:
as_rvalue,join_with,as_const,enumerate,adjacent,adjacent_transform(pairwiseare aliases),chunk,slide,chunk_by,stride
and C++26 is expected to provide even more (concat and maybe being the most expected ones).
Each adaptor has its own use case, feature, and limitations. Especially, each adaptors has its own accepted range properties, and the output range’s properties also differ. These properties limitations are often not documented, in standard or elsewhere, making determine those properties a pain. Therefore, this post serves as an expansion upon the excellent post by Barry Revzin, adding more range adaptors and adding more properties so that the reference is more complete.
This post still follows the same convention set in the above linked post (W w meaning type and value, [T] means range with refernece type T, (A, B) means tuple<A, B>, A -> B means function taking A and returning B),
and the properties surveyed are the original ones (reference, category, common, sized, const-iterable, borrowed) plus an additional one:
constant (also, value type is included for convenience). A range being a constant range simply means that its iterator are constant iterator, i.e. we cannot modify its elements using its iterators,
so const vector<int> is a constant range but vector<int> is not. Notice that all of those 7 categories can be detected by concepts:
- reference:
ranges::range_reference_t<R> - value type:
ranges::range_value_t<R> - category:
ranges::input_range<R>toranges::contiguous_range<R> - common:
ranges::common_range<R> - sized:
ranges::sized_range<R> - const-iterable:
ranges::range<const R> - borrowed:
ranges::borrowed_range<R> - constant:
ranges::constant_range<R>(C++23)
Note also that value type and reference type are two entirely different beast. Reference type is the type returned by operator*,
and also the type that you interact with more commonly (ranges in this post is referred to as [T], where T is its reference type),
basically you can think reference type as the element type (it is not necessarily a language reference). Value type is often a cvr-unqualified type
that serves as “value of the same type of the element”, which is commonly just reference type minus cvref qualifiers, but not necessarily (value type and reference type can be completely unrelated, as long as
they have a common reference).
All of the original descriptions and properties are copied here, credit belongs to the original author.
C++20 Range Adaptors
Factories
views::empty<T>: [T&]
Produces an empty range of type T.
- constraint:
Tis an object type - reference:
T& - value type:
T - category: contiguous
- common: always
- sized: always (0)
- const-iterable: always
- borrowed: always
- constant: when
Tisconst-qualified
views::single(t: T) -> [T&]
Produce a range that only contains a single value: t.
- constraint:
Tis an object type - reference:
T& - value type:
T - category: contiguous
- common: always
- sized: always (1)
- const-iterable: always
- borrowed: never
- constant: never (
single_view<T>is only instantiated with decayed type)
views::iota(beg: B[, end: E]) -> [B]
Produce a range that start at beg, and incrementing forever (when there is only one argument) or until beg == end (exclude end as usual).
>>> iota(0)
[0, 1, 2, ...]
>>> iota(0, 5)
[0, 1, 2, 3, 4]
>>> iota(beg, end)
[beg, beg + 1, beg + 2, ..., end - 1]
(Note that B and E can be any type, not just integral)
- constraint:
Bis copyable andweakly_incrementable(support pre/postfix++and have difference type) andEissemiregular(copyable and default initializable). Also,beg == end,beg != end(and reverse) are valid. - reference:
B(prvalue range!) - value type:
B - category:
- if
Bis advanceable (beg += n,beg -= n,beg + n,n + beg,beg - n,beg - begare all valid, andBis totally ordered), random access. - otherwise, if
Bis decrementable (support pre/postfix--), then bidirectional - otherwise, if
Bisincrementable(regular andbeg++returnsB), then forward - otherwise, input.
- if
- common: when
BandEare the same type - sized: the range is not infinity (there is a bound provided) and either:
- the range is common and random access, or
- both
BandEare integer-like types, or Eis a sized sentinel forB.
- const-iterable: always
- borrowed: always (iterator owns the current value)
- constant: when
Bis a non-class type (likeint)
views::istream<T>(in: In) -> [T&]
Produce a range of T such that elements are read by in >> t (read one element per increment).
- constraint:
Tis movable and default initializable, andInis derived frombasic_istream - reference:
T& - value type:
T - category: input
- common: never (iterator are move-only, so not a C++17 input iterator anyway)
- sized: never
- const-iterable: never
- borrowed: never
- constant: never (
Tmust be movable so it must not beconst-qualified)
views::counted(it: It, n: N) -> [*It]
This is not a real range adaptor (there is no counted_view). Instead, it is an adaptor that adapt the range represented as [it, it + n) (begin + count)
as the standard iterator-sentinel model. It adapts by construct a std::span or ranges::subrange.
- constraint:
n >= 0andNmust be convertible to the difference type ofIt. - reference: same as
It’s reference type - value type: same as
It’s value type - category: same as
It’s category (preserve contiguous) - common: if
Itis at least random access - sized: always
- const-iterable: always
- borrowed: always (
counted_iteratorowns the iterator and the count) - constant: when
Itis a constant iterator
Real Adaptors
views::all(r: [T]) -> [T]
Still, views::all is a semi-range adaptor; there is no all_view. Essentially, views::all(r) is a view of all the elements in r,
which it done wrapping by either return auto(r) directly (if r is already a view), wrap in ref_view (if r is a lvalue),
or wrap in owning_view otherwise. Therefore, all of views::all(r)’s range properties are exactly identical to that of r’s.
views::filter(r: [T], f: T -> bool) -> [T]
Produce a new range that only preserve elements of r that let f(e) evaluate to true.
>>> filter([1, 2, 3, 4], e => e % 2 == 0)
[2, 4]
- constraint:
Fis copy-constructible, an object type, and is invocable byTandvalue_type&, and return a value that is contextually convertible tobool - reference:
T - value type: same as
r’s value type - category: at most bidirectional
- common: when
ris common - sized: never
- const-iterable: never
- borrowed: never
- constant: when
ris constant
views::transform(r: [T], f: T -> U) -> [U]
Return a new range such that each element is f(e) (where e is each element in r). Commonly called map in other languages.
>>> transform(["aa", "bb", "cc", "dd"], e => e[0])
['a', 'b', 'c', 'd']
- constraint:
Fis move-constructible, an object type, and is invocable byT - reference:
U - value type:
remove_cvref_t<U> - category: at most random access
- common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable andfis const-invocable - borrowed: never
- constant: when
Uis a value of non-class type (like prvalue range ofint) or a const reference (l/rvalue both applies)
views::take(r: [T], n: N) -> [T]
Produce a new range consists of the first n elements of r. If r has less than n elements, contains all of r’s elements.
>>> take([1, 2, 3, 4], 2)
[1, 2]
>>> take([1, 2, 3, 4], 8)
[1, 2, 3, 4]
Note that views::take will produce r’s type whenever possible (for example, empty_view passed in will return an empty_view).
- constraint:
n >= 0andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris sized and random access - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::take_while(r: [T], f: T -> bool) -> [T]
Produce a new range that includes all the element of r that makes f(e) evaluates to true until it first evaluates to false.
(i.e. filter but stop when first false)
>>> take_while([1, 2, 3, 1, 2, 3], e => e < 3)
[1, 2]
- constraint:
Fis an object type and is const-invocable byTandvalue_type&, and return a value that is contextually convertible tobool - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: never (
begin()must return an iterator-to-r, soend()cannot reuse that iterator type) - sized: never
- const-iterable: when
ris const-iterable andfis const-invocable by the reference and lvalue of value type ofas_const(r) - borrowed: never
- constant: when
ris constant
views::drop(r: [T], n: N) -> [T]
Produce a new range consists of the all but the first n elements of r. If r has less than n elements, produce an empty range.
>>> drop([1, 2, 3, 4], 2)
[3, 4]
>>> drop([1, 2, 3, 4], 8)
[]
Note that views::drop will produce r’s type whenever possible (for example, empty_view passed in will return an empty_view).
- constraint:
n >= 0andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::drop_while(r: [T], f: T -> bool) -> [T]
Produce a new range that excludes the element of r until the first element that makes f(e) evaluates to false.
(i.e. drop but stop when first false)
>>> drop_while([1, 2, 3, 1, 2, 3], e => e < 3)
[3, 1, 2, 3]
- constraint:
Fis an object type and is const-invocable byTandvalue_type&, and return a value that is contextually convertible tobool - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris common - sized: when
R’s sentinel is a sized sentinel forR’s iterator (note that in common case this requires common & random access range, but not necessarily; the requirement iss - iandi - sare valid and return the difference type) - const-iterable: never
- borrowed: when
ris borrowed - constant: when
ris constant
views::join(r: [[T]]) -> [T]
Join together a range of several range-of-Ts into a single range-of-T. Commonly called flatten in other languages.
>>> join([[1, 2], [3], [4, 5, 6]])
[1, 2, 3, 4, 5, 6]
- constraint:
randR’s reference type ([T]) are both input ranges - reference:
T - value type: same as
[T]’s value type (the value type of inner range) - category:
- If
ris a range of glvalue ranges, then at most bidirectional based on inner range’s category - Otherwise (range of prvalue ranges), input
- If
- common: when both
rand inner range are forward and common, andris a range of glvalue ranges - sized: never
- const-iterable: when
ris const-iterable and is a range of glvalue ranges - borrowed: never
- constant: when inner range is constant
views::lazy_split(r: [T], p: T | [T]) -> [[T]]
(The fixed version after C++20 DR P2210R2)
Produce a range that splits a range of T into a range of several range-of-Ts based on delimeter (which can be a single element or a continuous subrange).
>>> lazy_split("a bc def", ' ')
["a", "bc", "def"]
>>> lazy_split("a||b|c||d", "||")
["a", "b|c", "d"]
>>> lazy_split("abcd", "") # when size = 0, just split at every element
["a", "b", "c", "d"]
Note that lazy_split is maximally lazy, it will never touch any element until you increment to the element (i.e. will not compute any “next pattern position”),
and thus support input ranges. However, the tradeoff is that the resulting inner range can only be at most forward, as you don’t really know you are at the end until you increment here.
- constraint:
pis either a forward range or convertible to the value type ofR. Also, whenris only an input range,pmust be a sized range with size 0 or 1 (nothing or a single element). (The reference type and lvalues of values ofPandRalso must be inter-comparable) - reference:
[T](lazy_split_view::value_type, a range with reference typeT) - value type: outer range same as reference, inner range same as
r’s value type - category: both outer range and inner range is at most forward based on
R’s category (for a stronger inner range, seeviews::split) - common: outer range when
ris forward and common, inner range never - sized: (both outer and inner range) never
- const-iterable: outer range when
ris const-iterable and bothRandconst Rare forward ranges; inner range always - borrowed: (both outer and inner range) never
- constant: outer range never, inner range when
ris constant
views::split(r: [T], p: T | [T]) -> [[T]]
(The fixed version after C++20 DR P2210R2)
Produce a range that splits a range of T into a range of several range-of-Ts based on delimeter (which can be a single element or a continuous subrange).
>>> split("a bc def", ' ')
["a", "bc", "def"]
>>> split("a||b|c||d", "||")
["a", "b|c", "d"]
>>> split("abcd", "") # when size = 0, just split at every element
["a", "b", "c", "d"]
split is still lazy, but it eagerly computes the start of next subrange when iterating, thus does not support input range but allow subrange to be at most contiguous.
(Since input range is rare and most string algorithm require more than forward range, this should be used in most times)
- constraint:
rmust be a forward range (for splitting input range, uselazy_split),pis either a forward range or convertible to the value type ofR. (The reference type and lvalues of values ofPandRalso must be inter-comparable) - reference:
[T](specifically, the reference type is preciselyranges::subrange<iterator_t<R>>) - value type: same as reference type (inner range’s value type same as
r’s) - category: outer range forward, inner range same as
r(preserve contiguous) - common: outer range when
ris common, inner range always - sized: outer range never, inner range when
r’s sentinel is a sized sentinel (common case is whenris random access) - const-iterable: outer range never, inner range when
ris const-iterable - borrowed: outer range never, inner range always
- constant: outer range never, inner range when
ris constant
views::common(r: [T]) -> [T]
Produce a range with same element as in r, but ensure that the result is a common range.
(Basically exists as a compatibility layer so that pre-C++20 iterator-pair algorithms can use C++20 ranges)
- constraint: either
ris common, or iterators ofRmust be copyable - reference:
T - value type: same as
r’s value type - category: if
ris common or both random access and sized, then same asr(preserve contiguous); otherwise at most forward - common: always (this is the point of this adaptor)
- sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::reverse(r: [T]) -> [T]
Produce a range that contains the reverse of the elements in r.
>>> reverse([1, 2, 3])
[3, 2, 1]
Note that the reverse of reverse_view is simply the base range itself, and subrange passed-in will return subrange too.
- constraint:
ris at least bidirectional - reference:
T - value type: same as
r’s value type - category: at most random access
- common: always
- sized: when
ris sized - const-iterable: when
ris const-iterable andconst Ris common - borrowed: when
ris borrowed - constant: when
ris constant
views::elements<I: size_t>(r: [(T1, T2, ..., TN)]) -> [TI]
Produce a range consists of the I-th element of each element (which are tuples).
views::keys is equivalent to views::element<0>, and views::values is equivalent to views::element<1>.
>>> r = [("A", 1), ("B", 2)]
>>> elements<1>(r) # or values(r)
[1, 2]
>>> keys(r) # or elements<0>(r)
["A", "B"]
- constraint:
r’s reference typeT = (T1, T2, ..., TN)(minus reference) and value type are tuple-like types with size larger thanI, and eitherTis a true reference (not prvalue/proxy range), or theI-th type in the tuple is move constructible. - reference:
TI(the return type ofstd::get<I>(e)whereeis an element ofr), withR’s cvref qualifier copied onto - value type:
remove_cvref_t<reference> - category: at most random access
- common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant (?)
Other Standard Views
(std::initializer_list<T> is technically a view, but it does not model ranges::view.)
std::basic_string_view<charT[, traits[, Alloc]]>: [charT&]
A lightweight view of a constant contiguous sequence of charTs (i.e. a string). Can view const charT*, std::basic_string, and many more.
- constraint:
charTmust be char-like (non-array trivial standard-layout type), andtraitsmust be a character trait - reference:
charT& - value type:
charT - category: contiguous
- common: always
- sized: always
- const-iterable: always
- borrowed: always
- constant: always
std::span<T[, extent: size_t]>: [T&]
A lightweight view of a contiguous sequence of Ts). Can view T*, so a replacement of traditional T* + length idiom.
A span<T> is by default with dynamic extent, and span<T, extent> is a view of fixed size.
- constraint:
Tmust be a complete object type that is not abstract. - reference:
T& - value type:
remove_cvref_t<T> - category: contiguous
- common: always
- sized: always
- const-iterable: always
- borrowed: always
- constant: when
Tisconst-qualified
C++23 Range Adaptors
These are the range adaptors available in C++23 DIS.
Factories
views::zip(r1: [T1], r2: [T2], ...) -> [(T1, T2, ...)]
Produce a new range that is r1, r2, … zipped together; i.e. a range of tuple of each corresponding elements in each of the argument ranges.
>>> zip([1, 2, 3], [4, 5, 6])
[(1, 4), (2, 5), (3, 6)]
>>> zip([1, 2], ["A", "B"], [1.0, 2.0])
[(1, "A", 1.0), (2, "B", 2.0)]
>>> zip()
[] # empty view with type tuple<>
- constraint: all the ranges are input
- reference:
tuple<T1, T2, ...>(note thatTIis the reference type) - value type:
tuple<range_value_t<R1>, ...>(not the reference type minus reference) - category: the weakest category in all of
r1,r2, …, and also at most random access - common: when either of following is true:
- there is only one range (
zip(r)) and this range is common, or - the
zip_viewis not bidirectional (i.e. at most forward), and all ofr1,r2, … are common, or - all of
r1,r2, … are both random access and sized
- there is only one range (
- sized: when all of
r1,r2, … are sized - const-iterable: when all of
r1,r2, … are const-iterable - borrowed: when all of
r1,r2, … are borrowed - constant: when all of
r1,r2, … are constant
views::zip_transform(f: (T1, T2, ...) -> U, r1: [T1], r2: [T2], ...) -> [U]
Produce a new range in which each element is f(e1, e2, ...) where e1, e2 is the corresponding element of r1, r2, … respectfully.
>>> zip_transform(f, [1, 2, 3], [4, 5, 6])
[f(1, 4), f(2, 5), f(3, 6)]
>>> zip_transform((a, b, c) => to_string(a) + b + to_string(c), [1, 2], ["A", "B"], [1.0, 2.0])
["1A1.0", "2B2.0"]
>>> zip_transform(f)
[] # empty view with the type of result of f()
- constraint:
Fis move constructible and is an object type, is invocable byT1, T2, ..., and all the ranges are input - reference:
U - value type:
remove_cvref_t<U> - category: if
f(e1, e2, ...)does not return a lvalue reference, input; otherwise the weakest category in all ofr1,r2, …, and also at most random access - common: when
zip(r1, r2, ...)is common - sized: when all of
r1,r2, … are sized - const-iterable: when all of
r1,r2, … are const-iterable andfis const-invocable - borrowed: never
- constant: when the result of
f(e1, e2, ...)is a non-class orstd::tuple(pr)value, or when it returns a constant reference
views::cartesian_product(r1: [T1], r2: [T2], ...) -> [(T1, T2, ...)]
Produce a new range that is r1, r2, … cartesian producted together; i.e. a range of tuple of every possible pair of elements in each of the argument ranges.
>>> cartesian_product([1, 2, 3], [4, 5, 6])
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
>>> cartesian_product([1, 2], ["A", "B"], [1.0, 2.0])
[(1, "A", 1.0), (1, "A", 2.0), (1, "B", 1.0), (1, "B", 2.0),
(2, "A", 1.0), (2, "A", 2.0), (2, "B", 1.0), (2, "B", 2.0)]
>>> cartesian_product()
[()] # views::single(std::tuple<>{})
- constraint: all of
r2, r3, ...(all ranges except the first one) are forward - reference:
tuple<T1, T2, ...>(note thatTIis the reference type) - value type:
tuple<range_value_t<R1>, ...>(not the reference type minus reference) - category:
- if the first range
r1is random access, and all other ranges are both random access and sized, then random access - otherwise, if the first range
r1is bidirectional, and all other ranges are either both bidirectional and common, or both random access and sized, then bidirectional - otherwise, if the first range
r1is forward, then forward - otherwise, input
- if the first range
- common: when
r1is common, or is both sized and random access - sized: when all of
r1,r2, … are sized - const-iterable: when all of
r1,r2, … are const-iterable - borrowed: never
- constant: when all of
r1,r2, … are constant
views::repeat(t: T[, n: N]) -> [const T&]
Produce a range that repeats the same value t either infinitely (when there is only one argument), or for n times.
>>> repeat(2)
[2, 2, 2, ...]
>>> repeat(2, 5)
[2, 2, 2, 2, 2]
- constraint:
Tis move constructible and is an object type;N(if provided) is a semiregular integer-like type - reference:
const T& - value type:
T - category: random access
- common: when the resulting range is not infinite (i.e. when
nis provided) - sized: when the resulting range is not infinite (i.e. when
nis provided) - const-iterable: always
- borrowed: never
- constant: always
Real Adaptors
views::as_rvalue(r: [T]) -> [T&&]
Produce a range with same element as in r, but ensure that the result is a range of rvalue reference.
(Basically, did a std::move on each element so that you can then move every element from the view into some container or things like that)
- constraint:
ris an input range - reference:
range_rvalue_reference_t<R>(which normally is the result type ofstd::move(*r.begin())so basicallyT&&, but you can customize it byranges::iter_move) - value type: same as
r’s value type - category: at most random access (the original intent is that
as_rvalue_viewshould be input-only, but later P2520R0 changedas_rvalue_viewto now be up to random access) - common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::join_with(r: [[T]], p: U | [U]) -> [common_reference_t<T, U>]
Join together a range of several range-of-Ts into a single range-of-T, with p inserted between each parts. This is the reverse of views::split.
>>> join_with([[1, 2], [3], [4, 5, 6]], 3)
[1, 2, 3, 3, 3, 4, 5, 6]
>>> join_with([[1, 2], [3], [4, 5, 6]], [3, 4])
[1, 2, 3, 4, 3, 3, 4, 4, 5, 6]
- constraint:
randR’s reference type ([T]) are both input ranges,pis either a forward range or convertible to the value type ofR. (inner range andp’s value and reference type must also have common reference) - reference:
common_reference_t<T, U> - value type: the
common_type_tof inner range andp’s value type - category:
- If
ris a range of prvalue ranges, then input - Otherwise (
ris a range of glvalue ranges), and ifris bidirectional, and inner range andpare both bidirectional and common, then bidirectional - Otherwise, if
rand inner range are both forward, then forward - Otherwise, input
- If
- common: when both
rand inner range are forward and common, andris a range of glvalue ranges - sized: never
- const-iterable: when both
randpare const-iterable andris a range of glvalue ranges - borrowed: never
- constant: when inner range and
pare both constant
views::as_const(r: [T]) -> [T]
Produce a range with same element as in r, but ensure that the result’s element cannot be modified (i.e. a constant range).
(Basically, did a std::as_const on each element so that you cannot modify them, albeit with a much more complicated algorithm that avoid wrapping if at all possible by delegate to std::as_const)
- constraint:
ris an input range - reference:
range_const_reference_t<R>(assumingr’s value type is justremove_cvref_tof its reference, then for a range ofT&, justconst T&; for a range ofT&&, justconst T&&; for a range of prvalueT, justT) - value type: same as
r’s value type - category: same as
r’s category (preserve contiguous) - common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: always
views::enumerate(r: [T]) -> [(N, T)]
Produce a range such that each of the original elements of r is accompanied by its index in r.
>>> enumerate([1, 3, 6])
[(0, 1), (1, 3), (2, 6)]
(Notice that the index type N is range_difference_t<R>)
- constraint:
ris an input range, andTis move constructible. - reference:
tuple<N, T> - value type:
tuple<N, range_value_t<R>>(not the reference type minus reference) - category: at most random access
- common: when
ris common and sized - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::adjacent<N: size_t>(r: [T]) -> [(T, T, ...)]
Produce a new range where each elements is a tuple of the next consecutive N elements. pairwise is an alias for adjacent<2>.
If r has less than N elements, the resulting range is empty.
>>> adjacent<4>([1, 2, 3, 4, 5, 6])
[(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)]
>>> pairwise(["A", "B", "C"]) # or adjacent<2>
[("A", "B"), ("B", "C")]
>>> adjacent<7>([1, 2, 3])
[] # empty view with type tuple<int&, int&, ...> (repeat 7 times)
>>> adjacent<0>([1, 2, 3, 4, 5, 6])
[] # empty view with type tuple<>
- constraint:
ris a forward range andN > 0 - reference:
tuple<T, T, ...>(repeatNtimes, note thatTis the reference type ofr) - value type:
tuple<range_value_t<R>, ...>(repeatNtimes, not the reference type minus reference) - category: at most random access
- common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::adjacent_transform<N: size_t>(f: (T, T, ...) -> U, r: [T]) -> [U]
Produce a new range where each elements is the result of f(e1, e2, ...), where e1, e2, ... are the next consecutive N elements. pairwise_transform is an alias for adjacent_transform<2>.
If r has less than N elements, the resulting range is empty.
>>> adjacent_transform<4>(f, [1, 2, 3, 4, 5, 6])
[f(1, 2, 3, 4), f(2, 3, 4, 5), f(3, 4, 5, 6)]
>>> adjacent_transform<4>((a, b, c, d) => a + b + c + d, [1, 2, 3, 4, 5, 6])
[10, 14, 18]
>>> pairwise_transform((a, b) => a + b, ["A", "B", "C"]) # or adjacent_transform<2>
["AB", "BC"]
>>> adjacent_transform<0>(f, [1, 2, 3, 4, 5, 6])
[] # empty view with type of the result of f()
- constraint:
ris a forward range,N > 0,Fis move constructible and is an object type, and invocable byT, T, ...(repeatNtimes) - reference:
U - value type:
remove_cvref_t<U> - category: if
f(e1, e2, ...)does not return a lvalue reference, then input; otherwise at most random access - common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable andfis const-invocable - borrowed: never
- constant: when the result of
f(e1, e2, ...)is a non-class orstd::tuple(pr)value, or when it returns a constant reference
views::chunk(r: [T], n: N) -> [[T]]
Produce a new range-of-range that is the result of dividing r into non-overlapping n-sized chunks (except that last chunk can be smaller than n).
>>> chunk([1, 2, 3, 4, 5], 2)
[[1, 2], [3, 4], [5]]
>>> chunk([1, 2, 3, 4], 8)
[[1, 2, 3, 4]]
- constraint:
n > 0andNis convertible tor’s difference type - reference:
[T](ifris forward, then actually the reference type isranges::subrange<iterator_t<R>>) - value type: same as reference type (inner range’s value type same as
r’s) - category: outer range at most random access, inner range same as
r(preserve contiguous) - common: outer range when
ris common and either both sized and bidirectional, or forward and not bidirectional; inner range whenris common - sized: outer range when
ris sized, inner range whenr’s sentinel is a sized sentinel (common case is whenris random access) - const-iterable: (both outer and inner range) when
ris const-iterable and forward - borrowed: outer range when
ris borrowed and forward, inner range whenris forward - constant: outer range never, inner range when
ris constant
views::slide(r: [T], n: N) -> [[T]]
Produce a new range-of-range that is the result of dividing r into overlapping n-sized chunks (basically, the m-th range is a view into the m-th through m+n-1-th elements of r).
This is similar to views::adjacent<n> with the difference being that adjacent require a compile-time size and produce range-of-tuples, while views::slide require a runtime size
and provide range-of-ranges.
>>> slide([1, 2, 3, 4, 5], 2)
[[1, 2], [2, 3], [3, 4], [4, 5]]
>>> slide([1, 2, 3, 4], 8)
[]
- constraint:
ris a forward range,n > 0, andNis convertible tor’s difference type - reference:
[T](specifically, the reference type is precisely the result type ofviews::counted(r.begin(), n), which is a span (whenris contiguous) or aranges::subrangeotherwise) - value type: same as reference type (inner range’s value type same as
r’s) - category: outer range at most random access, inner range same as
r(preserve contiguous) - common: outer range when
ris common or is both random access and sized, inner range whenris random access - sized: outer range when
ris sized, inner range always - const-iterable: outer range when
ris const-iterable and random access and sized, inner range always - borrowed: outer range when
ris borrowed, inner range always - constant: outer range never, inner range when
ris constant
views::chunk_by(r: [T], f: (T, T) -> bool) -> [[T]]
Produce a new range-of-range such that f is invoked on consecutive elements, and a new group is started when f returns false.
>>> chunk_by([1, 2, 2, 3, 1, 2, 0, 4, 5, 2], (a, b) => a <= b)
[[1, 2, 2, 3], [1, 2], [0, 4, 5], [2]]
>>> chunk_by([1, 2, 2, 3, 1, 2, 0, 4, 5, 2], (a, b) => a >= b)
[[1], [2, 2], [3, 1], [2, 0], [4], [5, 2]]
- constraint:
ris a forward range, andfis invocable on any combination ofTandr’s value type (and return a contextually-convertible-to-bool) - reference:
[T](specifically, the reference type is preciselyranges::subrange<iterator_t<R>>) - value type: same as reference type (inner range’s value type same as
r’s) - category: outer range at most bidirectional, inner range same as
r(preserve contiguous) - common: outer range when
ris common, inner range always - sized: outer range never, inner range when
r’s sentinel is a sized sentinel (common case is whenris random access) - const-iterable: outer range never, inner range when
ris const-iterable - borrowed: outer range never, inner range always
- constant: outer range never, inner range when
ris constant
views::stride(r: [T], n: N) -> [T]
Produce a new range consists of an evenly-spaced subset of r (with space fixed at n).
>>> stride([1, 2, 3, 4], 2)
[1, 3]
>>> stride([1, 2, 3, 4, 5, 6, 7], 3)
[1, 4, 7]
- constraint:
n > 0andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: at most random access
- common: when
ris common and either sized or non-bidirectional - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
Other Standard Views
std::generator<T[, U[, Alloc]]> : [U ? T : T&&]
Produce a view of all the things you have co_yielded in a coroutine.
std::generator<int> ints(int start = 0) {
while (true) co_yield start++;
}
void f() {
std::println("{}", ints(3) | views::take(3)); // [3, 4, 5]
}
- constraint: if
Uis present, it is a cv-unqualified object type andTis either a true reference or copy constructible - reference: if
Uis present,T; otherwiseT&& - value type: if
Uis present,U; otherwiseremove_cvref_t<T> - category: input
- common: never
- sized: never
- const-iterable: never
- borrowed: never
- constant: when
Tis a const reference
C++26 Range Adaptors
Factories
views::concat(r1: [T1], r2: [T2], ...) -> [common_reference_t<T1, T2, ...>]
Produce a new range that is r1, r2, … concated head-to-tail together; i.e. a range that starts at the first element of the first range, ends at the last element of the last range, with all
range elements sequenced in between respectively in the order of arguments.
>>> concat([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]
>>> concat([1, 2], [3, 4], [1.0, 2.0])
[1.0, 2.0, 3.0, 4.0, 1.0, 2.0]
# concat() is ill-formed
- constraint: all of
T1,T2, … have acommon_reference_t, each of which is converted to that common reference type, and all ofr1,r2, …’s value type have acommon_type_t - reference:
common_reference_t<T1, T2, ...> - value type:
common_type_t<range_value_t<R1>, ...>(not the reference type minus reference) - category:
- if all of
r1,r2, … are random access, and all but the last range are common, then random access - otherwise, if all of
r1,r2, … are bidirectional, and all but the last range are common, then bidirectional - otherwise, if all of
r1,r2, … are forward, then forward - otherwise, input
- if all of
- common: when the last range
rnis common - sized: when all of
r1,r2, … are sized - const-iterable: when all of
r1,r2, … are const-iterable - borrowed: never (can be made conditionally borrowed, but the space cost is too high)
- constant: when all of
r1,r2, … are constant
views::indices(n: N) -> [N]
A convenient alias/alternative for views::iota(0uz, ranges::size(r)). Basically, produce a range of 0, 1, …, n - 1.
>>> indices(5)
[0, 1, 2, 3, 4]
- constraint:
Nmust be an integral type - reference:
N(prvalue range!) - value type:
N - category: random access
- common: always
- sized: always
- const-iterable: always
- borrowed: always (iterator owns the current value)
- constant: always
Real Adaptors
views::cache_latest(r: [T]) -> [T&]
Cache the last element of any range to avoid extra work.
For example: r | views::transform(f) | views::filter(g) will call f twice for every element of r when iterating, because filter dereferences twice on each iteration. If you add views::cache_latest between the two adaptor, f will only be called once per element.
- constraint:
ris an input range - reference:
T&(force lvalue reference here) - value type: same as
r’s value type - category: input
- common: never
- sized: when
ris sized - const-iterable: never
- borrowed: never
- constant: when
ris constant
views::as_input(r: [T]) -> [T]
Downgrade any range to an input, non-common range.
Useful to avoid expensive operations that many range algorithm/adaptor perform to preserve higher properties. For example:
views::join’s iterator comparison need to do two base iterator comparisons (one for outer and one for inner) for common range, but only one is needed for non-common range.views::chunkhave more expensive algorithm when passed with a forward range: iterating through chunk border will incur a whole pass of all the elements for forward ranges.
(Note that views::as_input will produce r’s type whenever possible)
- constraint:
ris an input range - reference:
T - value type: same as
r’s value type - category: input (this is the point of this adaptor)
- common: never
- sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
Other Standard Views
std::optional<T>: [T&]
In C++26, std::optional<T>, who represents an object that may or may not store a T, is upgraded to model view. The underlying intention is for optional to behave as a container of 0 or 1 elements.
>>> optional<int>()
[]
>>> optional<int>(1)
[1]
- reference:
T& - value type:
remove_cv_t<T> - category: contiguous
- common: always
- sized: always (0 if disengaged, 1 if engaged)
- const-iterable: always
- borrowed: never
- constant: when
Tisconst-qualified
Future Range Adaptors In Review
Factories
views::nullable(n: std::maybe<T>) -> [T&]
(Current design as of P1255R14.)
Produce a new range of 0 or 1 element based on a nullable object.
int* p = new int(3);
nullable(p) // [3]
int* q = nullptr;
nullable(q) // []
- constraint:
Nis copyable, an object type, and modelsstd::maybe(basically dereferencable and contextually convertible tobool). (Or areference_wrapperof such a type) - reference:
T&(the iterator type is actuallyT*) - value type:
T - category: contiguous
- common: always
- sized: always
- const-iterable: always
- borrowed: when
Nis a pointer, areference_wrapperor a reference - constant: when
Tisconst-qualified
any_view<V[, Opts[, R[, RR[, Diff]]]]>: [R ? R : V&]
(Current design as of P3411R4.)
A type-erased view that allows customizing the traversal category and other properties. Useful for hiding the concrete result type of a range pipeline, such as:
ranges::any_view<Widget> getWidgets()
{
std::vector<Widget> widgets_{ /* ... */ };
return widgets_ | views::filter(/* ... */) | views::take_while(/* ... */);
}
Here, if you used auto as return type, the return type will be take_while_view<filter_view<vector<Widget>, ...>, ...>, which is not only complicated to spell and mangle, but also exposed internal implementation. Using any_view here hide that nicely.
The Opts template parameter (defaults to any_view_options::input) is a scoped enum that specifies the category, sized, borrowedness and copyability of the resulting any_view:
enum class any_view_options
{
input = 1,
forward = 3,
bidirectional = 7,
random_access = 15,
contiguous = 31,
sized = 32,
borrowed = 64,
copyable = 128
} Opts;
Users are expected to bit-or these options to construct the desired composition of properties. Note that RRef specifies the desired range_rvalue_reference_t (defaults to Ref - & + &&).
- reference:
R(defaults toV&if not specified) - value type:
V - category: depends on
Opts - common: never
- sized: depends on
Opts - const-iterable: never
- borrowed: depends on
Opts - constant: depends on
V,RandRRef
views::null_term(r: *[T]) -> [T]
(Current design as of P3705R2.)
A view that produces a null-terminated range from an starting iterator. For instance, for const char* long_string, views::null_term(long_string) effectively represents a cstring_view of this NTBS without the overhead of computing the length.
(Note that this view is just an alias of subrange(r, std::null_sentinel), where null_sentinel is a simple sentinel providing operator== that forwards to *rng == T().)
- constraint:
ris an iterator with a default initializable value type. - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: never
- sized: never
- const-iterable: when
ris const-iterable - borrowed: never
- constant: when
ris constant
Real Adaptors
views::flat_map(r: [T], f: T -> [U]) -> [U]
(Current design as of P3211R1.)
Transform the input sequence to a range-of-range, and then join all the ranges. Following P2328, this adaptor can be implemented directly as views::transform(r, f) | views::cache_latest | views::join, but this adaptor is not just an alias for that because a standalone view can cache the transform result and avoid repeated calls without reducing the entire range back to input (which cache_latest would do).
>>> flat_map([0, 1, 2], x => [x, x, x])
[0, 0, 0, 1, 1, 1, 2, 2, 2]
- constraint:
rand[U]are both input ranges,Fis move-constructible, an object type, and is invocable byT - reference:
U - value type: same as
[U]’s value type (the value type of invocation result) - category:
- If
[U]is a glvalue range, then at most bidirectional based on[U]’s category - Otherwise (range of prvalue ranges), input
- If
- common: when both
rand[U]are forward and common, and[U]is a glvalue range - sized: never
- const-iterable: when
ris const-iterable andfis const-invocable, and[U]is a glvalue range - borrowed: never
- constant: when
[U]is constant
views::slice(r: [T], m: N, n: N) -> [T]
(Current design as of P3216R1.)
Produce a new range consists of the m-th to n-th (as usual, left inclusive, right exclusive) elements of r. If r has less than n elements, contains all the elements after the m-th. If r has less than m elements, produce an empty range.
>>> slice([1, 2, 3, 4, 5], 1, 3)
[2, 3]
>>> slice([1, 2, 3, 4, 5], 1, 10)
[2, 3, 4, 5]
>>> slice([1, 2, 3, 4, 5], 10, 12)
[]
Note that views::slice will produce r’s type whenever possible (for example, empty_view passed in will return an empty_view), even if views::slice(r, m, n) is not just an alias for views::take(views::drop(r, m), n - m). (The reason for a dedicated view boils down to performance and support for reserve_hint().)
- constraint:
n >= m && m >= 0andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris sized and random access - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::unchecked_take(r: [T], n: N) -> [T]
(Current design as of P3230R2.)
A variation of views::take that assumes there are at least n elements in r.
In other words, more efficient in common cases but is UB if you try to take more than length elements.
>>> unchecked_take([1, 2, 3, 4], 2)
[1, 2]
Note that views::unchecked_take will produce r’s type whenever possible (for example, span passed in will return an span). Also note that views::unchecked_take may downgrade infinite ranges to finite ones (views::iota(0) | views::unchecked_take(5) is just views::iota(0, 5), while views::take cannot preserve type when iota_view is not sized).
- constraint:
n >= 0 && n <= ranges::distance(r)andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris random access - sized: always (
n) - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::unchecked_drop(r: [T], n: N) -> [T]
(Current design as of P3230R2.)
A variation of views::drop that assumes there are at least n elements in r.
In other words, more efficient in common cases but is UB if you try to drop more than length elements.
>>> unchecked_drop([1, 2, 3, 4], 2)
[3, 4]
Note that views::unchecked_drop will produce r’s type whenever possible (for example, span passed in will return an span). Also note that views::unchecked_drop may process infinite ranges better (views::iota(0) | views::unchecked_drop(5) is just views::iota(5), while views::drop cannot preserve type when iota_view is not sized).
- constraint:
n >= 0 && n <= ranges::distance(r)andNis convertible tor’s difference type - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: when
ris common - sized: when
ris sized - const-iterable: when
ris const-iterable - borrowed: when
ris borrowed - constant: when
ris constant
views::take_before(r: [T] | It, p: U) -> [T]
(Current design as of P3220R1.)
Produce a new range that includes all the element of r until p (inclusive). Similar to views::take_while but using a value instead of a predicate for ending detection. Very useful in cases like importing NTBS ranges with views::take_before(str, '\0').
>>> take_before([1, 2, 3, 4, 5], 3)
[1, 2, 3]
>>> take_before([1, 2, 3, 4, 5], 6)
[1, 2, 3, 4, 5]
- constraint:
ris an input range, andUis an object type and move constructible andt == uis well-formed for bothTandr’s value type. Ifrdoes not modelrange(i.e.It), then it must be an input iterator. - reference:
T - value type: same as
r’s value type - category: same as
r(preserve contiguous) - common: never (
begin()must return an iterator-to-r, soend()cannot reuse that iterator type) - sized: never
- const-iterable: when
ris const-iterable andUis equality comparable with the reference and lvalue of value type ofas_const(r) - borrowed: when
Tis a scalar type - constant: when
ris constant
views::input_filter(r: [T], f: T -> bool) -> [T]
(Current design as of P3725R1.)
An input-only version of views::filter that prevents the mutate-and-then-iterate problem. Note that unlike most other input-only views, this view supports const iteration.
>>> input_filter([1, 2, 3, 4], e => e % 2 == 0)
[2, 4]
- constraint:
Fis copy-constructible, an object type, and is invocable byTandvalue_type&, and return a value that is contextually convertible tobool - reference:
T - value type: same as
r’s value type - category: input
- common: when
ris common - sized: never
- const-iterable: always
- borrowed: never
- constant: when
ris constant
views::set_{difference,intersection,union,symmetric_difference}(r1: [T], r2: [U]) -> [T] | [U]
(Current design as of P3741R0.)
These four views performs common set operations between two ranges:
views::set_difference(A, B)returns elements inAthat is not inBviews::set_intersection(A, B)returns elements inAthat is also inBviews::set_union(A, B)returns elements that are either inAor inBor bothviews::set_symmetric_difference(A, B)returns elements that are either inAor inB, but not both>>> set_difference([1, 2, 3, 4], [1, 2, 5, 6]) [3, 4] >>> set_intersection([1, 2, 3, 4], [1, 2, 5, 6]) [1, 2] >>> set_union([1, 2, 3, 4], [1, 2, 5, 6]) [1, 2, 3, 4, 5, 6] >>> set_symmetric_difference([1, 2, 3, 4], [1, 2, 5, 6]) [3, 4, 5, 6]- constraint:
r1andr2are both input ranges, their iterator have an indirect strict weak order (basically just have comparison betweenTanUand their value types). Forunionandsymmetric_difference, also requires thatr1andr2can be concatenated usingviews::concat - reference: for
differenceandintersection,T; forunionandsymmetric_difference,common_reference_t<T, U> - value type: for
differenceandintersection, same asr1’s value type; forunionandsymmetric_difference,common_type_t<range_value_t<R1>, range_value_t<R2>> - category: at most forward
- common: never
- sized: never
- const-iterable: for
set_union, always; for other three, never - borrowed: when both
r1andr2are borrowed - constant: when both
r1andr2are constant
views::cycle(r: [T]) -> [T]
(Current design as of P3806R0.)
Produce a new range that repeatedly cycle through all the element of r.
>>> cycle([1, 2, 3])
[1, 2, 3, 1, 2, 3, 1, 2, 3, ...]
>>> cycle([])
[]
- constraint:
ris a forward range - reference:
T - value type: same as
r’s value type - category: if
ris random access and sized, then random access; otherwise, at most forward - common: never (
end()returnsdefault_sentinel) - sized: never (infinite range)
- const-iterable: when
ris const-iterable andconst Ris a forward range - borrowed: never
- constant: when
ris constant
views::scan(r: [T], f: (Acc/T | U, T) -> U[, init: Acc]) -> [U]
(Current design as of P3351R3.)
Produce a new range that takes a range and a function that takes the current element and the current state as parameters. Basically, views::transform with a stateful function. Optionally takes an initial seed to be used as the initial accumulator.
>>> scan([1, 2, 3, 4, 5], (a, b) => a + b)
[1, 3, 6, 10, 15]
>>> scan([1, 2, 3, 4, 5], (a, b) => a + b, 10)
[11, 13, 16, 20, 25]
- constraint:
Fis move-constructible, an object type, and is invocable by either(Acc, T)(if provided initial seed) or(T, T)(if not), while also invocable by(U, T)whereUis the return type ofFinvoking on these parameter types. - reference:
U - value type:
remove_cvref_t<U> - category: input (due to stashing iterator concerns it cannot be forward)
- common: never (iterator need to store accumulator)
- sized: when
ris sized - const-iterable: when
ris const-iterable andfis const-invocable - borrowed: never
- constant: when
Uis a value of non-class type (like prvalue range ofint) or a const reference (l/rvalue both applies)
Other Standard Views
std::filesystem::path_view : [const path_view_component&]
(Current design as of P1030R8.)
path_view represents a trivially copyable view of explicitly unencoded or encoded character sequences in the format of a native or generic filesystem path. When iterated, it yields a path_view_component that represents a part of a path not separated by path separators.
>>> path_view("/foo/bar")
["foo", "bar"]
- reference:
const path_view_component& - value type:
path_view_component - category: bidirectional
- common: always
- sized: never
- const-iterable: always
- borrowed: never
- constant: always
std::basic_cstring_view<charT[, traits[, Alloc]]>: [charT&]
(Current design as of P3655R3.)
A lightweight view of a constant contiguous sequence of charTs (i.e. a string) with guaranteed null (\0) termination. Can view C strings (null-terminated const charT*), std::basic_string, and many more.
- constraint:
charTmust be char-like (non-array trivial standard-layout type), andtraitsmust be a character trait - reference:
charT& - value type:
charT - category: contiguous
- common: always
- sized: always
- const-iterable: always
- borrowed: always
- constant: always