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.
lazy_split_view
Section: 25.7.16.3 [range.lazy.split.outer] Status: New Submitter: Hewill Kang Opened: 2025-04-26 Last modified: 2025-04-27
Priority: Not Prioritized
View other active issues in [range.lazy.split.outer].
View all other issues in [range.lazy.split.outer].
View all issues with New status.
Discussion:
Consider (demo):
#include <print> #include <ranges> #include <sstream> int main() { std::istringstream is{"1 0 2 0 3"}; auto r = std::views::istream<int>(is) | std::views::lazy_split(0) | std::views::stride(2); std::println("{}", r); // should print [[1], [3]] }
The above leads to SIGSEGV in libstdc++, the reason is that we are iterating over the nested range as:
for (auto&& inner : r) { for (auto&& elem : inner) { // […] } }
which is disassembled as:
auto outer_it = r.begin(); std::default_sentinel_t out_end = r.end(); for(; outer_it != out_end; ++outer_it) { auto&& inner_r = *outer_it; auto inner_it = inner_r.begin(); std::default_sentinel_t inner_end = inner_r.end(); for(; inner_it != inner_end; ++inner_it) { auto&& elem = *inner_it; // […] } }
Since inner_it
and output_it
actually update the same iterator,
when we back to the outer loop, lazy_split_view::outer-iterator
is now equal to default_sentinel
, which makes output_it
reach the end,
so ++outer_it
will increment the iterator past end, triggering the assertion.
Note that this also happens in MSVC-STL
when _ITERATOR_DEBUG_LEVEL
is turned on.
It seems that extra flags are needed to fix this issue because output_it
should not
be considered to reach the end when we back to the outer loop.
Proposed resolution:
This wording is relative to N5008.
Modify 25.7.16.3 [range.lazy.split.outer] as indicated:
[…]namespace std::ranges { template<input_range V, forward_range Pattern> requires view<V> && view<Pattern> && indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to> && (forward_range<V> || tiny-range<Pattern>) template<bool Const> struct lazy_split_view<V, Pattern>::outer-iterator { private: using Parent = maybe-const<Const, lazy_split_view>; // exposition only using Base = maybe-const<Const, V>; // exposition only Parent* parent_ = nullptr; // exposition only iterator_t<Base> current_ = iterator_t<Base>(); // exposition only, present only // if V models forward_range bool trailing_empty_ = false; // exposition only bool has_next_ = false; // exposition only, present only // if forward_range<V> is false public: […] }; }constexpr explicit outer-iterator(Parent& parent) requires (!forward_range<Base>);[…]-2- Effects: Initializes
parent_
withaddressof(parent)
andhas_next_
withcurrent != ranges::end(parent_->base_)
.constexpr outer-iterator& operator++();[…]-6- Effects: Equivalent to:
const auto end = ranges::end(parent_->base_); if (current == end) { trailing_empty_ = false; if constexpr (!forward_range<V>) has_next_ = false; return*this
; } const auto [pbegin, pend] = subrange{parent_->pattern_}; if (pbegin == pend) ++current; else if constexpr (tiny-range<Pattern>) { current = ranges::find(std::move(current), end, *pbegin); if (current != end) { ++current; if (current == end) trailing_empty_ = true; } } else { do { auto [b, p] = ranges::mismatch(current, end, pbegin, pend); if (p == pend) { current = b; if (current == end) trailing_empty_ = true; break; // The pattern matched; skip it } } while (++current != end); } if constexpr (!forward_range<V>) if (current == end) has_next_ = false; return *this;friend constexpr bool operator==(const outer-iterator& x, default_sentinel_t);-8- Effects: Equivalent to:
if constexpr (!forward_range<V>) return !x.has_next_ && !x.trailing_empty_; else return x.current == ranges::end(x.parent_->base_) && !x.trailing_empty_;