Как сделать так, чтобы std :: istream_iterator был доступен только для чтения до конца строки? - PullRequest
3 голосов
/ 01 июня 2019

Имея следующий кусок кода:

std::vector<int64> values;
std::copy(
  std::istream_iterator<int64>(std::cin),
  std::istream_iterator<int64>(),
  std::back_inserter(values)
);

Я бы хотел, чтобы он считывал входной поток только до конца строки. Как я могу сделать это с std::istream_iterator?

Ответы [ 4 ]

4 голосов
/ 01 июня 2019

Вы не можете сделать это с помощью std::istream_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";
    }
}

Запуск:

> 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
3 голосов
/ 01 июня 2019

Если вам нужна функциональность без добавления std::getline или std::stringstream, вы можете изменить классификацию символов потока так, чтобы символ новой строки не считался пропущенным пробелом. Вот минимальный пример:

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;
}
2 голосов
/ 01 июня 2019

Адаптировано из более раннего ответа на аналогичный вопрос ( здесь ):

#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;
}

Этот код должен занимать только одну строку и сохранять его в векторе v. Это благодаряиспользование функции std :: copy_n, которая принимает количество копируемых элементов в качестве входных данных.Здесь есть странность, о которой сообщается здесь .В зависимости от вашей платформы, одна или две строки будут прочитаны, хотя только первая из них будет сохранена в v.

При этом, если вы хотите сделать его отказоустойчивым, вы можете просто реализовать свою собственнуюфункция copy_n (...), например, так:

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);
}

Тогда вместо использования std :: copy_n (...) в вашем коде вы можете использовать own_copy_n (...).Таким образом, вы будете уверены, что вам нужно будет ввести только одну строку, и эта строка будет сохранена в вашем векторе v.

1 голос
/ 01 июня 2019

Невозможно изменить istream_iterator таким образом, потому что он не знает ни о новых строках, ни даже о символах. Он знает только о int64 (или о том, с каким типом вы его создали) и о том, закончился ли поток или произошел сбой (когда он вернет false на operator bool()).

Это означает, что наша точка настройки должна быть фактическим потоком.

Объект потока на самом деле является просто оболочкой вокруг std::basic_streambuf. Streambuf будет выплевывать символы, пока не найдет EOF. Вы можете просто настроить его так, чтобы он возвращал EOF, как только он обнаружил новую строку, тогда поток временно будет рассматривать это как конец потока.

Сделать это легко: объект streambuf доступен через функцию члена rdbuf(). Вы можете просто заменить буфер на свой, используя эту функцию. Чтобы продолжить чтение после новой строки, когда вы закончите, вы можете просто вернуть оригинал std::basic_streambuf обратно в поток.

#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, " "));
}

Смотри вживую!

...