Ranges::view::transform 生成一个 InputIterator,防止使用 std::prev

2024-01-11

考虑以下代码,它使用 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 下)这段代码陷入困境std::prev https://wandbox.org/permlink/IAOwmkKQlabOP6cu.

发生的情况是,由于 lambda 不返回左值引用,因此transformed范围迭代器被分类为输入迭代器(参见rules https://en.cppreference.com/w/cpp/ranges/transform_view/iterator for iterator_category选择为views::transform)。然而,std::prev requires https://en.cppreference.com/w/cpp/iterator/prev迭代器至少是一个双向迭代器,所以我猜这段代码实际上是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::prev使用手动迭代器递减,一切正常 https://wandbox.org/permlink/t6mMSBOcDFzOTZgt。切换到std::ranges::prev 也有效 https://wandbox.org/permlink/BVWhksNDhVgvQR6H.

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


根据 C++17 的计算,它不是随机访问迭代器。transform必须返回一个值而不是reference,并且 C++17 的迭代器类别不允许对 InputIterator 之上的任何内容进行这种操作。

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

std::prev是 C++20 之前的工具,因此它按照 C++20 之前的规则工作。如果您需要使用 C++20 规则,则必须使用C++20 等效项:std::ranges::prev https://en.cppreference.com/w/cpp/iterator/ranges/prev.

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

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

这就是为什么我们在ranges命名空间。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ranges::view::transform 生成一个 InputIterator,防止使用 std::prev 的相关文章

随机推荐