我如何检查流提取已消耗所有输入

How do I check that stream extraction has consumed all input?

本文关键字:输入 提取 何检查 检查      更新时间:2023-10-16

在以下功能中,我尝试查看字符串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")评估为false,因为i.eof()在功能结束时为false。

这是正确的,即使该功能已经读取了整个字符串,因为它没有尝试读取 paste 字符串的末尾。(因此,显然,此功能适用于INT和双重,因为istringstream在阅读这些功能时读取了这些功能。)

所以,假设我确实应该检查(i and <input completely consumed>)

问:如何使用EOF()?

检查输入是否完全消耗了输入

使用peek()get()检查流中的下一步:

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

您的版本也不适用于整数。考虑此输入:123 45。它将读取123并报告True,即使流中剩下一些字符。

在标准库的许多实现中,eof才能在尝试阅读超过末尾后才设置。您可以通过执行:

在代码中验证这一点
char _;
if (i && !(i >> _)) { // i is in a valid state, but
                      // reading a single extra char fails

在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()

(我不知道Buggy 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(OK)。看到未设置failbit我感到非常惊讶...(平台信息:OS X 10.9,Apple Clang 6.0,C 11模式)。

当然,您现在可以说:"但是,等等,标准规定应该是这样的",您可能是对的,但是知道这是一个功能而不是错误,如果您想减轻痛苦在不编写数英里代码的情况下进行正确检查的正确错误。

otoh,如果您使用" Marius"的出色令牌功能并首先在Whitespace上拆分str,那么突然,一切都变得非常容易。这是Tokeniser的一个稍微修改的版本。我重新编写了它以返回一个字符串的向量;原始模板是将令牌放在带有元件的容器中,可转换为字符串。(对于那些需要这种通用方法的人,请咨询上面的原始链接。)

// 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或Dokenizer,但它们比Marius的Tokeniser慢(至少在我的环境中)。

更新:代替boost::lexical_cast<T>,您可以使用C 11" std::sto*"函数(例如stoi将字符串令牌转换为INT)。这些抛出两种例外:std::invalid_argument如果无法执行转换,并且如果无法表示转换值,则std::out_of_range。您可以单独捕获这些或他们的父级std::runtime_error。对上面的示例代码进行修改作为练习给读者: - )