Having the following piece of code:
std::vector<int64> values;
std::copy(
std::istream_iterator<int64>(std::cin),
std::istream_iterator<int64>(),
std::back_inserter(values)
);
I'd like to make it read the input stream only until the end of line. How can I do that with std::istream_iterator?
You can't do it with std::istream_iterator.
But it is relatively easy to write an input iterator.
#include <iterator>
#include <iostream>
#include <sstream>
#include <vector>
#include <cctype>
template<typename T>
class istream_line_iterator: public std::iterator<std::input_iterator_tag, T>
{
std::istream* stream;
public:
// Creating from a stream or the end iterator.
istream_line_iterator(std::istream& s): stream(&s) {dropLeadingSpace();}
istream_line_iterator(): stream(nullptr) {}
// Copy
istream_line_iterator(istream_line_iterator const& copy): stream(copy.stream) {}
istream_line_iterator& operator=(istream_line_iterator const& copy) {stream = copy.stream;return *this;}
// The only valid comparison is against the end() iterator.
// All other iterator comparisons return false.
bool operator==(istream_line_iterator const& rhs) const {return stream == nullptr && rhs.stream == nullptr;}
bool operator!=(istream_line_iterator const& rhs) const {return !(*this == rhs);}
// Geting the value modifies the stream and returns the value.
// Note: Reading from the end() iterator is undefined behavior.
T operator*() const {T value;(*stream) >> value;return value;}
T* operator->() const; // Not sure I want to implement this.
// Input streams are funny.
// Does not matter if you do a pre or post increment. The underlying stream has changed.
// So the effect is the same.
istream_line_iterator& operator++() {dropLeadingSpace();return *this;}
istream_line_iterator& operator++(int) {dropLeadingSpace();return *this;}
private:
void dropLeadingSpace()
{
// Only called from constructor and ++ operator.
// Note calling this on end iterator is undefined behavior.
char c;
while((*stream) >> std::noskipws >> c) {
if (c == '\n') {
// End of line. So mark the iterator as reaching end.
stream = nullptr;
return;
}
if (!std::isspace(c)) {
// Found a non space character so put it back
stream->putback(c);
return;
}
}
// End of stream. Mark the iterator as reaching the end.
stream = nullptr;
}
};
int main()
{
std::stringstream s{"0 1 2 3 4 5 6 7 8 9 10\n11 12 13 14 15 16\n17 18 19"};
std::vector<int> d{istream_line_iterator<int>(s), istream_line_iterator<int>()};
for(auto v: d) {
std::cout << "V: " << v << "\n";
}
}
Running:
> g++ -std=c++17 main.cpp
> ./a.out
V: 0
V: 1
V: 2
V: 3
V: 4
V: 5
V: 6
V: 7
V: 8
V: 9
V: 10
If you want the functionality without adding a std::getline or std::stringstream, you can change the character classification of the stream so that a newline is not considered discardable whitespace. Here is a minimal example:
struct set_newline_as_ws : std::ctype<char> {
static const mask* make_table( std::ctype_base::mask m ) {
static std::vector<mask> v(classic_table(), classic_table() + table_size);
v['\n'] &= m;
return &v[0];
}
set_newline_as_ws( bool skip, std::size_t refs = 0 ) : ctype(make_table(skip ? ~space : space), false, refs) {}
};
std::istream& skipnewline( std::istream& is ) {
is.imbue(std::locale(is.getloc(), new std::ctype<char>));
return is;
}
std::istream& noskipnewline( std::istream& is ) {
is.imbue(std::locale(is.getloc(), new set_newline_as_ws(true)));
return is;
}
int main() {
std::vector<int64> values;
std::cin >> noskipnewline;
std::copy(
std::istream_iterator<int64>(std::cin),
std::istream_iterator<int64>(),
std::back_inserter(values)
);
std::cin >> skipnewline;
}
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