У меня есть класс, который представляет последовательность символов, и я хотел бы реализовать 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 ++, так что, возможно, я должен рассматривать каждый действительный префикс в качестве допустимого ввода?
(Между прочим, это было бы довольно просто с явным циклом, но я пытаюсь избежать явных циклов в пользу алгоритмов.)
Ты не хочешь 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
тип.
В частности, как только мы достигнем места в «GATTACA FOO», копирование
остановка (ожидается)
Это предположение уже неверно. Вместо этого вы получите
std::istringstream istr("GATTACA FOO");
seq s;
assert(!(istr >> s) && s == "GATTACAFOO");
Копирование с использованием istream_iterator<char>
и стандарт copy
Алгоритм не может работать, потому что это всегда будет извлекать символ до конца потока.
Вам нужна копия, которая преждевременно завершается, если достигнуто конечное условие и конечное условие не должно извлекать несовпадающий символ (т. Е. С использованием in.peek()
или даже прямо глядя на стримбуф).
Для этого с помощью std::copy()
потребуется собственный потоковый итератор специального назначения (который сравнивается с конечным итератором, если условие завершения соответствует следующему символу. ИМХО, что создает больше неясности, чем явный цикл. YMMV