为什么 std::views::istream 没有用 take_while 耗尽

问题描述 投票:0回答:2

我试图从文件中获取所有单词(非空白字符序列)。在尝试这样做时,我不小心创建了一个无限循环,因为在文件末尾,没有提取更多单词,但流尚未耗尽。 注意我意识到,只需使用

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

c++ istream std-ranges c++23
2个回答
3
投票

streambuf 迭代器迭代streambuf(即使为了方便而从流构造),它们不会触及流的状态。如果没有注释掉的代码,

operator>>
中没有任何内容会设置
failbit
来通知
istream_view
提取失败并且应该停止迭代。


1
投票

正如 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
 是为了防止跳过空格。

© www.soinside.com 2019 - 2024. All rights reserved.