为什么 return std::ranges::safe_iterator_t 而不是 std::ranges::safe_subrange_t 来自采用 std::ranges::output_range 的算法

Why return std::ranges::safe_iterator_t instead of std::ranges::safe_subrange_t from algorithms taking std::ranges::output_range

我正在编写一种算法,将一些数据写入提供的输出范围(问题的初始文本包括细节,并将评论中的讨论转向错误的方向)。我希望它在 API 中尽可能接近标准库中的其他范围算法。

我查看了 std::ranges::output_range 实例的最新草案,发现只有 2 个算法:

他们都 return std::ranges::safe_iterator_t。我认为 return std::ranges::safe_subrange_t 是合乎逻辑的。即使您写入输出流,在这种情况下您仍然可以 return 迭代器-哨兵对并将该范围传递给行。

我找到了 P0970,看起来 std::ranges::safe_subrange_t 是后来添加的。也许算法根本没有更新?还是有其他原因?

范围设计中 safe_iterator_t 的存在可归因于两件事:

  1. 一些算法 return 迭代器进入传递给算法的范围,并且
  2. 有些范围的迭代器可以超过其范围,有些则没有。

对于 (2),示例可能是 std::string_view。即使在 string_view 对象本身被销毁后,字符串视图中的迭代器仍然可以使用。这是因为 string_view 只是引用内存中其他地方的元素,而 string_view 对象本身不包含额外的附加状态。反例是任何容器;例如,std::vector,它拥有它的元素,以及 C++20 的 std::ranges 命名空间中的许多视图,其中大部分包含附加状态(例如,views::filter 的谓词)。

将上面的两个项目符号放在一起,现在考虑像 find 这样的函数(简化):

template <input_range R, class T>
  requires ...
safe_iterator_t<R> find(R && rg, const T & val);

这个函数 returns 是范围 rg 中的一个迭代器,但是如果 rg 是一个右值,那么当函数 returns 时它可能会被删除.这意味着 returned 迭代器几乎肯定是悬空的。

safe_iterator_t 检查 R 是否是迭代器可以安全地超出范围的那些特殊范围类型之一。如果是这样,您只需取回迭代器,不用大惊小怪。如果不是,则此函数 return 是一个名为 std::ranges::dangling 的特殊类型的空对象。这是为了让您了解您需要更深入地思考这里的生命这一事实。

相同的逻辑适用于采用输出范围的算法,例如 ranges::fillranges::generate

那么为什么不用 return safe_subrange_t 而不是 safe_iterator_t,您可能会问?这不会使该算法与其他算法很好地组合吗?

会的!但是会return调用他们已有的来电者信息;即范围结束的位置。在算法中,我们避免做不必要的工作以使其尽可能高效。给定 ABI 和调用约定,returning 一个指针(例如,找到的位置)比 returning 一个包含两个指针的结构(例如,找到的位置和范围的末尾)更有效。

相反,我们使用更高级别的视图(和 range-v3 中的操作)来简洁地组合多个操作。