I'm writing an algorithm that writes some data to provided output range (initial text of the question included the specifics and that turned discussion in comments into a wrong direction). I want it to be as close as possible in API to other ranges algorithms in the standard library.
I've looked at the latest draft for instances of std::ranges::output_range
and found only 2 algorithms:
And they both return std::ranges::safe_iterator_t
. I thought it is logical to return std::ranges::safe_subrange_t
instead. Even if you write to output stream, you can still return an iterator-sentinel pair in that case and pass that range down the line.
I've found P0970 and it looks like std::ranges::safe_subrange_t
was added later. Maybe the algorithms were simply not updated? Or is there a different reason?
The presence of safe_iterator_t
in the ranges design can be attributed to two things:
For (2), an example might be std::string_view
. Iterators into the string view are still ok to use even after the string_view
object itself has been destroyed. That is because a string_view
is just referring to elements elsewhere in memory, and the string_view
object itself contains no extra additional state. Counter-examples would be any of the containers; eg, std::vector
, which owns its elements, and many of the views in C++20's std::ranges
namespace, most of which contain additional state ( views::filter
's predicate, for instance).
Taking the two bullets above together, now consider a function like find
(simiplifying):
template <input_range R, class T>
requires ...
safe_iterator_t<R> find(R && rg, const T & val);
This function returns an iterator into the range rg
, but if rg
is an rvalue, then it will probably get deleted when the function returns. That means the returned iterator is almost certainly dangling.
safe_iterator_t
checks to see if R
is one of those special range type for which the iterators can safely outlive the range. If so, you just get the iterator back, no muss no fuss. If not, then this function returns an empty object of a special type named std::ranges::dangling
. That is intended to clue you in to the fact that you need to be thinking more deeply about lifetime here.
The same logic holds for algorithms that take output ranges, like ranges::fill
and ranges::generate
.
So why not return safe_subrange_t
instead of safe_iterator_t
, you might ask? Wouldn't that make the algorithm compose nicely with other algorithms?
It would! But it would be returning to the caller information they already have; namely, the position of the end of the range. In the algorithms, we avoid doing needless work to make them as efficient as possible. Given ABIs and calling conventions, returning a pointer (eg, the found position) is more efficient than returning a struct containing two pointers (eg, the found position and the end of the range).
Rather, we use higher-level views (and actions, in range-v3) for composing multiple operations concisely.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.