std::prev 的转换视图未定义行为
Undefined behaviour on std::prev for transform-view
考虑以下代码 (click here for 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
requires a LegacyBidirectionalIterator, whereas std::ranges::prev
requires the concept bidirectional_iterator。据我了解,这两者之间的唯一区别是(取自bidirectional_iterator的描述):
Unlike the LegacyBidirectionalIterator requirements, the bidirectional_iterator concept does not require dereference to return an lvalue.
如果这确实意味着调用 std::prev
会调用未定义的行为:基本上所有非范围算法在使用新类型的迭代器调用时都会调用未定义的行为,因为 LegacyForwardIterator and forward_iterator 具有相同的区别吗?旧算法的约束是否可以不放宽到新的迭代器类型,以避免这种情况,因为它仍然向后兼容?
This means there’s a compiler bug or the code calling std::prev
invokes undefined behaviour – which one is it?
后者,虽然 libstdc++ 应该能够检测到这个故障并更好地诊断它 as it does if you ask it to。
这里的问题是:
auto view = v | std::views::transform([] (auto i) { return static_cast<int>(i); });
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
显然永远不会发生。
就是说,如果您使用范围,则在所有情况下都应使用 std::ranges::meow
而不是 std::meow
,因为迭代器类别不同。这种情况非常引人注目,因为它是“有效”与“无限循环”的对比,但请注意 std::next(it, 100)
将是一个递增 it
100 次的循环,而 std::ranges::next(it, 100)
将 return it + 100
,因此即使它们都“有效”,仍然存在显着差异。
考虑以下代码 (click here for 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
requires a LegacyBidirectionalIterator, whereas std::ranges::prev
requires the concept bidirectional_iterator。据我了解,这两者之间的唯一区别是(取自bidirectional_iterator的描述):
Unlike the LegacyBidirectionalIterator requirements, the bidirectional_iterator concept does not require dereference to return an lvalue.
如果这确实意味着调用 std::prev
会调用未定义的行为:基本上所有非范围算法在使用新类型的迭代器调用时都会调用未定义的行为,因为 LegacyForwardIterator and forward_iterator 具有相同的区别吗?旧算法的约束是否可以不放宽到新的迭代器类型,以避免这种情况,因为它仍然向后兼容?
This means there’s a compiler bug or the code calling
std::prev
invokes undefined behaviour – which one is it?
后者,虽然 libstdc++ 应该能够检测到这个故障并更好地诊断它 as it does if you ask it to。
这里的问题是:
auto view = v | std::views::transform([] (auto i) { return static_cast<int>(i); });
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
显然永远不会发生。
就是说,如果您使用范围,则在所有情况下都应使用 std::ranges::meow
而不是 std::meow
,因为迭代器类别不同。这种情况非常引人注目,因为它是“有效”与“无限循环”的对比,但请注意 std::next(it, 100)
将是一个递增 it
100 次的循环,而 std::ranges::next(it, 100)
将 return it + 100
,因此即使它们都“有效”,仍然存在显着差异。