3

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?

Patryk
  • 22,602
  • 44
  • 128
  • 244
  • 1
    The cononical way would be to read the line using `std::getline`, put it in to a `std::istringstream` and then copy into a vector the way you're doing here. – David G Jun 01 '19 at 15:46
  • Yeah, I do know that's possible but then we get more copying by coping into `std::string` and then reiterating over it again to push_back into the vector – Patryk Jun 01 '19 at 15:48
  • `istream_iterator` will always iterate to the end of stream. There's nothing you can do about that, except creating a new stream with the end defined exactly where you want it to be. The easiest way to do that is by using a `stringstream`. – Not a real meerkat Jun 01 '19 at 15:50

4 Answers4

6

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
Martin York
  • 257,169
  • 86
  • 333
  • 562
4

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;
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
David G
  • 94,763
  • 41
  • 167
  • 253
  • That's a clever usage of the locale. – Martin York Jun 01 '19 at 16:46
  • won't this set `failbit` on the stream, though? You probably should call `cin.clear()` after reading a line. Also, [What do I do if I want to get a second line?](http://coliru.stacked-crooked.com/a/78f8af7e803fd5ea) – Not a real meerkat Jun 01 '19 at 16:54
  • @CássioRenan Then you will have to do two things: Call `clear()` and remove the newline character. – David G Jun 01 '19 at 17:02
2

Adapted from an earlier answer to a similar question (here):

#include <vector>
#include <algorithm>
#include <string>
#include <iterator>

namespace detail 
{
    class Line : public std::string 
    { 
        friend std::istream & operator>>(std::istream & is, Line & line)
        {   
            return std::getline(is, line);
        }
    };
}

template<class OutIt> 
void read_lines(std::istream& is, OutIt dest)
{
    typedef std::istream_iterator<detail::Line> InIt;
    std::copy_n(InIt(is), 1, dest);
}

int main()
{
    std::vector<std::string> v;
    read_lines(std::cin, std::back_inserter(v));

    return 0;
}

This code should take only one line and saves it into the vector v. This is thanks to the use of std::copy_n function which takes the number of elements-to-be-copied as input. There is a quirk, though, reported here. Depending on your platform, one or two lines will be read even though only the first one will be saved into v.

That being said, if you want to make it fail-safe, you can just implement your own copy_n(...) function, like so:

template<class InIt, class Range, class OutIt>
OutIt own_copy_n(InIt first, Range count, OutIt dest)
{
  *dest = *first;
  while (0 < --count)
      *++dest = *++first;
  return (++dest);
}

Then instead of using std::copy_n(...) in you code, you can use own_copy_n(...). This way you'll be sure you'll have to input one line only, and that that line will be saved into your vector v.

borizzzzz
  • 620
  • 1
  • 6
  • 17
1

It is impossible to change istream_iterator in this way, because it has no knowledge of newlines, or even of characters, for that matter. It only knows about int64(Or whatever type you instantiated it with) and if the stream ended or failed (When it then will return false on operator bool()).

This means that our point of customization must be the actual stream.

The stream object is actually just a wrapper around a std::basic_streambuf. The streambuf will spit out characters until it finds an EOF. You can simply adapt it to return an EOF once it finds a newline, then the stream will temporarily treat that as the end of stream.

Doing this is easy: The streambuf object is accessible through the member funtion rdbuf(). You can just replace the buffer with your custom one using this function. To have it continue reading after the newline when you're done, you can just return the original std::basic_streambuf back to the stream.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <streambuf>
#include <vector>

// This is our custom std::basic_streambuf object.
// We chose the underflow function as our point of customization, because
// it is there that the action is happening: it is this function that
// that is responsible for reading more data from the stream.
class newline_buf : public std::streambuf {
    std::streambuf* src;
    char ch; // single-byte buffer
protected:
    int underflow() {
        if( (ch= src->sbumpc()) == '\n') {
            return traits_type::eof(); // return EOF on new line.
        }
        setg(&ch, &ch, &ch+1); // make one read position available
        return ch; // may also return EOF by the regular means
    }
public:
    newline_buf(std::streambuf* buf) : src(buf) {
        setg(&ch, &ch+1, &ch+1); // buffer is initially full
    }
};

int main() {
    // testing with a stringstream to make things easier to reproduce.
    // Should work fine with any type of input stream.
    std::istringstream iss(R"(12345 12345 12345 
    67890 67890 67890)");

    // We store the original rdbuf so we can recover it later.
    auto original_rdbuf = iss.rdbuf();
    newline_buf buf(original_rdbuf);
    iss.basic_ios::rdbuf(&buf);

    // Do the copy and then recover the original rdbuf
    std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::ostream_iterator<int>(std::cout, " "));
    iss.basic_ios::rdbuf(original_rdbuf);

    // You can try doing a new copy, just to convince yourself that the stream is still in a valid state.
    //std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::ostream_iterator<int>(std::cout, " "));
}

See it live!

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55