如何检查流提取是否消耗了所有输入?

时间:2012-11-07 16:56:36

标签: c++ boolean eof istringstream

在以下函数中,我通过查看是否可以读取类型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。

这是正确的,即使该函数已读取整个字符串,因为它还没有尝试读取过去字符串的结尾。 (所以,显然这个函数适用于int和double,因为istringstream在阅读这些函数时会读到结尾。)

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

问:如何使用eof()检查输入是否完全耗尽?

4 个答案:

答案 0 :(得分:8)

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

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

您的版本也不适用于整数。请考虑以下输入:123 45。它会读取123并报告为true,即使流中仍有一些字符。

答案 1 :(得分:3)

在标准库的许多实现中,eof只有在您尝试超越结束后才会设置。您可以通过执行以下操作在代码中验证:

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

答案 2 :(得分:3)

根据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()

(我不知道bug std::ws是否仍然存在问题。我开发了一个 它的版本在它发生的时候起作用了,而且我还在继续 使用它。)

答案 3 :(得分:2)

我想提供一种完全不同的方法: 获取输入字符串,自行标记,然后使用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(OK),j设置为9(??),x设置为6.0(= 0.6e + 01) ,y到550(好)。我很惊讶地看到failbit未被设置...(平台信息:OS X 10.9,Apple Clang ++ 6.0,C ++ 11模式)。

当然你现在可以说,&#34;但等等,标准声明它应该是这样的#34;,你可能是对的,但知道它是一个功能而不是一个bug并不能减少如果你想在不写入数英里代码的情况下进行适当的错误检查,那就太痛苦了。

OTOH,如果你使用&#34; Marius&#34;非常好tokeniser function并首先在空格上分割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 splittokenizer,但它们比Marius&#39; tokeniser(至少在我的环境中)。

更新:您可以使用C ++ 11&#34; boost::lexical_cast<T>&#34;而不是std::sto*。函数(例如stoi将字符串标记转换为int)。这会产生两种异常:如果无法执行转换,则std::invalid_argument;如果无法表示转换后的值,则std::out_of_range。 你可以单独捕获它们或它们的父std::runtime_error。对上述示例代码的修改留给读者练习: - )

相关问题