Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about usage of 'std::istreambuf_iterator'

Tags:

c++

Following is an example from cppreference.com,

The Code is:
#include <vector>
#include <sstream>
#include <iostream>
#include <iterator>

int main()
{
// typical use case: an input stream represented as a pair of iterators
std::istringstream in("Hello, world");
std::vector<char> v( (std::istreambuf_iterator<char>(in)),
                      std::istreambuf_iterator<char>() );
std::cout << "v has " << v.size() << " bytes. ";
v.push_back('\0');
std::cout << "it holds \"" << &v[0] << "\"\n";


// demonstration of the single-pass nature
std::istringstream s("abc");
std::istreambuf_iterator<char> i1(s), i2(s);
std::cout << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';
++i1;
std::cout << "after incrementing i1, but not i2\n"
          << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';
++i2; // this makes the apparent value of *i2 to jump from 'a' to 'c'
std::cout << "after incrementing i2, but not i1\n"
          << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';

}

I have two questions:

  1. Can someone elaborate on the code std::vector<char> v( (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() );, I do not quite understand what it's doing..and why can we print the string "Hello, world" just by using cout<<&v[0]
  2. Why does the apprent value of *i2 jump for "a" to "c"? can someone explain the mechanisms of it?

Thanks so much!

like image 450
Brain Zhang Avatar asked Dec 10 '14 17:12

Brain Zhang


2 Answers

Can someone elaborate on the code ...

std::vector<T> has a constructor that takes two iterators on <T> - one for the beginning and one for the end of the range.

This constructor makes an input stream iterator from an input stream in:

std::istreambuf_iterator<char>(in)

You can access its elements going forward until you reach the end of the stream. Once you reach the end of stream, the iterator becomes equivalent to an iterator created using the default constructor:

std::istreambuf_iterator<char>()

Therefore, passing this pair of iterators constructs a vector<T> from the data read from an input stream. The entire stream will be consumed.

Why does the apprent value of *i2 jump for "a" to "c"?

Both iterators are reading from the same stream. When you increment the first iterator, it consumes 'b' from the underlying stream. In the meantime, i2 refers to the first character of the stream, which it got without advancing at the time when it has been constructed.

Once you increment i2, it asks the stream for the next character. Character 'b' has been consumed already, so the next character is 'c'.

Finally, the code pulls a little trick that you may have overlooked: it pushes a null terminator into the vector<char> in order to be able to print the vector using the const char* overload of operator <<(...).

like image 185
Sergey Kalinichenko Avatar answered Oct 17 '22 11:10

Sergey Kalinichenko


A default-constructed istreambuf_iterator is basically an end-of-file iterator--that is, another iterator will compare equal to it only when it reaches the end of the file.

Therefore, the code:

std::vector<char> v( (std::istreambuf_iterator<char>(in)),
                      std::istreambuf_iterator<char>() );

...reads chars from in until the first iterator has been incremented to equal the second iterator, which happens when (and only when) the first iterator reaches the end of the file (stringstream, in this case). In short, it copies the entire contents of the file into the vector.

The printing "hello world" part is a bit simpler: ostream has an operator<< overload for char *, which assumes that the char * points at a C-style string, so it should print out the entire string that's pointed at. Since they've done a push_back to add a '\0' to the string, that makes it a C-style string.

The second part is demonstrating that even though you have two iterators into the stream, you still have only one stream, and one read position in that stream. At the same time, each iterator holds a copy of the most recent item it read from the stream.

Therefore, anytime you increment either iterator (or any iterator into the same stream) it increments the current read position. So, you start with i1 and i2 both pointing to the beginning of the stream. Then you increment i1. That increments the read position, and reads b into i1, so when you dereference i1, that's what you'll get. When you increment i2, that moves the read position again, and reads c into i2, so dereferencing i2 will give c.

The use of two (or more) iterators doesn't change the nature of the stream--every time you increment any iterator into the same stream, that reads the next item from that stream--and the "next item" is always determined by the stream itself, based on its one read position.

like image 8
Jerry Coffin Avatar answered Oct 17 '22 09:10

Jerry Coffin