如何实现 operator>>(std::istream&, std::array<char, N>&)?

How to implement operator>>(std::istream&, std::array<char, N>&)?

提问人:John Zwinck 提问时间:1/16/2017 更新时间:1/16/2017 访问量:697

问:

我想要一个通用的、完全兼容的 istream“提取运算符”,如下所示:

template <typename CharT, size_t N, class Traits>
std::basic_istream<CharT, Traits>& operator>>(
    std::basic_istream<CharT, Traits>& in,
    std::array<CharT, N>& out)
{
    std::basic_string<CharT, Traits> buf; // this is not great
    in >> buf;
    if (buf.size() >= N) {
        in.setstate(std::ios::failbit); // is this the right thing to do?
        out[0] = 0;
    } else {
        std::copy(buf.begin(), buf.end(), out.data());
        out[buf.size()] = 0;
    }

    return in;
}

但这会不必要地分配和复制内存。我想避免这种情况。但我也想保留完整的功能,包括适当的空格和对 的支持等。 如果更容易的话,使用 Boost 就可以了。std::noskipws

C++ 运算符重载 IOstream iStream

评论

1赞 Arunmu 1/16/2017
我敢肯定,您一定已经发现了一次只读取一个字符然后保持计数以检查溢出的方法有问题。
0赞 Holt 1/16/2017
@JohnZwinck我认为您应该通过一些边缘情况示例来指定您想要的确切行为,例如 , ." a b c"" abc "
0赞 John Zwinck 1/16/2017
@Holt:我想要完全相同的行为,就像使用而不是 一样,如果字符串不适合 N (包括 null 终止符),则增加设置失败。其余行为和所有边缘情况的处理方式应与常规 C++ 字符串相同。std::stringstd::array<char, N>

答:

0赞 Richard Hodges 1/16/2017 #1

像这样的东西:

#include <array>
#include <istream>
#include <sstream>

template <typename CharT, size_t N, class Traits>
std::basic_istream<CharT, Traits>& operator>>(
    std::basic_istream<CharT, Traits>& in,
    std::array<CharT, N>& out)
{
    auto first = out.begin();
    auto last = out.end();
    while (first != last and in)
    {
        in >> *first++;
    }
    return in;
}

int main()
{
    std::istringstream ss { "a cd" };
    ss >> std::noskipws;

    std::array<char, 4> a;
    ss >> a;

    for (char c : a) {
        std::cout << "[" << c << "]\n";
    }
}

结果:

[a]
[ ]
[c]
[d]

评论

0赞 John Zwinck 1/16/2017
谢谢你。你能让它做 null 终止,包括在输入流为空或失败时将零作为第一个字符吗?
0赞 John Zwinck 1/16/2017
这里有一个重要的错误:当它遇到空格(没有 noskipws)时,它不会停止。常规运算符这样做,这也应该如此。std::string
0赞 Richard Hodges 1/16/2017
@JohnZwinck当你说“bug”时,这意味着当遇到这些情况时,你已经想到了一些定义的行为。在编写此代码时,我没有意识到这些要求(您看到用户和实现者之间长期存在的问题了吗?如果你正式陈述你的需求,我可以建立一个测试套件来检查它们,然后相应地迭代这些代码,直到我们通过为止。这一切都始于了解并准确说明您想要的行为。
2赞 M.M 1/16/2017 #2

不是一个完整的答案,但对于评论来说太大了:

使唯一操作数处于状态的运算符过载将导致麻烦。这是因为两阶段查找,任何其他自定义都会隐藏您的一个,请参阅示例namespace stdoperator>>

通常,通过在与操作数之一相同的命名空间中定义重载运算符来避免此问题,以便与参数相关的查找始终找到运算符。(ADL 仍会搜索封闭的命名空间,即使较近的命名空间具有该名称)。

但是,该解决方案不适用于您,因为将自己的函数添加到命名空间 std 是未定义的行为

我不确定这个问题的首选解决方法是什么。如果您希望通过使用 on 模板参数的通用模板代码找到此函数,则需要显式地使重载可见,并在内部调用相同的函数。或者更整洁地说,你把你的东西放在它自己的命名空间中。>>using ::operator>>;using mystuff::operator>>

评论

0赞 John Zwinck 1/16/2017
哎哟!好吧,在我的特殊情况下,我愿意并且能够在我自己的命名空间中使用 typedef 或包装类。所以我想我可以解决这个问题,但这是一个昏昏欲睡的问题,所以感谢您指出它!std::array
1赞 Holt 1/16/2017 #3

以下操作应有效:

template <typename CharT, size_t N, class Traits>
std::basic_istream<CharT, Traits>& operator>>(
    std::basic_istream<CharT, Traits>& in,
    std::array<CharT, N>& out)
{
    in >> std::setw(N) >> out.data();
    if (!in.eof() && !std::isspace((CharT)in.peek(), in.getloc())) {
        out[0] = 0;
        in.setstate(in.rdstate() | std::ios::failbit);
    }
    return in;
}

据我所知,它的行为就像您的版本一样,只是它永远不会读取超过字符。std::basic_stringN - 1