You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在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>的类型选择:

  1. 当输入是std::vector时,std::ranges::range_reference_t<Range>是int&,pair存储的是指向vector元素的左值引用——vector的生命周期长于循环中的pair对象,因此引用始终有效。
  2. 当输入是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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:58:13