C++20 introduced the ranges::borrowed_range
, which defines requirements of a range such that a function can take it by value and return iterators obtained from it without danger of dangling. In short (which
refer to P2017R1):
A range is a borrowed_range when you can hold onto its iterators after the range goes out of scope.
At the same time, an type helper borrowed_subrange_t
have also been introduced:
template<ranges::range R>
using borrowed_subrange_t = std::conditional_t<
ranges::borrowed_range<R>,
ranges::subrange<ranges::iterator_t<R>>,
ranges::dangling
>;
which is an alias template that is used by some constrained algorithms such as ranges::unique
and ranges::find_end
to avoid returning potentially dangling iterators or views.
When type R
models borrowed_range
, the borrowed_subrange_t
of R
is basically a subrange<ranges::iterator_t<R>>
,
which means it also a ranges::common_range
, since it only takes one template argument and the second defaults to be the same type as the first one.
But there seems to be some misleading, since there are some subrange
types that can be borrowed but still a not common_range
, consider the following code:
auto r = views::iota(0);
auto s1 = ranges::subrange{r.begin(), r.begin() + 5};
auto s2 = ranges::subrange{r.begin() + 5, r.end()};
I create two subrange
s from a borrowed_range
ranges::iota_view
, one contains the first 5 elements, and the other contains all the elements of itoa_view
starting from the fifth element. They are subrange
s of itoa_view
, and they are obviously, borrowed:
static_assert(ranges::borrowed_range<decltype(s1)>);
static_assert(ranges::borrowed_range<decltype(s2)>);
So to some extent, both of their types can be regarded as the borrowed_subrange_t
of the itoa_view
type, but according to the definition, only the type of s1
is borrowed_subrange_t
of the type r
, which also means that the following code is ill-formed since the iota_view
r
is not an common_range
:
auto bsr = ranges::borrowed_subrange_t<decltype(r)>{r}; // ill-formed
Why does the standard need to ensure that borrowed_subrange_t
of some range
R
is a common_range
, that is, the return type of begin()
and end()
are the same? What is the reason behind this? Why not define it more generally like:
template <ranges::range R>
using borrowed_subrange_t = std::conditional_t<
ranges::borrowed_range<R>,
ranges::subrange<
ranges::iterator_t<R>,
std::common_iterator<
ranges::iterator_t<R>,
ranges::sentinel_t<R>
>
>,
ranges::dangling
>;
Will there be any potential defects and dangers in doing so?