Any way to combine std::istream_iterator and std::for_each_n()?

Viewed 412

I created the following pure test program to illustrate the question:

#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;
}

If I enter n, then n+1 values will be read.

cppreference explains about the istream_iterator:

The actual read operation is performed when the iterator is incremented, not when it is dereferenced. The first object is read when the iterator is constructed. Dereferencing only returns a copy of the most recently read object.

If I want to use the std::istream_iterator together with std::for_each_n(), I guess that I run into the implementation problem of std::for_each_n(), which maybe implemented (I know that it is only an example) according to cppreference like

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;
}

So, in order to check if f() has been called n times, it increments i AND the input iterator first. This leads in my demo example to an additional read from std::cin. So, it can never work for my test program. std::copy_n() seems to be implemented differently. The following works, when I want to read n values from std::cin into a vector. Like

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

So, I wonder, why the different behavior? Can the combination of std::istream_iterator and std::for_each_n() ever work?

What is the alternative solution using the <algorithm> library?

1 Answers

istream_iterator was originally designed for [begin, end) ranges, as in

std::for_each(std::istream_iterator<int>{std::cin}, std::istream_iterator<int>{}, /* do something */);

Given n values, the code first reads these n values, and then attempts to read once (!) and encounter EOF, causing the loop to terminate. In your case, this attempt causes an additional read. copy_n and for_each_n are not designed with iterators that cause side effect when incremented. In other words, istream_iterator was not designed for such situations.

The standard doesn't specify what should happen in this case. Let's take a look at the libc++ implementation of 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;
}

Note that it does ++__first before checking --n, __n > 0. Hence the extra increment operation.

Here's the libc++ implementation of copy_n: (this is the version for non-random-access iterators)

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;
}

Here, ++__first is executed after __n > 0 is checked. That explains the behavior you observed. Of course, a different implementation can behave differently.

The simplest way to work around this is to write a manual loop:

for (int i = 0; i < nValues; ++i) {
    int x;
    std::cin >> x;
    do_something(x);
}

I wouldn't say this is any inferior to using a standard algorithm.

Of course, you can also write your own istream iterator that reads on dereference, not incrementation, but then you have to make sure that successive dereference operations do not cause multiple read operations ([tab:inputiterator]). Maybe you can hold a can_read member that initializes to true, is set to false when the iterator is dereferenced, and is set to true when the iterator is incremented, so you read only if can_read is true.

Related