operator++ в std::istream_iterator не итерируется по итератору
Изучаю итератор std::istream_iterator. Я пытаюсь проитерироваться по нему. Подскажите, пожалуйста, почему следующий код не работает как ожидается?
#include <iostream>
#include <sstream>
#include <iterator>
int main()
{
std::istringstream str("0.1 0.2 0.3 0.4");
std::istream_iterator<double> iter(str);
int n = std::distance(iter, std::istream_iterator<double>());
for (int i = 0; i < n; i++) {
std::cout << *iter << std::endl;
++iter;
}
}
Как результат, выводится первый элемент 4 раза:
0.1
0.1
0.1
0.1
Ответы (2 шт):
Все давольно просто: давайте немного модифицируем ваш пример
#include <iostream>
#include <sstream>
#include <iterator>
int main()
{
std::istringstream str("0.1 0.2 0.3 0.4");
std::istream_iterator<double> iter(str);
for (int i = 0; i < 4; i++) {
std::cout << *iter << std::endl;
++iter;
}
}
Я убрал distance и в цикле выставил константное значение. Что мы видим в output:
0.1
0.2
0.3
0.4
Это явно означает то, что distance изменяет наш итератор! Но distance должна копировать его, как так получилось? Дело в том, что конструктор копирования istream_iterator тривиален а значит при изменении копии у нас будет "как бы меняться" оригинал. Это связано с тем, что этот итератор завязан именно на stream, а в них вы не можете хранить состояние.
Проблема в том, что std::istream_iterator является итератором ввода (input iterator). Это значит, что с помощью такого итератора можно читать, но перемещаться только вперёд и только однократно:
- read
- increment (without multiple passes)
Этот первый проход происходит при вызове std::distance, но так как итератор передаётся по значению (т.е. копируется при вызове функции), то первое значение 0.1 кешируется в исходном итераторе и выводится на первой итерации цикла при *iter. Последующий инкремент ++iter приводит к тому, что итератор начинает указывать на end-of-stream, т.е. за конец последовательности (тот самый, на который указывает итератор, созданный конструктором по умолчанию std::istream_iterator<double>()), и следовательно вторая и последующие итерации цикла приводят к неопределённому поведению (UB). В вашем случае это вырождается в четырёхкратный вывод 0.1, но вполне может привести и к ошибке сегментирования.
Если нужно по итератору ввода посчитать количество элементов и не потерять возможность чтения данных, то считать элементы нужно одновременно с их чтением. Например:
#include <iostream>
#include <sstream>
#include <iterator>
int main()
{
std::istringstream str("0.1 0.2 0.3 0.4");
int n = 0;
std::istream_iterator<double> iter(str);
std::istream_iterator<double> eos;
while (iter != eos) {
std::cout << *iter << std::endl;
++iter;
++n;
}
std::cout << "size=" << n << "\n";
}