Can the reading of N input values be made more elegant with C++20?

Viewed 95

Suppose I want to read n integers from the standard input stream. Reading:

How to read N integers into a vector?

I find the recommended solution is basically:

template<class InputIt, class Size, class OutputIt>
OutputIt copy_atmost_n(InputIt first, InputIt last, Size count, OutputIt result)
{
   for (Size i = 0; i < count && first != last; ++i)
      *result++ = *first++;
   return result;
}

auto read_n(std::istream& is, std::size_t n) {
    vector<int> result;
    // possibly reserve n elements
    copy_atmost_n(
       std::istream_iterator<int>(is),
       std::istream_iterator<int>(),
       n,
       std::back_inserter(result)
    );
    return result;
}

I dislike this for the following reasons:

  1. The use of copy_atmost_n() is making an assumption regarding how failure to "dereference" the first iterator will effect first's subsequent value. But this assumed behavior is more idiosyncratic than universally obvious. I'm sure this would confuse lots of developers.
  2. A "staging" variable is needed.
  3. Repetition of std::istream_iterator<int>.

Does C++20, and especially ranges, afford us a nicer approach?

Note: It is not absolutely necessary to return an std::vector<int>. Some kind of lazy structure may also be used if it makes things easier.

1 Answers

The answer should† be:

auto read_n(std::istream& is, std::size_t n) {
    return std::ranges::istream_view<int>(is) | std::views::take(n);
}

if you just want a lazy range. And then collecting that eagerly into a vector<int> as you see fit.


†But that doesn't quite work yet due to LWG 3408, but should be resolved soon by P2259, so it will eventually be correct.

Related