From 5640585b6a06023011f1f3253f6a77a195c7a864 Mon Sep 17 00:00:00 2001 From: luka Date: Fri, 25 Sep 2026 17:07:13 +0800 Subject: [PATCH 1/3] Implement LWG-4249 The past end issue for `lazy_split_view` `lazy_split_view::outer-iterator::operator++()` now sets `trailing_empty_` for non-forward views when it lands past a delimiter before the end. Otherwise, iterating the inner range exhausts the shared base range and leaves the outer iterator comparing equal to `default_sentinel`, so a consumer incrementing it afterwards (e.g. `views::stride`, or a range-for's increment clause) would increment past the end, tripping hardening checks. Test coverage: `test_lwg_4249()` in P0896R4_views_lazy_split reproduces the issue example with `views::istream` and `views::stride`. --- stl/inc/ranges | 4 +++ .../tests/P0896R4_views_lazy_split/test.cpp | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/stl/inc/ranges b/stl/inc/ranges index 8317ea26049..7193a9347f9 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -3767,6 +3767,10 @@ namespace ranges { ++_Cur; if (_Cur == _End) { _Trailing_empty = true; + } else if constexpr (!forward_range<_Vw>) { + // LWG-4249: inner iteration can exhaust the base range before the sentinel comparison, + // so mark as trailing empty instead of appearing to be at the sentinel. + _Trailing_empty = true; } } } else { diff --git a/tests/std/tests/P0896R4_views_lazy_split/test.cpp b/tests/std/tests/P0896R4_views_lazy_split/test.cpp index 09b77dff2d8..4ce615c5383 100644 --- a/tests/std/tests/P0896R4_views_lazy_split/test.cpp +++ b/tests/std/tests/P0896R4_views_lazy_split/test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -383,6 +384,31 @@ void test_lwg_4027() { // COMPILE-ONLY static_assert(is_const_v>); } +void test_lwg_4249() { + // Iterating an inner range exhausts the shared base range, so the outer iterator can only be incremented + // once more before it reaches the end; it must not compare equal to default_sentinel in between. + istringstream is{"1 0 2 0 3"}; + auto r = views::istream(is) | views::lazy_split(0) | views::stride(2); + + auto i = r.begin(); + assert(i != r.end()); + assert(ranges::equal(*i, views::single(1))); + ++i; + assert(i != r.end()); + assert(ranges::equal(*i, views::single(3))); + ++i; + assert(i == r.end()); + + istringstream is2{"1 0 2"}; + auto plain = views::istream(is2) | views::lazy_split(0); + auto j = plain.begin(); + assert(ranges::equal(*j, views::single(1))); + ++j; + assert(ranges::equal(*j, views::single(2))); + ++j; + assert(j == plain.end()); +} + int main() { static_assert(instantiation_test()); instantiation_test(); @@ -392,4 +418,6 @@ int main() { static_assert(test_lwg_3904()); assert(test_lwg_3904()); + + test_lwg_4249(); } From 1e2fcc1a9891c1dbccb2d106c7fedb66debff434 Mon Sep 17 00:00:00 2001 From: luka Date: Sat, 26 Sep 2026 01:14:44 +0800 Subject: [PATCH 2/3] Guard test_lwg_4249 with __cpp_lib_ranges_stride P0896R4_views_lazy_split runs the strict C++20 matrix, in which views::stride (C++23) is not available. Compile the stride-based LWG-4249 test only when the feature is provided. --- tests/std/tests/P0896R4_views_lazy_split/test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/std/tests/P0896R4_views_lazy_split/test.cpp b/tests/std/tests/P0896R4_views_lazy_split/test.cpp index 4ce615c5383..1baed5ac34f 100644 --- a/tests/std/tests/P0896R4_views_lazy_split/test.cpp +++ b/tests/std/tests/P0896R4_views_lazy_split/test.cpp @@ -384,6 +384,7 @@ void test_lwg_4027() { // COMPILE-ONLY static_assert(is_const_v>); } +#ifdef __cpp_lib_ranges_stride void test_lwg_4249() { // Iterating an inner range exhausts the shared base range, so the outer iterator can only be incremented // once more before it reaches the end; it must not compare equal to default_sentinel in between. @@ -408,6 +409,7 @@ void test_lwg_4249() { ++j; assert(j == plain.end()); } +#endif // __cpp_lib_ranges_stride int main() { static_assert(instantiation_test()); @@ -419,5 +421,7 @@ int main() { static_assert(test_lwg_3904()); assert(test_lwg_3904()); +#ifdef __cpp_lib_ranges_stride test_lwg_4249(); +#endif // __cpp_lib_ranges_stride } From 3663a72b436994794e2809a7a0acfb8086ec904f Mon Sep 17 00:00:00 2001 From: luka Date: Sat, 26 Sep 2026 19:58:56 +0800 Subject: [PATCH 3/3] Rework `test_lwg_4249` per PR review Drop the `views::stride` dependency and the `__cpp_lib_ranges_stride` guard so the test provides coverage in every configuration, including the strict C++20 ones. Assert `j != plain.end()` before and after iterating over `*j`, which is the exact observable that LWG-4249 changes. --- .../tests/P0896R4_views_lazy_split/test.cpp | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/tests/std/tests/P0896R4_views_lazy_split/test.cpp b/tests/std/tests/P0896R4_views_lazy_split/test.cpp index 1baed5ac34f..f85823733b5 100644 --- a/tests/std/tests/P0896R4_views_lazy_split/test.cpp +++ b/tests/std/tests/P0896R4_views_lazy_split/test.cpp @@ -384,32 +384,23 @@ void test_lwg_4027() { // COMPILE-ONLY static_assert(is_const_v>); } -#ifdef __cpp_lib_ranges_stride void test_lwg_4249() { - // Iterating an inner range exhausts the shared base range, so the outer iterator can only be incremented - // once more before it reaches the end; it must not compare equal to default_sentinel in between. - istringstream is{"1 0 2 0 3"}; - auto r = views::istream(is) | views::lazy_split(0) | views::stride(2); - - auto i = r.begin(); - assert(i != r.end()); - assert(ranges::equal(*i, views::single(1))); - ++i; - assert(i != r.end()); - assert(ranges::equal(*i, views::single(3))); - ++i; - assert(i == r.end()); - - istringstream is2{"1 0 2"}; - auto plain = views::istream(is2) | views::lazy_split(0); + // Iterating over an inner range advances the shared current, so the outer iterator can only be + // incremented once more before reaching the end; it must not compare equal to default_sentinel + // in between. + istringstream is{"1 0 2"}; + auto plain = views::istream(is) | views::lazy_split(0); auto j = plain.begin(); + assert(j != plain.end()); assert(ranges::equal(*j, views::single(1))); + assert(j != plain.end()); ++j; + assert(j != plain.end()); assert(ranges::equal(*j, views::single(2))); + assert(j != plain.end()); ++j; assert(j == plain.end()); } -#endif // __cpp_lib_ranges_stride int main() { static_assert(instantiation_test()); @@ -421,7 +412,5 @@ int main() { static_assert(test_lwg_3904()); assert(test_lwg_3904()); -#ifdef __cpp_lib_ranges_stride test_lwg_4249(); -#endif // __cpp_lib_ranges_stride }