Как я могу проверить, что извлечение потока потребляет весь ввод?

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

template <class T>
bool can_be_converted_to(const std::string& s, T& t)
{
std::istringstream i(s);
i>>std::boolalpha;
i>>t;
if (i and i.eof())
return true;
else
return false;
}

Тем не мение, can_be_converted_to<bool>("true") оценивается как ложное, потому что i.eof() ложно в конце функции.

Это правильно, даже если функция прочитала всю строку, потому что она не пыталась прочитать прошлое конец строки. (Итак, по-видимому, эта функция работает для int и double, потому что istringstream читает за концом при чтении этих.)

Итак, предполагая, что я действительно проверяю (i and <input completely consumed>):

Q: Как я могу проверить, что ввод был полностью использован без использования eof ()?

5

Решение

использование peek() или же get() чтобы проверить, что будет дальше в потоке:

return (i >> std::boolalpha >> t && i.peek() == EOF);

Ваша версия также не работает для целых чисел. Рассмотрим этот вход: 123 45, Он будет читать 123 и сообщать об истине, даже если в потоке остались некоторые символы.

8

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

Во многих реализациях стандартной библиотеки eof будет установлен только после того, как вы попробовали читать за пределами. Вы можете проверить это в своем коде, выполнив:

char _;
if (i && !(i >> _)) { // i is in a valid state, but
// reading a single extra char fails
3

Продолжая ответ Джрока, вы можете использовать i.get() так же легко, как
i.peek()По крайней мере, в этом случае. (Я не знаю, есть ли причина
предпочесть одно другому.)

Кроме того, в соответствии с соглашением, что пустое пространство никогда
разделитель, вы можете извлечь его перед проверкой конца.
Что-то вроде:

return i >> std::ws && i.get() == std::istream::traits_type::eof();

Некоторые старые реализации std::ws глючили и ставили
поток в состоянии ошибки. В этом случае вам нужно будет пройти тест,
и сделать что-то вроде:

return !(i >> std::ws) || i.get() == std::istream::traits_type::eof();

Или просто прочитайте std::ws до состояния, и зависит исключительно от
i.get(),

(Я не знаю, если глючит std::ws все еще проблема. Я разработал
версия, которая работала тогда, когда это было, и я просто продолжал
используй это.)

3

Я хотел бы предложить совершенно другой подход:
Возьмите вашу входную строку, разбейте ее на токены, а затем преобразуйте отдельные поля, используя boost::lexical_cast<T>,

Причина: я потратил впустую день на анализ строки, содержащей 2 int и 2 двойных поля, разделенных пробелами. Делать следующее:

int i, j;
double x, y;
std::istringstream ins{str};

ins >> i >> j >> x >> y;
// how to check errors???...

анализирует правильный ввод, такой как

`"5 3 9.9e+01 5.5e+02"`

правильно, но не обнаруживает проблему с этим:

`"5 9.6e+01 5.5e+02"`

Что происходит то i будет установлен на 5 (ОК), j будет установлен на 9 (??), x до 6,0 (= 0,6e + 01), y до 550 (ок). Я был очень удивлен, увидев failbit не устанавливается … (информация о платформе: OS X 10.9, Apple Clang ++ 6.0, режим C ++ 11).

Конечно, теперь вы можете сказать: «Но подождите, Стандарт заявляет, что так и должно быть», и вы можете быть правы, но знание того, что это особенность, а не ошибка, не уменьшает боль, если вы хотите сделать правильную ошибку проверка без написания миль кода.

OTOH, если вы используете «Marius» отлично функция токенизатора и разделить str сначала на пустом месте, потом вдруг все становится очень легко. Вот слегка модифицированная версия токенизатора. Я переписал это, чтобы вернуть вектор строк; оригинал — это шаблон, который помещает токены в контейнер с элементами, преобразуемыми в строки. (Для тех, кому нужен такой общий подход, пожалуйста, обратитесь к оригинальной ссылке выше.)

// \param str: the input string to be tokenized
// \param delimiters: string of delimiter characters
// \param trimEmpty: if true then empty tokens will be trimmed
// \return a vector of strings containing the tokens
std::vector<std::string> tokenizer(
const std::string& str,
const std::string& delimiters = " ",
const bool trimEmpty = false
) {
std::vector<std::string> tokens;
std::string::size_type pos, lastPos = 0;
const char* strdata = str.data();
while(true) {
pos = str.find_first_of(delimiters, lastPos);
if(pos == std::string::npos) {
// no more delimiters
pos = str.length();
if(pos != lastPos || !trimEmpty) {
tokens.emplace_back(strdata + lastPos, pos - lastPos);
}
break;
} else {
if(pos != lastPos || !trimEmpty) {
tokens.emplace_back(strdata + lastPos, pos - lastPos);
}
}
lastPos = pos + 1;
}
return tokens;
}

а потом просто используйте это так (ParseError это какой-то объект исключения):

std::vector<std::string> tokens = tokenizer(str, " \t", true);
if (tokens.size() < 4)
throw ParseError{"Too few fields in " + str};

try {
unsigned int i{ boost::lexical_cast<unsigned int>(tokens[0]) },
j{ boost::lexical_cast<unsigned int>(tokens[1]) };
double x{ boost::lexical_cast<double>(tokens[2]) },
y{ boost::lexical_cast<double>(tokens[3]) };
// print or process i, j, x, y ...
} catch(const boost::bad_lexical_cast& error) {
throw ParseError{"Could not parse " + str};
}

Примечание: вы можете использовать Boost split или токенизатор если хотите, но они были медленнее, чем токенизатор Мариуса (по крайней мере, в моем окружении).

Обновление: вместо boost::lexical_cast<T> Вы можете использовать C ++ 11 «std::sto*«функции (например, stoi преобразовать строковый токен в int). Они генерируют два вида исключений: std::invalid_argument если преобразование не может быть выполнено и std::out_of_range если преобразованное значение не может быть представлено.
Вы можете поймать их отдельно или их родителей std::runtime_error, Изменения в приведенном выше примере кода оставлены читателю в качестве упражнения 🙂

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