如何解决range::view::transform 产生一个 InputIterator 阻止使用 std::prev
考虑以下使用 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
选择 {{1 }})。但是,views::transform
requires 的迭代器至少是一个双向迭代器,所以我猜这段代码实际上是UB。在 libstdc++ 中,将 std::prev
应用于输入迭代器会导致此函数
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
替换为手动迭代器递减,everything works fine。切换到 std::prev
works,too。
现在,我不能对 std::ranges::prev
上的视图执行 std::prev
显然是荒谬的。虽然存在一个简单的解决方案,但我非常担心标准库的新旧范围操作部分之间这种意想不到的相互作用。所以,我的问题是:这是一个已知问题吗?在处理新范围时,我是否真的应该忘记 std::vector
命名空间中没有的所有内容,并重写所有现有代码以确保它们与新范围?
解决方法
根据 C++17 的计算,它不是随机访问迭代器,但根据 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
命名空间中为这些东西添加新函数的原因。
您的转换返回一个纯右值,所以它不能是除 InputIterator 以外的任何东西。这是 C++20 中迭代器类别发生变化的主要原因之一。
如果操作的返回值是引用,则 then you can。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。