C++ ranges中join视图的subrange编译失败问题咨询
C++ ranges中join视图迭代器无法使用std::advance和subrange的问题
问题代码
std::vector<std::vector<int>> input = {{0, 1, 2}, {3,4,5}, {6,7,8}}; auto range = input | std::views::transform([](auto &i) {return i | std::views::all;}) | std::views::join; for (auto i : range) { std::cout << i << std::endl; } // 至此一切正常,但接下来出现问题: auto l = range.begin(), r = range.begin(); std::advance(r, 2); // 必须通过<decltype>指定subrange类型,否则编译器报推导错误,但即使指定仍无法编译 auto subrange = std::ranges::subrange<decltype(l)>(std::move(l), std::move(r)); for (auto i : subrange) { std::cout << i << std::endl; } return 0;
编译器错误信息(Clang/GCC)
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/stl_iterator_base_funcs.h:223:49: 错误:'std::iterator_traits<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>>'中没有名为'difference_type'的类型 223 | typename iterator_traits<_InputIterator>::difference_type __d = __n; | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~ <source>:15:10: 注意:此处请求实例化函数模板特化'std::advance<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>, int>' 15 | std::advance(r, 2); | ^ <source>:18:34: 错误:类模板'subrange'的约束不满足[with _It = std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>, _Sent = std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>, _Kind = std::ranges::subrange_kind::unsized] 18 | auto subrange = std::ranges::subrange<decltype(l)>(std::move(l), std::move(r)); | ^~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/ranges_util.h:252:42: 注意:因为'sentinel_for<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>, std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>>'计算结果为false 252 | template<input_or_output_iterator _It, sentinel_for<_It> _Sent = _It, | ^ /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/iterator_concepts.h:641:10: 注意:因为'__detail::__weakly_eq_cmp_with<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>, std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>>'计算结果为false 641 | && __detail::__weakly_eq_cmp_with<_Sent, _Iter>; | ^ /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/concepts:296:10: 注意:因为'__t == __u'无效:二进制表达式的操作数无效('const remove_reference_t<_Iterator<false>>'(别名'const std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>')和'const remove_reference_t<_Iterator<false>>'(别名'const std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int>>>, (lambda at <source>:8:48)>>::_Iterator<false>')) ** 296 | { __t == __u } -> __boolean_testable; ** | ^
问题原因
迭代器概念降级为输入迭代器:
当在transform中返回i | std::views::all时,得到的是std::ranges::ref_view<std::vector<int>>类型。join_view处理这种视图序列时,其迭代器会被降级为输入迭代器——这是C++20标准中join_view的实现规则导致的,输入迭代器不支持以下操作:- 多步前进(
std::advance传入大于1的数值) - 迭代器之间的相等比较(只能和专属哨位比较)
- 没有
difference_type类型定义,这直接导致传统std::advance报错
- 多步前进(
subrange约束不满足:
std::ranges::subrange要求迭代器和哨位满足sentinel_for概念,而输入迭代器的迭代器本身不能作为自己的哨位(输入迭代器不支持==比较两个迭代器实例),因此构造subrange的约束不成立。
修复方案
去掉std::views::all,直接返回子容器的引用,让join_view生成随机访问迭代器(和原容器迭代器概念一致):
std::vector<std::vector<int>> input = {{0, 1, 2}, {3,4,5}, {6,7,8}}; // 直接返回子vector的引用,无需views::all auto range = input | std::views::transform([](auto &i) { return i; }) | std::views::join; for (auto i : range) { std::cout << i << std::endl; } auto l = range.begin(), r = range.begin(); std::advance(r, 2); // 现在支持多步前进 // 无需指定模板参数,自动推导合法 auto subrange = std::ranges::subrange(std::move(l), std::move(r)); for (auto i : subrange) { std::cout << i << std::endl; } return 0;
额外建议
对于C++ ranges迭代器,优先使用std::ranges::advance而非传统的std::advance,它会根据迭代器的概念自动选择最优实现(比如随机访问迭代器直接跳转,输入迭代器逐次递增),适配性更强。
内容的提问来源于stack exchange,提问作者user2717396
相关产品推荐
相关产品推荐

