В C++20, когда я использую std::ranges::views::take()
на std::ranges::istream_view()
, следующий токен в istream_view
после итерации представления дубля пропускается.
Рассмотрим следующий фрагмент кода C++20:
#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;
}
Когда я компилирую и запускаю этот код в g++14 или clang++17, получается следующий вывод (обратите внимание на отсутствующую «6»):
1 2 3 4 5 7
Ссылка на Годболт: https://godbolt.org/z/vdGvx9666
Почему это происходит? Это предполагаемое поведение? Что можно сделать, чтобы обойти это?
Проблема в том, как итераторы ввода и диапазоны ввода определяются/обрабатываются в C++. C++26 views::cache_last
и его эталонная реализация cache1
в ranges-v3 могут помочь. Но вам нужно сохранить экземпляр istream_view
. Объект std::istream
уже поглотил свой объект после конечного символа.
Это похоже на недостаток std::ranges::copy
.
Обратите внимание, что возвращаемое значение:
Возвращаемое значение
ranges::in_out_result, содержащий входной итератор, равный последнему, и выходной итератор после последнего скопированного элемента.
Теперь в вашем случае тип итератора ввода — std::ranges::istream_view
, и эта вещь, которую нужно указать после того, как последний скопированный элемент, должна прочитать (потребить) следующий элемент. Это необходимо для определения того, достиг ли текущий итератор конца (потока).
Таким образом, чтобы получить возвращаемое значение std::ranges::copy
итератора std::ranges::istream_view
, необходимо выйти за пределы последнего элемента и прочитать следующий, что приведет к проблеме, которую вы наблюдаете.
Обратите внимание, что старая мода std::copy_n
определяется по-другому, чтобы предотвратить эту проблему: обратите внимание «возможная реализация» странным образом увеличивает входной итератор и тот факт, что входной итератор не возвращается.
Спасибо за упоминание std::copy_n
! Я попробовал использовать istream_iterator
вместо диапазона диапазонов, и это работает: godbolt.org/z/jrxdqP4s8 . Это хороший обходной путь. Однако использование версии copy_n
, std::ranges::copy_n
с диапазонами, похоже, имеет ту же проблему, что и использование std::ranges::copy
с std::ranges::views::take
: godbolt.org/z/x5xsenx9o .
Это известная проблема для Ranges.