Любой способ объединить std :: istream_iterator и std :: for_each_n ()? - PullRequest
2 голосов
/ 22 июня 2019

Я создал следующую чистую тестовую программу для иллюстрации вопроса:

#include <iostream>
#include <iterator>
#include <algorithm>

int main()  // Test program. Not for real life solution. Just for demonstrating the question
{
    int odd{ 0 }, even{ 0 }, nValues{ 0 };

    std::cout << "Check and count odd and even numbers\n\nHow many numbers to check? Enter a value:  ";
    std::cin >> nValues;

    std::for_each_n(std::istream_iterator<int>(std::cin), nValues, [&](const int i) {if ((i % 2) == 0) ++even; else ++odd; });

    std::cout << "\nYou entered '" << even << "' even values and '" << odd << "' odd values\n\n";
    return 0;
}

Если я введу n, тогда будут прочитаны значения n+1.

cppreference объясняет о istream_iterator :

Фактическая операция чтения выполняется, когда итератор увеличивается , а не при разыменовании.Первый объект читается , когда итератор построен .Разыменование возвращает только копию последнего прочитанного объекта.

Если я хочу использовать std::istream_iterator вместе с std::for_each_n(), я предполагаю, что сталкиваюсь с проблемой реализации std::for_each_n(), которая может быть реализована (я знаю, что это только пример) в соответствии с cppreference подобно

template<class InputIt, class Size, class UnaryFunction>
InputIt for_each_n(InputIt first, Size n, UnaryFunction f)
{
    for (Size i = 0; i < n; ++first, (void) ++i) {
        f(*first);
    }
    return first;
}

Итак, чтобы проверить, был ли f() вызван n раз, он увеличивается i И Итератор ввода first.В моем демонстрационном примере это приводит к дополнительному чтению из std::cin.Таким образом, это никогда не может работать для моей тестовой программы.std::copy_n() похоже, реализовано по-другому.Следующее работает, когда я хочу прочитать n значения из std::cin в vector.Например,

std::vector<int> v(3);std::copy_n(std::istream_iterator<int>(std::cin), 3, v.begin());

Интересно, а почему такое поведение?Может ли комбинация std::istream_iterator и std::for_each_n() когда-либо работать?

Какое альтернативное решение использует библиотеку <algorithm>?

1 Ответ

2 голосов
/ 22 июня 2019

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.

...