Why is reading from an istream_iterator assigned to a variable not working?

181 views Asked by At

I wrote the following program that reads in 3 numbers from std::cin, and outputs them to std::cout, and does this twice:

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

int main()
{
    std::copy_n(std::istream_iterator<int>(std::cin), 
                3, 
                std::ostream_iterator<int>(std::cout, " "));
    
    std::copy_n(std::istream_iterator<int>(std::cin), 
                3, 
                std::ostream_iterator<int>(std::cout, " "));              
}

For an input of 1 2 3 4 5 6, the program prints the expected 1 2 3 4 5 6.


As I found the code a bit verbose, I tried to store the iterators in variables:

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

int main()
{
    auto ins = std::istream_iterator<int>(std::cin);
    auto outs = std::ostream_iterator<int>(std::cout, " ");
               
    std::copy_n(ins, 3, outs);
    std::copy_n(ins, 3, outs);
}

But now for the input 1 2 3 4 5 6, the program prints 1 2 3 1 4 5.

I don't understand the output. What's going on here, and what am I doing wrong?

Also, note that it only matters when I use ins. Whether I use outs or not doesn't affect the output.

2

There are 2 answers

4
Tony Delroy On

If you look at Defect Report P0738R2 you'll see the first read for an istream_iterator should be performed by the constructor, so it's reading 1 at the line auto ins = ....

copy_n takes its arguments by value, so the first invocation doesn't move main()s ins variable past the 1 it has already read, and that's provided again to the second invocation of copy_n.

If you want concision, you could do something like:

auto mkins() = [] { return std::istream_iterator<int>(std::cin); }

std::copy_n(mkins(), 3, outs);
std::copy_n(mkins(), 3, outs);
1
Remy Lebeau On

Per this reference:

std::istream_iterator is a single-pass input iterator that reads successive objects of type T from the std::basic_istream object for which it was constructed, by calling the appropriate operator>>. 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.

So, when you first create the ins variable, it reads 1 from cin right away and caches it.

If you look at the declaration of copy_n(), the input iterator is passed by value, which means it is copied.

template< class InputIt, class Size, class OutputIt >
OutputIt copy_n( InputIt first, Size count, OutputIt result );

Let’s assume, for argument’s sake, that the following implementation of copy_n() is being used (check your compiler for actual implementation):

template< class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result)
{
    if (count > 0) {
        *result++ = *first;
        for (Size i = 1; i < count; ++i) {
            *result++ = *++first;
        }
    }
    return result;
}

When you pass ins to copy_n(), that cached 1 is copied into the first parameter. When copy_n() dereferences first, it receives the cached 1 and outputs to result. Then copy_n() increments first which reads 2 from cin and caches it, then dereferences first to receive 2 and output it, then increments first which reads 3 from cin and caches it, then dereferences first to receive 3 and output it, then exits.

When you pass ins to copy_n() again, the original cached 1 is still in ins and is copied into the first parameter. When copy_n() dereferences first, it receives the cached 1 and outputs to result. Then copy_n() increments first which reads 4 from cin and caches it, then dereferences first to receive 4 and output it, then increments first which reads 5 from cin and caches it, then dereferences first to receive 5 and output it, then exits.