Reputation: 60208
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.
Upvotes: 4
Views: 193
Reputation: 595712
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.
Upvotes: 3
Reputation: 106076
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);
Upvotes: 2