This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
std::views::split
on an empty rangeSection: 25.7.17.3 [range.split.iterator], 25.7.16.3 [range.lazy.split.outer] Status: New Submitter: David Stone Opened: 2023-11-19 Last modified: 2024-06-24
Priority: 3
View all issues with New status.
Discussion:
Consider the following example (which uses fmt::println
instead of std::println
,
but they do the same thing in C++23):
#include <iostream> #include <string> #include <ranges> #include <fmt/ranges.h> int main() { fmt::println("{}", std::views::split(std::string(" x "), ' ')); fmt::println("{}", std::views::split(std::string(" "), ' ')); fmt::println("{}", std::views::split(std::string("x"), ' ')); fmt::println("{}", std::views::split(std::string(""), ' ')); }
The output of this program (as specified today) is
[[], ['x'], []] [[], []] [['x']] []
The principle set out in LWG 3478(i) is that splitting a sequence containing N
delimiters should lead to N+1
subranges. That principle was broken if the N
-th
delimiter was at the end of the sequence, which was fixed by P2210.
split_view::iterator
's constructor unconditionally initializes
trailing_empty_
to false
. Instead, change 25.7.17.3 [range.split.iterator]/1
to initialize trailing_empty_
to cur_ == next_.begin()
(i.e.
trailing_empty_
is typically false
, but if we're empty on initialization then we
have to have a trailing empty range).
The following demo shows Barry Revzin's implementation from P2210, adjusted to fix this:
godbolt.org/z/axWb64j9f
[2024-03-11; Reflector poll]
Set priority to 3 after reflector poll.
[2024-03; Reflector comments]
"For split
, we need to adjust the definition of end()
for the
common_range
case
(which may require introducing a new constructor to the iterator);
right now it would compare ranges::end(base_)
against a value-initialized
iterator, which is not in the domain of ==
.
For lazy_split
, we need to also change the non-forward overload."
"What should splitting an empty range on an empty pattern produce? Right now the behavior is that splitting a range of N > 0 elements with an empty pattern produces a range of N single-element ranges. I suppose you can argue that an empty pattern matches between adjacent elements but not at the start or end, so that an empty range, like a single-element range, contains 0 delimiters so should produce a range of one empty range. But it's also at least arguable that this should produce an empty range instead, so that we maintain the N element <-> N subrange and 1 element per subrange invariant.
Proposed resolution:
This wording is relative to N4964.
Modify 25.7.17.3 [range.split.iterator] as indicated:
constexpr iterator(split_view& parent, iterator_t<V> current, subrange<iterator_t<V>> next);-1- Effects: Initializes
parent_
withaddressof(parent)
,cur_
withstd::move(current)
,andnext_
withstd::move(next)
, andtrailing_empty_
withcur_ == next_.begin()
.
Modify 25.7.16.3 [range.lazy.split.outer] as indicated:
constexpr outer-iterator(Parent& parent, iterator_t<Base> current) requires forward_range<Base>;-3- Effects: Initializes
parent_
withaddressof(parent)
,andcurrent_
withstd::move(current)
, andtrailing_empty_
withcurrent_ == ranges::end(parent.base_)
.