为何在C++20中无法从std::views::iota生成数字对?
问题:std::views::iota输入时range_pair函数出现未定义行为?
问题重现
我编写了如下函数用于从输入范围生成相邻元素对:
template <typename Range> requires std::ranges::borrowed_range<Range> auto pair(Range && r) { // 创建原范围的迭代器序列 auto iterator_range = std::views::iota( std::ranges::begin(r), std::ranges::end(r)); return iterator_range | std::views::drop(1) | std::views::transform([](auto const & it){ using T = std::ranges::range_reference_t<Range>; return std::pair<T,T>(*std::prev(it), *it); }); }
该函数对std::vector输入完全正常:
auto number_range = std::vector{0,1,2,3,4,5,6,7,8,9}; for(auto [a, b] : pair(number_range)) std::cout << a << ", " << b << std::endl;
但传入std::views::iota(0, 10)时,GCC运行会崩溃,Clang则符合预期,代码如下:
auto number_range = std::views::iota(0, 10); for(auto [a, b] : pair(number_range)) std::cout << a << ", " << b << std::endl;
预期输出:
std::vector 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9 std::views::iota 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9
问题原因
你的代码存在未定义行为,核心问题在于std::pair<T,T>的类型选择:
- 当输入是
std::vector时,std::ranges::range_reference_t<Range>是int&,pair存储的是指向vector元素的左值引用——vector的生命周期长于循环中的pair对象,因此引用始终有效。 - 当输入是
std::views::iota时,std::views::iota是生成右值元素的view,std::ranges::range_reference_t<Range>是int&&,此时pair存储的是右值引用。这些引用绑定到*it和*std::prev(it)返回的临时int对象,而临时对象的生命周期仅在lambda调用期间。当lambda返回pair后,临时对象销毁,pair中的引用变为悬垂引用,后续访问这些引用就是未定义行为——这导致GCC崩溃,Clang的正常表现只是巧合,并非代码正确。
补充说明:std::views::iota的迭代器是合法的双向迭代器,std::prev(it)本身没有问题,根源还是悬垂引用。
解决方法
将pair的存储类型改为值类型,而非引用类型,彻底避免悬垂问题。可以用std::ranges::range_value_t<Range>替代range_reference_t:
template <typename Range> requires std::ranges::borrowed_range<Range> auto pair(Range && r) { auto iterator_range = std::views::iota( std::ranges::begin(r), std::ranges::end(r)); return iterator_range | std::views::drop(1) | std::views::transform([](auto const & it){ using T = std::ranges::range_value_t<Range>; return std::pair<T,T>(*std::prev(it), *it); }); }
如果希望在输入是左值范围时保留引用、右值范围时存储值,可以用条件类型判断:
template <typename Range> requires std::ranges::borrowed_range<Range> auto pair(Range && r) { auto iterator_range = std::views::iota( std::ranges::begin(r), std::ranges::end(r)); return iterator_range | std::views::drop(1) | std::views::transform([](auto const & it){ using Ref = std::ranges::range_reference_t<Range>; using T = std::conditional_t< std::is_rvalue_reference_v<Ref>, std::ranges::range_value_t<Range>, Ref >; return std::pair<T,T>(*std::prev(it), *it); }); }
修改后,无论输入是左值范围(如vector)还是右值view(如iota),都能保证pair中的成员有效,避免未定义行为。
内容的提问来源于stack exchange,提问作者bradgonesurfing
相关产品推荐
相关产品推荐

