Title
Behavior of std::views::split on an empty range
Status
new
Section
[range.split.iterator][range.lazy.split.outer]
Submitter
David Stone

Created on 2023-11-19.00:00:00 last changed 1 month ago

Messages

Date: 2024-03-11.22:16:42

Proposed resolution:

This wording is relative to N4964.

  1. Modify [range.split.iterator] as indicated:

    constexpr iterator(split_view& parent, iterator_t<V> current, subrange<iterator_t<V>> next);
    

    -1- Effects: Initializes parent_ with addressof(parent), cur_ with std::move(current), and next_ with std::move(next), and trailing_empty_ with cur_ == next_.begin().

  2. Modify [range.lazy.split.outer] as indicated:

    constexpr outer-iterator(Parent& parent, iterator_t<Base> current)
      requires forward_range<Base>;
    

    -3- Effects: Initializes parent_ with addressof(parent), and current_ with std::move(current), and trailing_empty_ with current_ == ranges::end(parent.base_).

Date: 2024-03-15.00:00:00

[ 2024-03-11; Reflector poll ]

Set priority to 3 after reflector poll.

Date: 2023-11-25.16:26:29

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 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.

However, the principle is still broken if the sequence contains zero delimiters. A non-empty sequence will split into one range, but an empty sequence will split into zero ranges. That last line is incorrect — splitting an empty range on a delimiter should yield a range of an empty range — not simply an empty range.

Proposed Resolution: Currently, split_view::iterator's constructor unconditionally initializes trailing_empty_ to false. Instead, change [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

History
Date User Action Args
2024-03-11 22:16:42adminsetmessages: + msg13989
2023-11-25 13:20:33adminsetmessages: + msg13874
2023-11-19 00:00:00admincreate