I would expect the following to leave the buf_iter
pointing to the character n
characters after the point at which it started. Instead it is left pointing at the last character read. Why is this? i.e. if I do in_stream.tellg() before and after the copy_n, they differ not by n
but by (n-1)
. Had I read n
characters with in_stream.read
, then the position would be advanced by n
.
std::istreambuf_iterator<char> buf_iter(in_stream); std::copy_n(buf_iter, n, sym.begin());
I've looked at the implementation and it clearly does this on purpose, skipping the final increment.
Another post here mentions that incrementing the from iterator when it is hooked up to, say, cin
, will cause one too many reads since a read is done on operator++()
. That sounds like an issue with cin
- why isn't the read done on operator*()
?
Does the standard specify this anywhere? The docs I've seen don't mention what happens to the from iterator, and I've seen two different pages that give "possible correct implementations" that do each of the behaviours:
At cppreference we have:
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; }
while at cplusplus.com we have:
template<class InputIterator, class Size, class OutputIterator> OutputIterator copy_n (InputIterator first, Size n, OutputIterator result) { while (n>0) { *result = *first; ++result; ++first; --n; } return result; }
Both do n reads and result in the same contents in result. However, the first will only increment the "first" iterator n-1
times, and the second will increment it n
times.
What gives? How do I write portable code? I can use tellg
and then seekg
but then I might as well just do the loop by hand (ugh!).
Note that I'm not trying to read from the iterator after calling copy_n
, rather I want to read from the underlying stream after calling copy_n
, and the problem is that copy_n
is left pointing on byte short of where I had expected it to be. For now I'm going with the somewhat hideous but apparently portable:
auto pos = in_stream.tellg(); std::istreambuf_iterator<char> buf_iter(in_stream); std::copy_n(buf_iter, cl, sym.begin()); in_stream.seekg(pos + cl); uint64_t foo; in_stream.read(reinterpret_cast<char *>(&foo), 8);
BTW, in case its not clear, I'm trying to avoid copying the data into a buffer and then again into the string sym
.
@DaveS: Moving out of my specific problem, here is a simple program that does not output what I would expect due to the fact that the input iterator isn't incremented the final time:
#include <algorithm> #include <string> #include <iostream> #include <fstream> int main(int argc, const char * argv[]) { std::ifstream in("numbers.txt"); std::istreambuf_iterator<char> in_iter(in); std::ostreambuf_iterator<char> out_iter(std::cout); std::copy_n(in_iter, 3, out_iter); std::cout << std::endl; std::copy_n(in_iter, 3, out_iter); std::cout << std::endl; std::copy_n(in_iter, 3, out_iter); std::cout << std::endl; return 0; }
The input file is just "0123456789\n"
I'm getting:
012 234 456
Because of the side-effect of istreambuf_iterator::operator++()
, this would give a different result if copy_n
was implemented to increment the input iterator n
times.
@aschepler: Need to capture the local parameter, but I'm going with it:
std::generate_n(sym.begin(), cl, [&in_stream](){ return in_stream.get(); });
The reason why many std::copy_n
implementations increment n-1 times is due to the interactions with istream_iterator
, and how that is typically implemented.
For example, if you had an input file with integers in them
std::vector<int> buffer(2); std::istream_iterator<int> itr(stream); // Assume that stream is an ifstream of the file std::copy_n(itr, 2, buffer.begin());
Because istream_iterator
is specified to read on increment (and on either construction or first dereference), if std::copy_n
incremented the input iterator 2 times, you would actually read 3 values out of the file. The third value would simply be discarded when the local iterator inside copy_n
went out of scope.
istreambuf_iterator
doesn't have the same interactions, since it doesn't actually copy the value from the stream into a local copy, like most istream_iterators
do, but copy_n
still behaves this way.
Edit: Example of losing data if copy-N incremented N times (the cplusplus.com description, which doesn't seem to be correct). Note, this really only applies to istream_iterators
or other iterators that reads and remove their underlying data on increment.
std::istream_iterator<int> itr(stream); // Reads 1st value while(n > 0) // N = 2 loop start { *result = *first; ++result; ++first; // Reads 2nd value --n; // N: 1 // N = 1 loop start *result = *first; ++result; ++first; // Reads 3rd value --n; // N :0 // Loop exit } return result;
n3797 [algorithms.general]/12
In the description of the algorithms operators
+
and-
are used for some of the iterator categories for which they do not have to be defined. In these cases the semantics ofa+n
is the same as that ofX tmp = a; advance(tmp, n); return tmp;
and that of
b-a
is the same as ofreturn distance(a, b);
[alg.modifying.operations]
template<class InputIterator, class Size, class OutputIterator> OutputIterator copy_n(InputIterator first, Size n, OutputIterator result);
5Effects: For each non-negative integer i < n, performs
*(result + i) = *(first + i)
.6Returns:
result + n
.7Complexity: Exactly
n
assignments.
I'm not sure this is well-formed for InputIterators (no multipass), since it does not modify the original iterator, but always advances a copy of the original iterator. It doesn't seem to be efficient either.
[input.iterators]/Table 107 - Input iterator requirements (in addition to Iterator)
Expression:
++r
Return type:X&
pre:r
is dereferencable.
post:r
is dereferenceable orr
is past-the-end.
post: any copies of the previous value ofr
are no longer required either to be dereferenceable or to be in the domain of==
.
As far as I can see, the a
in
X tmp = a; advance(tmp, n); return tmp;
is therefore no longer required to be incrementable.
Related defect report: LWG 2173
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With