【问题标题】:Contiguous iterator detection连续迭代器检测
【发布时间】:2017-08-08 16:23:09
【问题描述】:

C++17 引入了 ContiguousIterator http://en.cppreference.com/w/cpp/iterator 的概念。 但是,std::iterator_traits<It>::iterator_category 似乎没有计划报告 contiguous_iterator_tag(与我们现在拥有 random_access_iterator_tag 的方式相同)。

为什么contiguous_iterator_tag 不见了?

是否有确定迭代器是否连续的常规协议? 还是编译时测试?

过去我提到对于容器如果有一个.data() 成员可以转换为指向::value 类型的指针并且有.size() 成员可以转换为指针差异,那么一个应该假设容器是连续的,但我无法提取迭代器的类似功能。

一种解决方案可能是同时为连续迭代器提供data 函数。

当然,如果&(it[n]) == (&(*it)) + n 对所有n 都有效,则连续概念有效,但这不能在编译时检查。


编辑:我发现这个视频将其置于更广泛的 C++ 概念背景中。 CppCon 2016: "Building and Extending the Iterator Hierarchy in a Modern, Multicore World" 由 Patrick Niedzielski 撰写。该解决方案使用概念(Lite),但最终的想法是连续迭代器应该实现 pointer_from 函数(与我的 data(...) 函数相同)。

结论是,概念将有助于将理论形式化,但它们并不神奇,因为某人、某处将在连续的迭代器上定义新的特别命名的函数。 该演讲概括了分段迭代器(具有相应的函数segmentlocal),不幸的是它没有提及跨步指针。


2020 年编辑:

标准现在有

struct contiguous_iterator_tag: public random_access_iterator_tag { };

https://en.cppreference.com/w/cpp/iterator/iterator_tags

【问题讨论】:

  • 为什么contiguous_iterator_tag 不见了? ← 因为它会默默地破坏 C++17 之前的代码,假设 std::vector::iterator 恰好是一个随机访问迭代器?
  • @kennytm,微妙的原因。但是,如果正确使用特征(我认为),可以使其向后兼容(例如,如果 random_access_iterator 派生自 contiguous_iterator_tag)。如果仅仅因为 trait 使用不当而中断,那将是不幸的。
  • 情况似乎不太乐观。我认为一个可能的想法是为所有连续迭代器提供.data()成员或data(ContiguousIterator it)函数,这类似于当前具有.data()成员的容器是连续的.
  • 好吧,他们对临时多态性有什么期望? C++ 背后的一个动机是避免它。整个 iterator_tag 应该被驱逐为掠夺。
  • @JiveDadson 我部分同意,原因如下。我得出的结论是,对于应该由操作员检测实施的东西,标签是一个不好的替代品。比如有operator++就是前向迭代器,有operator+=就是随机访问,有数据函数就是连续迭代器,等等。

标签: c++ iterator c++17 c++20 iterator-traits


【解决方案1】:

原答案

基本原理在N4284 中给出,这是连续迭代器提案的采用版本:

本文介绍了术语“连续迭代器”作为随机访问迭代器的改进,没有引入相应的contiguous_iterator_tag,在 Nevin Liber 的论文 N3884“连续迭代器:改进随机访问迭代器”。

一些代码被破坏了,因为它假定std::random_access_iterator 不能被细化,并且对其进行了明确的检查。基本上它破坏了不依赖多态性来检查迭代器类别的错误代码,但它仍然破坏了代码,因此从提案中删除了std::contiguous_iterator_tag

此外,std::reverse_iterator-like 类还有一个额外的问题:反向连续迭代器不能是连续迭代器,而是常规随机访问迭代器。对于std::reverse_iterator,这个问题本来可以解决,但更多用户定义的迭代器包装器在复制迭代器类别的同时扩充迭代器可能会撒谎或停止正常工作(例如 Boost 迭代器适配器)。

C++20 更新

由于我上面的原始答案,std::contiguous_iterator_tag 被带回 Ranges TS,然后在 C++20 中采用。为了避免上述问题,std::iterator_traits<T>::iterator_category 的行为没有改变。相反,std::iterator_traits 的用户特化现在可以定义一个额外的iterator_concept 成员类型别名,该别名允许别名std::contiguous_iterator_tag 或以前的迭代器标签。标准组件已相应更新,以便将指针和适当的迭代器标记为连续的迭代器。

标准定义了一个仅展示的ITER_CONCEPT(Iter),给定一个迭代器类型Iter,如果它存在则别名std::iterator_traits<Iter>::iterator_concept,否则别名std::iterator_traits<Iter>::iterator_category。没有等效的标准面向用户的类型特征,但新的迭代器概念使用了 ITER_CONCEPT。这是一个强烈的提示,您应该使用这些迭代器概念而不是旧式标记调度来实现其行为取决于迭代器类别的新函数。上述概念可用作布尔特征,因此您可以简单地检查迭代器是否为连续迭代器,如下所示:

static_assert(std::contiguous_iterator<Iter>);

std::contiguous_iterator 因此是 C++20 概念,您应该使用它来检测给定的迭代器是随机访问迭代器(它也有对应的范围:std::contiguous_range)。值得注意的是,std::contiguous_iterator 除了要求 ITER_CONCEPT 匹配 std::contiguous_iterator_tag 之外,还有一些额外的约束:最值得注意的是,它要求 std::to_address(it) 是返回原始指针类型的有效表达式。 std::to_address 是一个小型实用函数,旨在避免在尝试检索连续迭代器指向的地址时可能发生的一些陷阱 - 您可以在 Helpful pointers for ContiguousIterator 中阅读有关它解决的问题的更多信息。

【讨论】:

  • 很好的解释。这是一个悲伤的场景。我猜 ContiguousIterator 不能以编程方式使用,至少不能用于std::vector
  • @alfC 不。实际上我应该说 Ranges TS,它依赖于 Concepts,但实际上保持相同的旧协议。恕我直言,从头开始的新协议将是前进的道路,为 BC 保留旧协议。 Ranges v3 引入了ContiguousRange 概念,但我不确定它是如何工作的。
  • @Oktalist range-v3 ContiguousRange 概念通过将ContiguousRange 定义为RandomAccessRange,其引用类型是真正的引用并且自定义点针对其,完全回避了识别连续迭代器的问题ranges::data 返回一个指向同一引用类型的指针。现在唯一的用法是 spanContiguousRange 的转换构造函数 - 因为它不假定迭代器类型是连续的 - 只有在范围也建模 SizedRange 时才有用。
  • @alfC 过去曾提出过分段和分层迭代器的建议,但它们被认为太复杂而无法工作。跨步的事情通常出现在 SIMD 和并行算法的讨论中,但我认为没有任何东西提出某种跨步迭代器。 Range-v3 中有一个 strided 视图,但它不是针对 C++20 提出的。到目前为止,标准库中唯一的跨步组件可能是 std::[g]slice_array,但它似乎没有提供迭代器。
  • @alfC 谢谢,我用 C++20 提供的连续迭代器的(希望是准确的)摘要更新了答案。
猜你喜欢
  • 2015-05-27
  • 1970-01-01
  • 1970-01-01
  • 2016-12-11
  • 2017-10-22
  • 1970-01-01
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多