为什么在使用std::ranges::output_range的算法中返回std::ranges::safe_iterator_t而不是std::ranges::safe_subrange_t?

6
我正在编写一个算法,将一些数据写入到提供的输出范围(问题的初始文本包含了特定的细节,这使得评论中的讨论偏离了错误的方向)。我希望它在API上尽可能接近标准库中的其他范围算法。
我查看了最新的草案,只找到了两个std::ranges::output_range算法: 它们都返回std::ranges::safe_iterator_t。我认为逻辑上应该返回std::ranges::safe_subrange_t。即使你写入输出流,也可以在那种情况下返回一个迭代器-哨兵对,并将该范围传递到下一行。
我找到了P0970,看起来是后来添加了std::ranges::safe_subrange_t。也许算法只是没有被更新?还是有其他原因?

1
什么是“Unicode标量值”? - Lightness Races in Orbit
2
也许你应该提供一个你想要做的事情的例子。 - Lightness Races in Orbit
那又跟代码单元序列有什么不同呢?您想象的是哪种类型的转换? - Lightness Races in Orbit
@LightnessRacesinOrbit 我已经添加了我的代码。 - user3624760
1
@Lyberta:算法不是通过operator|来工作的。这是一种根本不同的功能,由范围操作等管理...它们不是C ++ 20的一部分。它们可能最终被标准化,但它们不是C ++ 20的一部分。 - Nicol Bolas
显示剩余7条评论
1个回答

11
存在safe_iterator_t的ranges设计可以归因于两个原因:
  1. 一些算法返回迭代器,这些迭代器指向传递给算法的range(s)。
  2. 一些ranges具有可以超出其范围的迭代器,而一些则没有。
对于(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);

该函数返回一个迭代器,指向范围rg。但如果rg是右值,则在函数返回时它很可能被删除。这意味着返回的迭代器几乎肯定是悬挂的。 safe_iterator_t检查R是否是那些迭代器可以安全地超出范围的特殊范围类型。如果是这样,您只需获得迭代器,无需任何麻烦。如果不是,则此函数将返回一个名为std::ranges::dangling的特殊类型的空对象。这旨在提示您需要更深入地考虑生命周期。
对于需要输出范围的算法(如ranges::fillranges::generate),相同的逻辑适用。
您可能会问,为什么不返回safe_subrange_t而是返回safe_iterator_t?这样做不会使算法与其他算法组合得更好吗? 确实会! 但它会向调用者返回他们已经拥有的信息; 即范围结束位置的位置。在算法中,我们避免做不必要的工作,以使它们尽可能高效。考虑到ABI和调用约定,返回指针(例如找到的位置)比返回包含两个指针(例如找到的位置和范围结束)的结构更有效。
相反,我们使用更高级别的视图(以及在range-v3中的操作)来简洁地组合多个操作。

为什么如果传递了拥有临时对象,它不会简单地编译失败呢? - oliora
1
好问题。你说得对,在find的情况下,那可能是一个更好的答案。还有其他返回多个信息片段的算法,在这种情况下,safe_iterator_t用于仅编辑悬挂的部分。我们本可以决定将仅返回safe_iterator_t的算法与返回带有悬挂成员的结构的算法区别对待,但没有人为此争论,所以我们就这样做了。 - Eric Niebler
我猜,试图在普通迭代器的位置使用“悬挂”将无法编译,因此无论如何都会有编译错误,只是离代码中断点更远。 - oliora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接