Section: 25.7.21 [range.reverse], 25.7.10 [range.take], 25.7.12 [range.drop], 25.7.13 [range.drop.while], 25.7.20 [range.common], 25.7.13 [range.drop.while], 25.7.23 [range.elements] Status: C++23 Submitter: Barry Revzin Opened: 2020-11-01 Last modified: 2023-11-22
Priority: Not Prioritized
View all other issues in [range.reverse].
View all issues with C++23 status.
Discussion:
Consider the following approach to trimming a std::string
:
auto trim(std::string const& s) { auto isalpha = [](unsigned char c){ return std::isalpha(c); }; auto b = ranges::find_if(s, isalpha); auto e = ranges::find_if(s | views::reverse, isalpha).base(); return subrange(b, e); }
This is a fairly nice and, importantly, safe way to implement trim
. The iterators b
and e
returned from find_if
will not dangle, since they point into the string s
whose lifetime outlives the function. But the status quo in C++20 is that s | views::reverse
is not a borrowed range (because reverse_view<V>
is never a borrowed range for any V
).
As a result, find_if(s | views::reverse, isalpha)
returns dangling rather than
a real iterator.
auto trim(std::string const& s) { auto isalpha = [](unsigned char c){ return std::isalpha(c); }; auto b = ranges::find_if(s, isalpha); auto reversed = s | views::reverse; auto e = ranges::find_if(reversed, isalpha).base(); return subrange(b, e); }
But borrowed range can be a transitive property. s
itself is a borrowed range (as all
lvalue references are) so s | views::reverse
could be made to be too, which would allow
the first example above to work with really no downside. We know such an iterator would not dangle,
we just need to teach the library this.
reverse_view<V>
a borrowed range when V
is a borrowed range (and likewise several other range adapters).
[2021-01-15; Telecon prioritization]
Set status to Tentatively Ready after five P0 votes in reflector discussion.
[2021-02-26 Approved at February 2021 virtual plenary. Status changed: Tentatively Ready → WP.]
Rationale:
Resolved by P2017R1.Proposed resolution: