On https://en.cppreference.com/w/cpp/ranges, std::views::counted is listed in the range adaptors section. However, it is not tagged as range adaptor object.
I guess that why I can't write using the pipe operator like:
std::vector<size_t> vec = {1, 2, 3, 4, 5};
auto view = vec | std::ranges::counted(... ; // does not compile
My questions are:
- what is a std::ranges::counted? Why is it listed in the range adaptor section?
- what are the use cases? what are the advantages over using take and drop?
CodePudding user response:
Cppreference is following the organization of the C 20 standard. And it puts views::counted
into the "Range Adaptors" section. Despite the fact that the standard says:
These adaptors can be chained to create pipelines of range transformations that evaluate lazily as the resulting view is iterated.
This is not true of the behavior of views::counted
. Indeed, most of the other elements in that section say that their customization points "denotes a range adaptor object" (which describes the piping functionality), but views::counted
does not.
It's unclear why they put it in that section, but it is a useful type in-and-of itself. It's really just an efficient way of saying subrange(it, it n)
. It is efficient in that it doesn't actually increment the iterator by n
.
The advantage it has over take_view
is that take_view
operates on a range, while all counted
needs is an iterator. The main difference is that counted
assumes that there are n
valid iterator positions (and will give UB if that is not the case), while take_view
does not. take_view
will give you up to n
objects, but if the range is shorter than that (as defined by the sentinel), it doesn't try to iterate past the end of the range.
CodePudding user response:
According to the docs
A counted view presents a view of the elements of the counted range [i, n) for some iterator i and non-negative integer n.
A counted range [i, n) is the n elements starting with the element pointed to by i and up to but not including the element, if any, pointed to by the result of n applications of i.
So essentially it returns a slice given a starting iterator and a number of elements to include after that iterator. The example shown in the docs is
#include <ranges>
#include <iostream>
int main()
{
const int a[] = {1, 2, 3, 4, 5, 6, 7};
for(int i : std::views::counted(a, 3))
std::cout << i << ' ';
std::cout << '\n';
const auto il = {1, 2, 3, 4, 5};
for (int i : std::views::counted(il.begin() 1, 3))
std::cout << i << ' ';
std::cout << '\n';
}
Output
1 2 3
2 3 4
Comparing the specific functions you listed, here are their summaries:
std::ranges::views::take
: a view consisting of (up to) the first N elements of another viewstd::ranges::views::drop
: a view consisting of elements of another view, skipping (up to) the first N elementsstd::ranges::views::counted
: creates a subrange from an iterator and a count, always containing exactly N elements
CodePudding user response:
views::counted
is similar to views::take
. However, the former accepts an iterator instead of a range.
One of the benefits of views::counted
over views::take
is that it allows us to construct a sized input/output range when we already know its size:
auto ints = views::istream<int>(std::cin);
auto counted = views::counted(ints.begin(), 4);
auto take = views::take(ints, 4);
static_assert(ranges::sized_range<decltype(counted)>); // ok
static_assert(ranges::sized_range<decltype(take)>); // failed
Unlike views::take
, since we omit the sentinel information, we must ensure that the iterator is still valid after incrementing n
times, while the former is guaranteed to always return a valid range.