istream_iterator
изначально был разработан для [begin, end)
диапазонов, как в
std::for_each(std::istream_iterator<int>{std::cin}, std::istream_iterator<int>{}, /* do something */);
Учитывая n
значений, код сначала читает эти n
значения, а затем пытается прочитать один раз (!) И встретиться с EOF, в результате чего цикл завершается. В вашем случае эта попытка вызывает дополнительное чтение. copy_n
и for_each_n
не разработаны с итераторами, которые вызывают побочный эффект при увеличении. Другими словами, istream_iterator
не был предназначен для таких ситуаций.
Стандарт не определяет, что должно произойти в этом случае. Давайте посмотрим на реализацию libc ++ for_each_n
:
template <class _InputIterator, class _Size, class _Function>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
_InputIterator
for_each_n(_InputIterator __first, _Size __orig_n, _Function __f)
{
typedef decltype(__convert_to_integral(__orig_n)) _IntegralSize;
_IntegralSize __n = __orig_n;
while (__n > 0)
{
__f(*__first);
++__first;
--__n;
}
return __first;
}
Обратите внимание, что он делает ++__first
перед проверкой --n, __n > 0
. Отсюда и дополнительная операция приращения.
Вот реализация libc ++ copy_n
: (это версия для итераторов без произвольного доступа)
template<class _InputIterator, class _Size, class _OutputIterator>
inline _LIBCPP_INLINE_VISIBILITY
typename enable_if
<
__is_input_iterator<_InputIterator>::value &&
!__is_random_access_iterator<_InputIterator>::value,
_OutputIterator
>::type
copy_n(_InputIterator __first, _Size __orig_n, _OutputIterator __result)
{
typedef decltype(__convert_to_integral(__orig_n)) _IntegralSize;
_IntegralSize __n = __orig_n;
if (__n > 0)
{
*__result = *__first;
++__result;
for (--__n; __n > 0; --__n)
{
++__first;
*__result = *__first;
++__result;
}
}
return __result;
}
Здесь ++__first
выполняется после проверки __n > 0
. Это объясняет поведение, которое вы наблюдали. Конечно, другая реализация может вести себя по-разному.
Самый простой способ обойти это - написать ручной цикл:
for (int i = 0; i < nValues; ++i) {
int x;
std::cin >> x;
do_something(x);
}
Я бы не сказал, что это хуже, чем использование стандартного алгоритма.
Конечно, вы также можете написать свой собственный итератор istream, который читает по разыменованию, а не по приращению, но тогда вы должны убедиться, что последовательные операции разыменования не вызывают множественных операций чтения ( [tab: inputiterator] ). Возможно, вы можете удерживать элемент can_read
, который инициализируется на true
, установлен на false
, когда разыменовывается итератор, и на true
, когда увеличивается итератор, поэтому вы читаете только, если can_read
true
.