我试图从文件中获取所有单词(非空白字符序列)。在尝试这样做时,我不小心创建了一个无限循环,因为在文件末尾,没有提取更多单词,但流尚未耗尽。 注意我意识到,只需使用
std::views::istream<std::string>(file_stream)
就可以解决我的问题,但我对原因很感兴趣。
我的代码: 使用的编译器:Clang 18.1.0 带标志:-std=c++23 -stdlib=libc++
#include <cctype>
#include <format>
#include <iostream>
#include <ranges>
#include <sstream>
#include <string>
#include <vector>
constexpr auto is_white_space = [](char ch) constexpr {
return std::isspace(static_cast<unsigned char>(ch));
};
struct word_extractor {
std::string word;
friend std::istream &operator>>(std::istream &s, word_extractor &we) {
std::string buff = std::ranges::subrange(std::istreambuf_iterator{s},
std::istreambuf_iterator<char>{})
| std::views::drop_while(is_white_space)
| std::views::take_while([](auto x) {
return !is_white_space(x);
})
| std::ranges::to<std::string>();
//if (s.peek() == EOF) s.get(); // uncommenting this code makes it work
we.word = buff;
return s;
}
};
int main() {
std::istringstream file_stream("lorem ipsum dolor sit amet ");
auto parsed_words = std::views::istream<word_extractor>(file_stream)
| std::views::transform([](const word_extractor &word_extractor)
{
return word_extractor.word;
})
| std::ranges::to<std::vector<std::string>>();
for (auto w : parsed_words) {
std::cout << std::format("{{{}}}\n", w);
}
}
输出带有
if (s.peek() == EOF) s.get()
:
{Lorem}
{ipsum}
{dolor}
{sit}
{amet}
由于无限循环,没有
if (s.peek() == EOF) s.get()
就没有输出。
如果没有手动使用
EOF
的注释行,代码就会陷入无限循环,因为 std::views::istream<word_extractor>(file_stream)
会尝试永远调用 operator>>
。为什么流没有耗尽,因为我首先消耗所有空白字符,然后消耗所有非空白字符?
问题:有没有办法让这种提取与c++范围一起工作,或者是否需要(丑陋的)手动检查
EOF
?
streambuf 迭代器迭代streambuf(即使为了方便而从流构造),它们不会触及流的状态。如果没有注释掉的代码,
operator>>
中没有任何内容会设置failbit
来通知istream_view
提取失败并且应该停止迭代。
正如 T.C. 所指出的,streambuf 迭代器不会改变流的状态。要仍然使用范围管道,可以使用另一个 views::istream<char>
代替
ranges::subrange()
:
struct word_extractor {
std::string word;
friend std::istream &operator>>(std::istream &s, word_extractor &we) {
s >> std::noskipws; // don't skip ws
std::string buff = std::views::istream<char>(s) | // read single char from stream
std::views::drop_while(is_white_space) |
std::views::take_while([](auto x) { return !is_white_space(x); }) |
std::ranges::to<std::string>();
we.word = buff;
return s;
}
};
但请注意,仍然需要空格进行分隔,因此通过将 s >> std::noskipws
应用于
operator>>
,完成
char
是为了防止跳过空格。