Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::copy_n not increment the Input iterator n times?

Tags:

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(); }); 
like image 938
sfjac Avatar asked Apr 25 '14 19:04

sfjac


2 Answers

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; 
like image 166
Dave S Avatar answered Sep 22 '22 10:09

Dave S


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 of a+n is the same as that of

X tmp = a; advance(tmp, n); return tmp; 

and that of b-a is the same as of

return 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 or r is past-the-end.
post: any copies of the previous value of r 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

like image 43
3 revs Avatar answered Sep 19 '22 10:09

3 revs