Реализовать чтение из потока через копию

У меня есть класс, который представляет последовательность символов, и я хотел бы реализовать operator >> для этого. Моя реализация в настоящее время выглядит так:

inline std::istream& operator >>(std::istream& in, seq& rhs) {
std::copy(
std::istream_iterator<char>(in),
std::istream_iterator<char>(),
std::back_inserter(rhs));
// `copy` doesn't know when to stop reading so it always also sets `fail`
// along with `eof`, even if reading succeeded. On the other hand, when
// reading actually failed, `eof` is not going to be set.
if (in.fail() and in.eof())
in.clear(std::ios_base::eofbit);
return in;
}

Однако следующее предсказуемо не работает:

std::istringstream istr("GATTACA FOO");
seq s;
assert((istr >> s) and s == "GATTACA");

В частности, как только мы достигнем места вGATTACA FOO”, Копирование останавливается (ожидается) и устанавливает бит сбоя на istream (также ожидается). Тем не менее, операция чтения действительно успешно, насколько seq обеспокоен.

Могу ли я моделировать это вообще, используя std::copy? Я также думал об использовании istreambuf_iterator вместо этого, но это на самом деле не решает эту конкретную проблему.

Более того, операция чтения на входеGATTACAFOO» должен потерпеть неудачу так как этот вход не представляет действительную последовательность ДНК (что представляет мой класс). С другой стороны, читая int от входа 42foo на самом деле преуспевает в C ++, так что, возможно, я должен рассматривать каждый действительный префикс в качестве допустимого ввода?

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

1

Решение

Ты не хочешь clear(eofbit) поскольку failbit должен оставаться установленным, если чтение не удалось из-за достижения EOF. В противном случае, если вы просто уйдете eofbit установить без failbit затем цикл, такой как while (in >> s) попытается еще раз прочитать после достижения EOF, а затем тот читать установит failbit снова. За исключением случаев, если он использовал ваш operator>> это очистило бы это, и попытайтесь читать снова. И опять. И опять. Правильное поведение для потока должно установить failbit Если чтение не удалось из-за EOF, просто оставьте его установленным.

Чтобы сделать это с помощью итераторов и алгоритма, вам нужно что-то вроде

copy_while(InputIter, InputIter, OutputIter, Pred);

который будет копировать входную последовательность только тогда, когда предикат был истинным, но его нет в стандартной библиотеке. Вы, конечно, могли бы написать один, хотя.

template<typename InputIter, typename OutputIter, typename Pred>
OutputIter
copy_while(InputIter begin, InputIter end, OutputIter result, Pred pred)
{
while (begin != end)
{
typename std::iterator_traits<InputIter>::value_type value = *begin;
if (!pred(value))
break;
*result = value;
result++;
begin++;
}
return result;
}

Теперь вы можете использовать это так:

inline bool
is_valid_seq_char(char c)
{ return std::string("ACGT").find(c) != std::string::npos; }

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
copy_while(
std::istream_iterator<char>(in),
std::istream_iterator<char>(),
std::back_inserter(rhs),
&is_valid_seq_char);
return in;
}

int main()
{
std::istringstream istr("GATTACA FOO");
seq s;
assert((istr >> s) and s == "GATTACA");
}

Это работает, но проблема в том, что istream_iterator использования operator>> читать символы, поэтому он пропускает пробелы. Это означает, что пространство после "GATTACA" потребляется алгоритмом и отбрасывается, так что добавив это в конец main потерпит неудачу

assert(istr.get() == ' ');

Чтобы решить эту проблему istreambuf_iterator который не пропускает пробелы:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
copy_while(
std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>(),
std::back_inserter(rhs),
&is_valid_seq_char);
return in;
}

Чтобы завершить это, вы, вероятно, хотите указать, что не удалось извлечь seq если нет символов где извлечено:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
copy_while( std::istreambuf_iterator<char>(in), {},
std::back_inserter(rhs), &is_valid_seq_char);
if (seq.empty())
in.setstate(std::ios::failbit);  // no seq in stream
return in;
}

Эта финальная версия также использует один из моих любимых трюков C ++ 11, чтобы немного упростить его, используя {} для конечного итератора. Тип второго аргумента для copy_while должен совпадать с типом первого аргумента, который выводится как std::istreambuf_iterator<char>, Итак {} просто инициализирует значение другого итератора того же типа.

Редактировать: Если вы хотите поближе к std::string извлечение, то вы можете сделать это тоже:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
std::istream::sentry s(in);
if (s)
{
copy_while( std::istreambuf_iterator<char>(in), {},
std::back_inserter(rhs), &is_valid_seq_char);
int eof = std::char_traits<char>::eof();
if (std::char_traits<char>::eq_int_type(in.rdbuf()->sgetc(), eof))
in.setstate(std::ios::eofbit);
}
if (rhs.empty())
in.setstate(std::ios::failbit);
return in;
}

Часовой будет пропускать начальные пробелы, и если вы достигнете конца ввода, он установит eofbit, Другое изменение, которое, вероятно, следует сделать, это очистить seq прежде чем что-то толкать в него, например начать с rhs.clear() или эквивалент для вашего seq тип.

5

Другие решения

В частности, как только мы достигнем места в «GATTACA FOO», копирование
остановка (ожидается)

Это предположение уже неверно. Вместо этого вы получите

std::istringstream istr("GATTACA FOO");
seq s;
assert(!(istr >> s) && s == "GATTACAFOO");

Копирование с использованием istream_iterator<char> и стандарт copyАлгоритм не может работать, потому что это всегда будет извлекать символ до конца потока.

Вам нужна копия, которая преждевременно завершается, если достигнуто конечное условие и конечное условие не должно извлекать несовпадающий символ (т. Е. С использованием in.peek() или даже прямо глядя на стримбуф).

Для этого с помощью std::copy() потребуется собственный потоковый итератор специального назначения (который сравнивается с конечным итератором, если условие завершения соответствует следующему символу. ИМХО, что создает больше неясности, чем явный цикл. YMMV

2

По вопросам рекламы [email protected]