A usual STL construct is:
vector col;
copy(istream_iterator(cin), istream_iterator(),
back_inserter(col));
Part of the answer below is quoted from C++ standard library: A tutorial and reference by Nicolai M. Josuttis with a little tweaks.
The expression
istream_iterator<string>(cin)
creates a string iterator that reads from the standard input stream
cin
. The template argumentstring
specifies that the stream iterator reads elements of this type. These elements are read with the usual input operator >>. Thus, each time the algorithm wants to process the next element, the istream iterator transforms that desire into a call ofcin >> string
The input operator for strings usually reads one word separated by whitespaces.
The expression
istream_iterator<string>()
calls the default constructor of the istream iterators that creates a so-called end-of-stream iterator. It represents a stream from which you can no longer read. The end-of-string iterator is used as the
end of the range
, so the algorithmcopy
reads all strings fromcin
until it can no longer read any more.
The last one:
back_inserter(col))
according to back_inserter documentation:
A std::back_insert_iterator which can be used to add elements to the end of the container c
It will add all read in strings into col
.
You can find information about std::istream_iterator and std::back_inserter for details.
First, note that in this case, there's no real need to use std::copy
at all. You can just initialize the vector directly from the iterators:
vector<string> col((istream_iterator<string>(cin)),
istream_iterator<string>());
This probably doesn't make the code a whole lot easier to understand though.
As far as how the code works, it's probably a little more straighforward than you think. An istream_iterator looks vaguely like this:
template <class T>
class istream_iterator {
std::istream *is;
T data;
public:
istream_iterator(std::istream &is) : is(&is) { ++(*this); }
istream_iterator() : is(nullptr) {}
T operator++() { (*is) >> data; return *this; }
T operator++(int) { (*is) >> data; return *this; }
T const &operator*() { return data; }
bool operator !=(istream_iterator &end) { return (*is).good(); }
bool operator ==(istream_iterator &end) { return !(*is).good(); }
};
Obviously there's more more I'm skipping over, but that's most of what we care about here. So, what happens is that when you create the iterator, it reads (or attempts to) an item from the stream into the variable I've called data
. When you dereference the iterator, it returns data
. When you increment the iterator, it reads (or attempts to) the next item from the file. Despite being written as if they compare one iterator to another, operator==
and operator!=
really just check for the end of the file1.
That's then used by std::copy
, which (again simplified) looks vaguely like this:
template <class InIt, class OutIt>
void std::copy(InIt b, InIt e, OutIt d) {
while (b != e) {
*d = *b;
++b;
++d;
}
}
So, this reads and item from the input iterator, writes that item to the output iterator, and repeats until the iterator for the current position compares equal to the iterator for the end of the input (which will happen when you reach the end of the file). Note that unlike other iterators, the only "end" position you're allowed to use with an istream iterator is the end of the file.