4

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 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This is very clear, thanks. I had no idea about this caching behavior. That's what was tripping me up, i.e. once the 1 is read, how could it be read again?
2

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

4 Comments

Thanks, the fix appears to work. But I'm not clear on the reason for the original code not working. Is the output unspecified?
@cigien the problem is not in the output. It is because you are reusing the same input iterator object for both copy_n calls. Tony’s answer is using 2 input objects, same as your “working” code. See my answer, I explain the problem in more detail.
@cigien: I thought that was very clear from "the first read for an istream_iterator should be performed by the constructor"...? Your code constructs the two istream_iterators before it's finished copying from the first iterator.
I was confused about the 1 appearing twice in the output. The first value seems to be cached, which I didn't realize from your answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.