Home > Net >  What is std::views::counted?
What is std::views::counted?

Time:12-03

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 view
  • std::ranges::views::drop: a view consisting of elements of another view, skipping (up to) the first N elements
  • std::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.

  • Related