5

I have an istream and need to copy the content between two delimiters to a std::string. I can find the delimiters' streampos, but when trying to use istream_iterator<char> to iterate over the section of the stream, it does not work. Here's what I tried:

#include <iostream>
#include <sstream>
#include <iterator>


std::string copyToString( std::istream& is )
{
    is >> std::ws;

    auto someLength {10};

    std::istream_iterator<char> beg {is};

    is.seekg( someLength, std::ios::cur );

    //std::istream_iterator<char> end { is };
    std::istream_iterator<char> end { std::next(beg, someLength) };

    return std::string{ beg, end };
}




int main()
{
    std::stringstream ss;
    ss.str( "   { my string content  }" );

    std::cout << "\"" << copyToString( ss ) << "\"\n";

    return 0;
}

I expected the output to be 10 chars long, but it's just "{". If I uncomment the line std::istream_iterator<char> end { is }; and comment out the next one, the output is just "".

What am I missing? Can I not use iterators like that?

I am aware that I could create a char[] as a buffer, copy it to that, etc., but that seems much less straightforward.

RL-S
  • 734
  • 6
  • 21
  • fwiw, when I run your code I get a segfault https://wandbox.org/permlink/zhPnYCFQtzAn3MiK – 463035818_is_not_an_ai Oct 17 '19 at 09:14
  • 1
    `std::next(beg, someLength)` reads 10 characters from the stream. (However, I don't know how to do this correctly.) – user253751 Oct 17 '19 at 09:14
  • @immibis: I believe you are mistaken: http://www.cplusplus.com/reference/iterator/next/?kw=next – RL-S Oct 17 '19 at 09:16
  • I'm not quite sure about input streams, but aren't you invalidating the `beg` iterator once you do `seekg`? I thought the point of these streams is that they do not keep their elements after they are passed? – GeckoGeorge Oct 17 '19 at 09:23
  • You could instead do: `std::string result(someLength, ' '); is.read(&result[0], someLength); return result;` – Eljay Oct 17 '19 at 09:30
  • @GeckoGeorge Thought about that, but my guess was that it's ```istream_iterator``` that you can't have going backwards, not the ```istream``` itself (which can totally do that). – RL-S Oct 17 '19 at 09:30
  • @Eljay Apart from using resize(), that's precisely what I'm currently doing. – RL-S Oct 17 '19 at 09:32
  • @RL-S "ForwardIterator shall be at least a forward iterator." - istream_iterator is an input iterator, which is less powerful than a forward iterator, so according to that documentation, using std::next on it is invalid. – user253751 Oct 17 '19 at 11:08

1 Answers1

2

std::istream_iterator is an input iterator. Input iterators are single pass and are invalidated when copies thereof are incremented. When you do std::next(beg, someLength), you read someLength characters. Then beg is invalidated.

Moreover, std::istream_iterator is not intended to be counted. It is designed to be compared to a default constructed iterator, because of the way stream errors are handled. If you attempt count them, then they will sometimes read one more time, depending on how the algorithm is implemented.

If you want to read n characters from an input stream, just use read. If you want to skip spaces, then just write a loop. If you want to use std::istream_iterator in an unintended way, then your code will fail unpredictably.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • Thanks. I was not aware that that is an unintended use, as it seems quite basic to me, and an otherwise typical use of iterators. – RL-S Oct 17 '19 at 10:01
  • @RL-S Yeah, input iterators are quite restricted, to say the least. – L. F. Oct 17 '19 at 10:17
  • But I actually also tried the default constructor ```std::istream_iterator end;```. Why did that not work? Output was ```"{"``` IIRC. – RL-S Oct 17 '19 at 13:57