Emm*_*a X 7 c++ undefined-behavior c++20 std-ranges
考虑以下代码(单击此处获取 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;
}
Run Code Online (Sandbox Code Playgroud)
主要问题:调用std::prev而不是调用std::ranges::prev迭代器会使 gcc 陷入无限循环。这意味着存在编译器错误或代码调用std::prev调用了未定义的行为——这是哪一个?
关于后者的一些想法:std::prev需要LegacyBidirectionalIterator,而std::ranges::prev需要概念bidirectional_iterator。从我的理解,这两者之间的唯一区别是(从说明采取bidirectional_iterator):
不像LegacyBidirectionalIterator要求,bidirectional_iterator概念并不需要取消引用返回左值。
如果这确实意味着调用会std::prev调用未定义的行为:当使用新类型的迭代器调用时,基本上所有非范围算法都会调用未定义的行为,因为LegacyForwardIterator和forward_iterator具有相同的区别?旧算法的约束是否可以不放松到新的迭代器类型,以避免这种情况,因为它仍然向后兼容?
这意味着存在编译器错误或代码调用
std::prev调用了未定义的行为——这是哪一个?
后者,虽然 libstdc++ 应该能够检测到这种故障并更好地诊断它,就像你要求它那样做。
这里的问题是:
auto view = v | std::views::transform([] (auto i) { return static_cast<int>(i); });
Run Code Online (Sandbox Code Playgroud)
view的迭代器是 C++20 随机访问迭代器,但因为它们的引用类型是一个纯右值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;并且advance(it, n)对于非双向迭代器将一直循环直到它命中n......对于否定n显然永远不会发生。
也就是说,如果您使用 Ranges,您应该在所有情况下都使用std::ranges::meow而不是std::meow,因为此迭代器类别差异。这种情况非常引人注目,因为它是“有效”与“无限循环”,但请注意,这std::next(it, 100)将是一个递增it100 倍的循环,而std::ranges::next(it, 100)将 return it + 100,因此即使它们都“有效”,它仍然存在显着差异。