В следующей функции я пытаюсь увидеть, если строка 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 ()?
использование peek()
или же get()
чтобы проверить, что будет дальше в потоке:
return (i >> std::boolalpha >> t && i.peek() == EOF);
Ваша версия также не работает для целых чисел. Рассмотрим этот вход: 123 45
, Он будет читать 123 и сообщать об истине, даже если в потоке остались некоторые символы.
Во многих реализациях стандартной библиотеки eof
будет установлен только после того, как вы попробовали читать за пределами. Вы можете проверить это в своем коде, выполнив:
char _;
if (i && !(i >> _)) { // i is in a valid state, but
// reading a single extra char fails
Продолжая ответ Джрока, вы можете использовать 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
все еще проблема. Я разработал
версия, которая работала тогда, когда это было, и я просто продолжал
используй это.)
Я хотел бы предложить совершенно другой подход:
Возьмите вашу входную строку, разбейте ее на токены, а затем преобразуйте отдельные поля, используя 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
, Изменения в приведенном выше примере кода оставлены читателю в качестве упражнения 🙂