为何连接向量视图后无法得到合法的const input_range?
问题:笛卡尔积transform后join生成的range无法作为const input_range使用
尝试对笛卡尔积执行transform操作生成网格边集,再用std::views::join展平结果,但该range无法作为const input_range传递给函数,取消注释代码中的static_assert后编译报错。
代码示例
#include <ranges> #include <vector> #include <iostream> #include <tuple> #include <iterator> int main() { auto indicies = std::views::iota(0, 3); auto coords = std::views::cartesian_product(indicies, indicies); auto edges = coords | std::views::transform([](const auto& coord) { using Coord = std::pair<std::size_t, std::size_t>; using Edge = std::pair<Coord, Coord>; std::vector<Edge> ret; const auto& [x, y] = coord; auto curr = Coord{x, y}; if (x + 1 < 3) { auto dest = Coord{x + 1, y}; ret.emplace_back(curr, dest); } if (y + 1 < 3) { auto dest = Coord{x, y + 1}; ret.emplace_back(curr, dest); } return ret; }) | std::views::join; std::cout << edges.cbegin()->first.first << std::endl; // static_assert(std::ranges::input_range<const decltype(edges)>);// <- 取消注释后编译失败 return 0; }
编译错误信息(g++-13)
<source>: In function 'int main()': <source>:31:32: error: static assertion failed 31 | static_assert(std::ranges::input_range<const decltype(edges)>);// <- does not compile with message that ; | ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:31:32: note: constraints not satisfied In file included from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/string_view:48, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/basic_string.h:47, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/string:54, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/locale_classes.h:40, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/ios_base.h:41, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/streambuf:43, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/streambuf_iterator.h:35, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/iterator:66, from /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/ranges:43, from <source>:1: /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/ranges_base.h:501:13: required for the satisfaction of 'range<_Tp>' [with _Tp = const std::ranges::join_view<std::ranges::transform_view<std::ranges::cartesian_product_view<std::ranges::iota_view<int, int>, std::ranges::iota_view<int, int> >, main::_anon_155> >] /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/ranges_base.h:501:21: in requirements with '_Tp& __t' [with _Tp = const std::ranges::join_view<std::ranges::transform_view<std::ranges::cartesian_product_view<std::ranges::iota_view<int, int>, std::ranges::iota_view<int, int> >, main::_anon_155> >] /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/ranges_base.h:503:22: note: the required expression 'std::ranges::__cust::begin(__t)' is invalid 503 | ranges::begin(__t); | ~~~~~~~~~~~~~^~~~~ /opt/compiler-explorer/arm/gcc-13.2.0/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/include/c++/13.2.0/bits/ranges_base.h:504:20: note: the required expression 'std::ranges::__cust::end(__t)' is invalid 504 | ranges::end(__t); | ~~~~~~~~~~~^~~~~ cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail Compiler returned: 1
问题原因
你的transform lambda返回的是临时std::vector<Edge>对象,std::views::transform生成的transform_view在迭代时,每次解引用都会创建这个临时vector。而std::views::join要将这些临时容器展平为一个range时,其const版本无法满足input_range的概念要求:
- 当
join_view被const限定后,底层的transform_view也会变成const,其迭代器解引用得到的是临时vector的const引用,但join_view需要遍历这个临时容器的元素,而临时对象的生命周期管理在const range的语境下无法符合标准库的range概念约束,导致无法调用begin()和end()。
解决方法
方法1:返回view而非临时容器
将transform返回的vector替换为标准库view(如std::views::single+std::views::concat),这样transform返回的是一个轻量级view而非临时容器,view的const版本天然符合range要求,对应的const join_view也能满足input_range:
#include <ranges> #include <vector> #include <iostream> #include <tuple> #include <iterator> int main() { auto indicies = std::views::iota(0, 3); auto coords = std::views::cartesian_product(indicies, indicies); auto edges = coords | std::views::transform([](const auto& coord) { using Coord = std::pair<std::size_t, std::size_t>; using Edge = std::pair<Coord, Coord>; const auto& [x, y] = coord; auto curr = Coord{x, y}; // 用view替代vector存储边 auto right_edges = (x + 1 < 3) ? std::views::single(Edge{curr, Coord{x+1, y}}) : std::views::empty<Edge>(); auto down_edges = (y + 1 < 3) ? std::views::single(Edge{curr, Coord{x, y+1}}) : std::views::empty<Edge>(); return std::views::concat(right_edges, down_edges); }) | std::views::join; std::cout << edges.cbegin()->first.first << std::endl; static_assert(std::ranges::input_range<const decltype(edges)>); // 现在可以通过编译 return 0; }
方法2:提前materialize为容器
如果必须使用vector存储中间结果,可以先将transform的结果转换为一个实际的容器(如std::vector<std::vector<Edge>>),再对这个容器执行join操作。此时join_view的底层是一个const容器,其元素是const vector,而const vector本身是合法的range,因此const join_view可以满足input_range:
#include <ranges> #include <vector> #include <iostream> #include <tuple> #include <iterator> int main() { auto indicies = std::views::iota(0, 3); auto coords = std::views::cartesian_product(indicies, indicies); // 先将transform的结果转换为实际容器 auto temp_edges = coords | std::views::transform([](const auto& coord) { using Coord = std::pair<std::size_t, std::size_t>; using Edge = std::pair<Coord, Coord>; std::vector<Edge> ret; const auto& [x, y] = coord; auto curr = Coord{x, y}; if (x + 1 < 3) { ret.emplace_back(curr, Coord{x+1, y}); } if (y + 1 < 3) { ret.emplace_back(curr, Coord{x, y+1}); } return ret; }) | std::ranges::to<std::vector>(); auto edges = temp_edges | std::views::join; std::cout << edges.cbegin()->first.first << std::endl; static_assert(std::ranges::input_range<const decltype(edges)>); // 现在可以通过编译 return 0; }
内容的提问来源于stack exchange,提问作者Георгий Гуминов
相关产品推荐
相关产品推荐

