为什么 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
的存在可归因于两件事:
- 一些算法 return 迭代器进入传递给算法的范围,并且
- 有些范围的迭代器可以超过其范围,有些则没有。
对于 (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::fill
和 ranges::generate
。
那么为什么不用 return safe_subrange_t
而不是 safe_iterator_t
,您可能会问?这不会使该算法与其他算法很好地组合吗?
会的!但是会return调用他们已有的来电者信息;即范围结束的位置。在算法中,我们避免做不必要的工作以使其尽可能高效。给定 ABI 和调用约定,returning 一个指针(例如,找到的位置)比 returning 一个包含两个指针的结构(例如,找到的位置和范围的末尾)更有效。
相反,我们使用更高级别的视图(和 range-v3 中的操作)来简洁地组合多个操作。
我正在编写一种算法,将一些数据写入提供的输出范围(问题的初始文本包括细节,并将评论中的讨论转向错误的方向)。我希望它在 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
的存在可归因于两件事:
- 一些算法 return 迭代器进入传递给算法的范围,并且
- 有些范围的迭代器可以超过其范围,有些则没有。
对于 (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::fill
和 ranges::generate
。
那么为什么不用 return safe_subrange_t
而不是 safe_iterator_t
,您可能会问?这不会使该算法与其他算法很好地组合吗?
会的!但是会return调用他们已有的来电者信息;即范围结束的位置。在算法中,我们避免做不必要的工作以使其尽可能高效。给定 ABI 和调用约定,returning 一个指针(例如,找到的位置)比 returning 一个包含两个指针的结构(例如,找到的位置和范围的末尾)更有效。
相反,我们使用更高级别的视图(和 range-v3 中的操作)来简洁地组合多个操作。