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 шт):

Автор решения: Andrej Levkovitch

Все давольно просто: давайте немного модифицируем ваш пример

#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";
}
→ Ссылка