当一个跨度是另一个跨度的子跨度时,C++ 是否允许在 std::span::迭代器之间进行比较?

Does C++ allow comparison between std::span::iterators when one span is a subspan of the other?

提问人:Maks Verver 提问时间:7/27/2023 最后编辑:DailyLearnerMaks Verver 更新时间:7/27/2023 访问量:374

问:

一般来说,C++ 不允许比较不同容器之间的迭代器。例如:

int main() {
  std::vector<int> v = {1, 2, 3};
  std::vector<int> w = {4, 5, 6};
  std::cout << v.end() == w.end() << std::endl;  // undefined!
}

但是,对于使用 ?例如:std::span::subspan()

int main() {
  int a[4] = { 1, 2, 3, 4};
  std::span<int> s(a);
  std::span<int> t = s.subspan(1, 2);
  std::cout << t.begin() - s.begin() << std::endl;
}

这会打印,这是意料之中的,因为内部迭代器可能只是指向底层数组的指针。问题是:标准是否保证它正常工作?1

如果是这样,更一般地说,我是否可以比较来自内存中同一连续对象的任何跨度的迭代器?例如:

int main() {
  int a[5] = { 1, 2, 3, 4, 5};
  std::span<int> s(a);
  std::cout << (s.subspan(1, 1).end() < s.subspan(3, 1).begin()) << std::endl;
}
C++ 语言律师 C++20 标准跨度

评论

3赞 Brian61354270 7/27/2023
添加了 language-lawyer 标签,每个“做标准保证”
1赞 Brian61354270 7/27/2023
相关草案部分:24.7.2.2.7 / span.iterators
0赞 dalfaB 7/27/2023
比较不同对象上的迭代器是一个 U.B. 使用 MSVC 尝试此代码,在调试中出现错误“无法比较不兼容的跨度迭代器”,但由于发布中有原始指针,因此代码运行良好。
3赞 Ben Voigt 7/27/2023
另一个有趣的问题是,是否可以将子跨度迭代器与底层容器中的迭代器进行比较

答:

3赞 康桓瑋 7/27/2023 #1

我不认为标准允许这样做。

根据 [forward.iterators]Cpp17ForwardIterator 的描述:

for 正向迭代器的域是 相同的基础序列。==

这意味着两个迭代器的相等和不等式运算仅在相同的基础序列上定义。

在您的示例中,被比较的两个迭代器的基础序列是不同的,因为它们具有不同的起点和不同的大小,这使得当其中一个迭代器超出另一个序列的边界时,比较不再明确定义。


如评论中所述,您的示例将在 MSVC-STL 的调试模式下触发断言,维护者在 #1435 中对此进行了解释。CPP 核心指南中也讨论了类似的问题,请参阅 #1157

评论

0赞 Weijun Zhou 7/27/2023
是否遇到与相同的问题?我不确定我是否应该问一个新问题,但偶尔我想问,当我知道这不等于潜在范围时。这是允许的吗?std::ranges::subrangestd::span::subspanstd::prev(subrange.begin())subrange.begin()begin()
1赞 康桓瑋 7/27/2023
我认为没关系,因为指定返回底层原始迭代器。原始迭代器上的操作的有效性不会改变。std::prev(subrange.begin())subrange.begin()
1赞 Yakk - Adam Nevraumont 7/28/2023
“被比较的两个迭代器的基础序列是不同的”——这需要引用。这句话的措辞是,span 的基础序列仍然是原始缓冲区,span 本身的开始/结束是一个子序列。这将取决于标准的措辞方式。
1赞 康桓瑋 7/28/2023
@Yakk-AdamNevraumont:我不认为当前的标准明确定义了迭代器的基本序列是什么。在 C++17 中有一个:“当且仅当表达式 ++i 的应用序列有限,使 i == j 时,迭代器 j 才被称为可从迭代器 i 访问。如果 j 可以从 i 到达,它们指的是相同序列的元素“,但我认为在当前上下文中仍然可以以不同的方式解释它。