【问题标题】:ranges::view::transform produces an InputIterator preventing the use of std::prev范围::view::transform 产生一个 InputIterator 阻止使用 std::prev
【发布时间】:2021-09-07 01:32:40
【问题描述】:

考虑以下代码,它使用 C++20 中的 Ranges 库:

#include <vector>
#include <ranges>
#include <iostream>

int main()
{
    std::vector<int> v{0,1,2,3,4,5,6,7};

    auto transformed = std::ranges::views::transform(v, [](int i){ return i * i; });

    std::cout << *std::prev(std::end(transformed));
}

得知(至少在 GCC-10.3.0 和 GCC-12.0.0 下)这段代码gets stuck in std::prev,我感到非常惊讶。

发生的情况是,由于 lambda 不返回左值引用,transformed 范围迭代器被归类为输入迭代器(请参阅 rules 以获取 iterator_category 选择以获取 views::transform)。但是,std::prevrequires 的迭代器至少是一个双向迭代器,所以我猜这段代码实际上是 UB。在 libstdc++ 中,将std::prev 应用于输入迭代器会导致此函数

template<typename _InputIterator, typename _Distance>
__advance(_InputIterator& __i, _Distance __n, input_iterator_tag)
{
    // concept requirements
    __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
    __glibcxx_assert(__n >= 0);
    while (__n--)
        ++__i;
}

__n == -1 调用,这解释了观察到的行为。

如果我们用手动迭代器减量替换std::preveverything works fine。切换到std::ranges::prevworks, too

现在,我不能对 std::vector 的视图做 std::prev 显然是荒谬的。虽然存在一个简单的解决方案,但我对标准库的新旧范围操作部分之间的这种意想不到的相互作用感到非常担心。所以,我的问题是:这是一个已知问题吗,在使用新范围时,我是否真的应该忘记 std::ranges 命名空间中的所有内容,并重写所有现有代码以确保它们适用于新范围

【问题讨论】:

    标签: c++ iterator c++20 std-ranges


    【解决方案1】:

    根据 C++17 的计算,它不是随机访问迭代器。 transform 必须返回一个值而不是 reference,并且 C++17 的迭代器类别不允许任何高于 InputIterator 的东西。

    但根据 C++20 的规则,这种类型是 std::random_access_iterator,它允许在连续以下的任何迭代器/范围上使用类似代理的迭代器。

    std::prev 是 C++20 之前的工具,因此它按照 C++20 之前的规则工作。如果需要使用 C++20 规则,则必须使用 the C++20 equivalent: std::ranges::prev

    现在,我不能对 std::vector 的视图执行 std::prev 显然是荒谬的。

    不,这是必要的。 C++20 的概念化迭代器类别比以前的 C++ 版本的限制更少。这意味着有些迭代器不能在 C++20 之前的代码中使用,而 可以在 C++20 基于范围的代码中使用。

    这就是为什么我们在 ranges 命名空间中为这些东西提供新函数的原因。

    【讨论】:

    • 您是否知道某个资源可以总结概念和旧需求之间的差异?也许还有这些变化背后的理由。
    • @Timo 对此也很感兴趣,我有点担心在我的代码库中盲目地将所有std::X 更改为std::ranges::X
    • @lisyarus:你不应该“盲目”做任何事情。你应该做对你的代码有意义的事情。如果您在某些代码中使用 C++20 范围/迭代器,那么该代码需要使用 C++20 算法,因为 C++20 范围/迭代器可能不适用于旧的 C++ 之前20码。
    • @NicolBolas 当然,这有点夸张。我要说的是我不知道 C++20 之前的迭代器和 C++20 范围之间的这种不兼容性,并且希望看到一些全面的差异概述/迁移指南(而不是阅读通过整个相应的标准部分)。
    【解决方案2】:

    你的转换返回一个纯右值,所以它不能InputIterator以外的任何东西。这是 C++20 中迭代器类别发生变化的主要原因之一。

    如果你的操作的返回值是一个引用,then you can

    【讨论】:

      猜你喜欢
      • 2020-11-02
      • 1970-01-01
      • 2011-09-14
      • 2011-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-24
      相关资源
      最近更新 更多