如何在C++20中使用std::ranges::istream_view并执行后续操作?
解决std::ranges::istream_view结合std::views::split的编译错误
在网络及C++20相关书籍中,未找到任何展示std::ranges::istream_view结果结合后续操作的示例,尝试的每一项后续操作都会触发难以自行排查的编译错误。例如想要分割通过istream_view传入的惰性字符流,以下代码无法运行,但能表达需求:
#include <cstdlib> #include <ranges> #include <sstream> #include <fmt/core.h> auto main() -> int { std::istringstream data{"a,b"}; for (const auto& item: std::views::istream<std::string>(data) | std::views::split(",") ) { fmt::print("{}\n", item); } return EXIT_SUCCESS; }
期望这段代码第一行输出"a",第二行输出"b",但GCC 12.2抛出如下编译错误:
无法执行程序 编译器返回值: 1 编译器标准错误输出: <source>: 在函数'int main()'中: <source>:11:5: 错误: 无法匹配'operator|'(操作数类型为'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'和'std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*>') 10 | std::views::istream<std::string>(data) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> > 11 | | std::views::split(",") | ^ ~~~~~~~~~~~~~~~~~~~~~~ | | | std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*> 从<source>:2包含的文件: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:868:7: 注意: 候选: 'template<class _Lhs, class _Rhs> requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs)' 868 | operator|(_Lhs __lhs, _Rhs __rhs) | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:868:7: 注意: 模板参数推导/替换失败: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:868:7: 注意: 约束未满足 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:37包含的文件: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts: 在替换'template<class _Lhs, class _Rhs> requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs) [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >; _Rhs = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*>]'时: <source>:11:28: 要求从这里 /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:67:13: 要求满足'derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>' [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >] /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:67:28: 注意: 'std::ranges::views::__adaptor::_RangeAdaptorClosure'不是'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'的基类 67 | concept derived_from = __is_base_of(_Base, _Derived) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:859:7: 注意: 候选: 'template<class _Self, class _Range> requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&)' 859 | operator|(_Range&& __r, _Self&& __self) | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:859:7: 注意: 模板参数推导/替换失败: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:859:7: 注意: 约束未满足 /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges: 在替换'template<class _Self, class _Range> requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&) [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >]'时: <source>:11:28: 要求从这里 /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:831:13: 要求满足'__adaptor_invocable<_Self, _Range>' [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >] /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:832:9: 在要求中 [with _Args = {std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >}; _Adaptor = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Split, const char*>] /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:832:44: 注意: 所需表达式'declval<_Adaptor>()((declval<_Args>)()...)'无效 832 | = requires { std::declval<_Adaptor>()(declval<_Args>()...); }; | ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~ cc1plus: 注意: 设置'-fconcepts-diagnostics-depth='至少为2以获取更多细节 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/streambuf:41包含的文件: 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/streambuf_iterator.h:35, 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/iterator:66, 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:43: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:87:3: 注意: 候选: 'constexpr std::_Ios_Fmtflags std::operator|(_Ios_Fmtflags, _Ios_Fmtflags)' 87 | operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b) | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:87:27: 注意: 无法将参数1从'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'转换为'std::_Ios_Fmtflags' 87 | operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b) | ~~~~~~~~~~~~~~^~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:130:3: 注意: 候选: 'constexpr std::_Ios_Openmode std::operator|(_Ios_Openmode, _Ios_Openmode)' 130 | operator|(_Ios_Openmode __a, _Ios_Openmode __b) | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:130:27: 注意: 无法将参数1从'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'转换为'std::_Ios_Openmode' 130 | operator|(_Ios_Openmode __a, _Ios_Openmode __b) | ~~~~~~~~~~~~~~^~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:170:3: 注意: 候选: 'constexpr std::_Ios_Iostate std::operator|(_Ios_Iostate, _Ios_Iostate)' 170 | operator|(_Ios_Iostate __a, _Ios_Iostate __b) | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/ios_base.h:170:26: 注意: 无法将参数1从'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'转换为'std::_Ios_Iostate' 170 | operator|(_Ios_Iostate __a, _Ios_Iostate __b) | ~~~~~~~~~~~~~^~~ 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/span:42包含的文件: 从/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:45: /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/cstddef:132:3: 注意: 候选: 'constexpr std::byte std::operator|(byte, byte)' 132 | operator|(byte __l, byte __r) noexcept | ^~~~~~~~ /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/cstddef:132:18: 注意: 无法将参数1从'std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >'转换为'std::byte' 132 | operator|(byte __l, byte __r) noexcept | ~~~~~^~~
问题根源与修正方案
原代码存在两个核心问题:
std::views::istream<std::string>的行为不符合预期:该视图会按空白字符分割读取流中的字符串,因此会一次性读取整个"a,b",而不是逐个读取字符,导致后续的split操作无法按逗号分割单个字符流。- split的使用方式不匹配:
std::views::split(",")接受的是字符串字面量,但原视图的元素是std::string,split会尝试将每个字符串元素按逗号分割,同时split返回的子range无法直接被fmt格式化输出。
修正后的代码如下:
#include <cstdlib> #include <ranges> #include <sstream> #include <fmt/core.h> #include <string> auto main() -> int { std::istringstream data{"a,b"}; for (const auto& subrange : std::views::istream<char>(data) | std::views::split(',') ) { fmt::print("{}\n", std::string(subrange.begin(), subrange.end())); } return EXIT_SUCCESS; }
修正说明
- 将
std::views::istream<std::string>改为std::views::istream<char>,实现逐个读取流中的字符,确保split操作能处理完整的字符流。 - 分隔符从字符串字面量
","改为字符',',匹配当前视图的元素类型(char)。 - 遍历split返回的子range时,将其转换为
std::string后再打印,因为split返回的是子range对象,无法直接被fmt格式化。
内容的提问来源于stack exchange,提问作者Wouter Beek
相关产品推荐
相关产品推荐

