Как проверить, что извлечение потока израсходовало все входные данные?

В следующей функции я пытаюсь проверить, можно ли преобразовать строку 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()?


person BenRI    schedule 07.11.2012    source источник
comment
Не ответ, а просто примечание: рассмотрите возможность использования tmp-переменной типа T, поскольку вы переопределяете t даже в случае, если e.eof() ложно.   -  person Mene    schedule 07.11.2012
comment
Вам вернут EOF, если вы сделаете i.peek()   -  person lc.    schedule 07.11.2012


Ответы (4)


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

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

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

person jrok    schedule 07.11.2012

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

char _;
if (i && !(i >> _)) { // i is in a valid state, but
                      // reading a single extra char fails
person David Rodríguez - dribeas    schedule 07.11.2012

Расширяя ответ jrok, вы можете использовать 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 все еще проблемой. Я разработал ее версию, которая работала тогда, когда она была, и я просто продолжал ее использовать.)

person James Kanze    schedule 07.11.2012

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

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

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

person Laryx Decidua    schedule 10.04.2015