我正在编写一种算法,将一些数据写入提供的输出范围(问题的初始文本包括具体细节,这将评论中的讨论转向了错误的方向)。我希望它在 API 中尽可能接近标准库中的其他范围算法。
我查看了 std::ranges::output_range
实例的最新草案,只发现了 2 种算法:
它们都返回std::ranges::safe_iterator_t
。我认为返回 std::ranges::safe_subrange_t 是合乎逻辑的。即使您写入输出流,在这种情况下您仍然可以返回迭代器-哨兵对并将该范围向下传递。
我找到了P0970看起来 std::ranges::safe_subrange_t
是后来添加的。也许算法根本没有更新?还是有其他原因?
最佳答案
safe_iterator_t
的存在在范围设计中可以归因于两件事:
- 某些算法将迭代器返回到传递给算法的范围内,并且
- 有些范围的迭代器生命周期可能超过其范围,而有些则没有。
对于 (2),一个示例可能是 std::string_view
。即使在string_view
之后,进入字符串 View 的迭代器仍然可以使用。对象本身已被破坏。那是因为string_view
只是引用内存中其他位置的元素,而 string_view
对象本身不包含额外的附加状态。反例是任何容器;例如,std::vector
,它拥有它的元素,以及 C++20 std::ranges
中的许多 View 。命名空间,其中大部分包含附加状态(例如 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::fill
和ranges::generate
.
那么为什么不返回 safe_subrange_t
而不是safe_iterator_t
,你可能会问?这难道不会使该算法与其他算法很好地结合吗?
会的!但是它会返回他们已经拥有的调用者信息;即范围结束的位置。在算法中,我们避免做不必要的工作,以使它们尽可能高效。给定 ABI 和调用约定,返回一个指针(例如,找到的位置)比返回包含两个指针(例如,找到的位置和范围末尾)的结构更有效。
相反,我们使用更高级别的 View (以及 range-v3 中的操作)来简洁地组合多个操作。
关于c++ - 为什么从采用 std::ranges::output_range 的算法返回 std::ranges::safe_iterator_t 而不是 std::ranges::safe_subrange_t,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57860924/