Search code examples
c++stdc++20std-ranges

Why does take(n) used on an istream_view cause it to skip the next token in C++20?


In C++20, when I use std::ranges::views::take() on a std::ranges::istream_view(), the next token in the istream_view after iterating the take view is skipped.

Consider the following C++20 code snippet:

#include <iostream>
#include <ranges>
#include <sstream>
#include <vector>
#include <algorithm>

namespace rn = std::ranges;

int main() {
    std::string input = "1 2 3 4 5 6 7 8 9 10";
    std::istringstream input_stream(input);

    std::vector<int> head;
    rn::copy(rn::istream_view<int>(input_stream) | rn::views::take(5),
             std::back_inserter(head));

    int next_int = 0;
    input_stream >> next_int;

    for(auto x : head) std::cout << x << " ";
    std::cout << next_int << std::endl;
}

When I compile and run this code in g++14 or clang++17, the following output is produced (notice the missing "6"):

1 2 3 4 5 7

Godbolt link: https://godbolt.org/z/vdGvx9666

Why does this happen? Is this the intended behaviour? What can be done to work around this?


Solution

  • This looks like shortcoming of std::ranges::copy.

    Note that return value of it is:

    std::ranges::copy, std::ranges::copy_if, std::ranges::copy_result, std::ranges::copy_if_result - cppreference.com

    Return value

    A ranges::in_out_result containing an input iterator equal to last and an output iterator past the last element copied.

    Now in your case input iterator type is std::ranges::istream_view and this thing to point after last element copied it have to read (consume) next element. This is necessary to detect if current iterator reached end (of stream).

    So to get return value of std::ranges::copy iterator std::ranges::istream_view must be progress beyond last element and read next one leading to problem you are osberving.

    Note that old fashion std::copy_n is defined in different way to prevent this problem: note "possible implementation" increases input iterator ins strange way and the fact that input iterator is not returned.