首页 > 解决方案 > 转换视图的 std::prev 上的未定义行为

问题描述

考虑下面的代码(点击这里查看godbolt):

#include <algorithm>
#include <ranges>
#include <vector>

int main() {
    auto v = std::vector<short>{1, 2};
    auto view = v | std::views::transform([] (auto i) { return static_cast<int>(i); });
    auto it = view.begin() + 1;
    auto prev_it = std::ranges::prev(it); //this one is fine
    //auto prev_it = std::prev(it); //this one dies with an infinite loop

    return *prev_it;
}

主要问题:调用std::prev而不是std::ranges::prev迭代器会使 gcc 陷入无限循环。这意味着存在编译器错误或代码调用std::prev调用了未定义的行为——它是哪一个?


关于后者的一些想法:std::prev需要LegacyBidirectionalIterator,而std::ranges::prev需要概念bidirectional_iterator 。据我了解,这两者之间的唯一区别是(取自 bidirectional_iterator 的描述

LegacyBidirectionalIterator要求不同, bidirectional_iterator概念不需要取消引用来返回左值。

如果这确实意味着调用会std::prev调用未定义的行为:基本上所有非范围算法在使用新类型的迭代器调用时都会调用未定义的行为,因为LegacyForwardIteratorforward_iterator共享相同的区别?旧算法的约束是否可以不放松到新的迭代器类型,以避免这种情况,因为这仍然是向后兼容的?

标签: c++c++20undefined-behaviorstd-ranges

解决方案


这意味着存在编译器错误或代码调用std::prev调用了未定义的行为——它是哪一个?

后者,尽管 libstdc++ 应该能够检测到此故障并更好地诊断它,就像您要求它一样

这里的问题是:

auto view = v | std::views::transform([] (auto i) { return static_cast<int>(i); });

view的迭代器是 C++20 的随机访问迭代器,但是因为它们的引用类型是 prvalue int,所以它们只能是 C++17 的输入迭代器。这正是 C++20 之前的迭代器模型的工作方式:前向迭代器需要一个真正的引用。

std::ranges::prev使用 C++20 迭代器类别,std::prev使用 C++17(或真正的 C++98)迭代器类别。并std::prev要求BidirectionalIterator. 未指定库需要在多大程度上尝试验证迭代器确实是双向的,但prev(it, n)指定为advance(it, -n); return it;andadvance(it, n)对于非双向迭代器只会循环直到它命中n......这对于否定n显然永远不会发生。

也就是说,如果你使用 Ranges,你应该使用std::ranges::meow而不是std::meow在所有情况下,因为这个迭代器类别的差异。这种情况非常戏剧化,因为它是“工作”与“无限循环”,但请注意,这std::next(it, 100)将是一个递增it100 次的循环,而std::ranges::next(it, 100)将返回it + 100,所以即使它们都“工作”,它仍然是一个显着的差异。


推荐阅读