考虑以下代码,它使用 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
使用新范围时的命名空间,并重写所有现有代码以确保它们适用于新范围?