From fb534412208096bd9c479cff0f7629390d37b148 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Sun, 26 Apr 2026 14:04:39 -0500 Subject: [PATCH 1/2] Add remove_prefix and remove_suffix (P3729) Adds the two mutating shrink operations to dynamic-extent span: - remove_prefix(n): drops the first n elements - remove_suffix(n): drops the last n elements Both are gated to dynamic_extent only via the same SFINAE pattern the default constructor uses. Fixed-extent spans carry their size in the type, so an in-place size mutation isn't representable - matching string_view (no fixed extent) and Abseil's Span (dynamic only). Eight new tests cover n=0, partial shrink, full shrink to empty, both operations combined, and a detection-idiom check confirming fixed-extent spans do not expose these methods. Fixes #3 --- include/beman/span/span.hpp | 13 +++++ tests/beman/span/span.test.cpp | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index f211b8c..511f88a 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -229,6 +229,19 @@ class span { return {data_ + offset, count == dynamic_extent ? size() - offset : count}; } + template = 0> + constexpr void remove_prefix(size_type n) noexcept { + assert(n <= size()); + data_ += n; + size_ -= n; + } + + template = 0> + constexpr void remove_suffix(size_type n) noexcept { + assert(n <= size()); + size_ -= n; + } + // 26.7.3.4 Observers [span.obs] [[nodiscard]] constexpr size_type size() const noexcept { return size_; } diff --git a/tests/beman/span/span.test.cpp b/tests/beman/span/span.test.cpp index 1ca77d0..8f8408a 100644 --- a/tests/beman/span/span.test.cpp +++ b/tests/beman/span/span.test.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include namespace bsp = beman::span; @@ -366,6 +368,99 @@ TEST(SpanSubviews, subspan_dynamic_to_end) { EXPECT_EQ(sub[1], 4); } +// --------------------------------------------------------------------------- +// remove_prefix / remove_suffix (P3729) +// --------------------------------------------------------------------------- + +TEST(SpanModifiers, remove_prefix_zero_is_noop) { + int arr[] = {1, 2, 3, 4, 5}; + bsp::span s(arr); + s.remove_prefix(0); + EXPECT_EQ(s.size(), 5u); + EXPECT_EQ(s.data(), arr); +} + +TEST(SpanModifiers, remove_prefix_partial) { + int arr[] = {1, 2, 3, 4, 5}; + bsp::span s(arr); + s.remove_prefix(2); + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s.data(), arr + 2); + EXPECT_EQ(s[0], 3); + EXPECT_EQ(s[2], 5); +} + +TEST(SpanModifiers, remove_prefix_full_empties_span) { + int arr[] = {1, 2, 3}; + bsp::span s(arr); + s.remove_prefix(s.size()); + EXPECT_EQ(s.size(), 0u); + EXPECT_TRUE(s.empty()); +} + +TEST(SpanModifiers, remove_suffix_zero_is_noop) { + int arr[] = {1, 2, 3, 4, 5}; + bsp::span s(arr); + s.remove_suffix(0); + EXPECT_EQ(s.size(), 5u); + EXPECT_EQ(s.data(), arr); +} + +TEST(SpanModifiers, remove_suffix_partial) { + int arr[] = {1, 2, 3, 4, 5}; + bsp::span s(arr); + s.remove_suffix(2); + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s.data(), arr); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); +} + +TEST(SpanModifiers, remove_suffix_full_empties_span) { + int arr[] = {1, 2, 3}; + bsp::span s(arr); + s.remove_suffix(s.size()); + EXPECT_EQ(s.size(), 0u); + EXPECT_TRUE(s.empty()); +} + +TEST(SpanModifiers, remove_prefix_and_suffix_combined) { + int arr[] = {10, 20, 30, 40, 50, 60}; + bsp::span s(arr); + s.remove_prefix(1); + s.remove_suffix(2); + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s[0], 20); + EXPECT_EQ(s[2], 40); +} + +namespace modifiers_detail { +template +struct has_remove_prefix : std::false_type {}; + +template +struct has_remove_prefix< + T, + std::void_t().remove_prefix(std::size_t{0}))>> + : std::true_type {}; + +template +struct has_remove_suffix : std::false_type {}; + +template +struct has_remove_suffix< + T, + std::void_t().remove_suffix(std::size_t{0}))>> + : std::true_type {}; +} // namespace modifiers_detail + +TEST(SpanModifiers, fixed_extent_has_no_modifiers) { + static_assert(modifiers_detail::has_remove_prefix>::value); + static_assert(modifiers_detail::has_remove_suffix>::value); + static_assert(!modifiers_detail::has_remove_prefix>::value); + static_assert(!modifiers_detail::has_remove_suffix>::value); +} + // --------------------------------------------------------------------------- // as_bytes / as_writable_bytes // --------------------------------------------------------------------------- From cd59340379e506c34944e356730c323f79185309 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Fri, 1 May 2026 08:25:26 -0500 Subject: [PATCH 2/2] Use C++20 requires clauses for remove_prefix/remove_suffix Addresses Jeff's review feedback on PR #8: Beman targets C++20 as baseline, so the new modifiers should use a requires clause rather than the SFINAE-style enable_if_t shim. Behavior is identical (the existing void_t-based detection tests still pass), just clearer at the point of declaration. Also picks up the clang-format pass over the at() tests carried in via the merge of main. --- include/beman/span/span.hpp | 10 +++++---- tests/beman/span/span.test.cpp | 38 +++++++++++++++------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 52f31f3..818447a 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -210,15 +210,17 @@ class span { return {data_ + offset, count == dynamic_extent ? size() - offset : count}; } - template = 0> - constexpr void remove_prefix(size_type n) noexcept { + constexpr void remove_prefix(size_type n) noexcept + requires(Extent == dynamic_extent) + { assert(n <= size()); data_ += n; size_ -= n; } - template = 0> - constexpr void remove_suffix(size_type n) noexcept { + constexpr void remove_suffix(size_type n) noexcept + requires(Extent == dynamic_extent) + { assert(n <= size()); size_ -= n; } diff --git a/tests/beman/span/span.test.cpp b/tests/beman/span/span.test.cpp index 5e39293..7137c21 100644 --- a/tests/beman/span/span.test.cpp +++ b/tests/beman/span/span.test.cpp @@ -205,7 +205,7 @@ TEST(SpanElementAccess, write_through_span) { // at() bounds-checked access (P2821R5, C++26) TEST(SpanAt, in_bounds_returns_element) { - int arr[] = {10, 20, 30}; + int arr[] = {10, 20, 30}; bsp::span s(arr); EXPECT_EQ(s.at(0), 10); EXPECT_EQ(s.at(1), 20); @@ -213,7 +213,7 @@ TEST(SpanAt, in_bounds_returns_element) { } TEST(SpanAt, returns_lvalue_reference_for_mutable_span) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); s.at(1) = 42; EXPECT_EQ(arr[1], 42); @@ -221,13 +221,13 @@ TEST(SpanAt, returns_lvalue_reference_for_mutable_span) { } TEST(SpanAt, throws_out_of_range_at_size_boundary) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_THROW(s.at(3), std::out_of_range); } TEST(SpanAt, throws_out_of_range_well_past_size) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_THROW(s.at(100), std::out_of_range); } @@ -238,14 +238,14 @@ TEST(SpanAt, empty_span_always_throws) { } TEST(SpanAt, fixed_extent_in_bounds_and_out_of_range) { - int arr[] = {7, 8, 9, 10}; + int arr[] = {7, 8, 9, 10}; bsp::span s(arr); EXPECT_EQ(s.at(3), 10); EXPECT_THROW(s.at(4), std::out_of_range); } TEST(SpanAt, const_span_returns_reference_to_const) { - const int arr[] = {1, 2, 3}; + const int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_EQ(s.at(2), 3); static_assert(std::is_same_v); @@ -420,7 +420,7 @@ TEST(SpanSubviews, subspan_dynamic_to_end) { // --------------------------------------------------------------------------- TEST(SpanModifiers, remove_prefix_zero_is_noop) { - int arr[] = {1, 2, 3, 4, 5}; + int arr[] = {1, 2, 3, 4, 5}; bsp::span s(arr); s.remove_prefix(0); EXPECT_EQ(s.size(), 5u); @@ -428,7 +428,7 @@ TEST(SpanModifiers, remove_prefix_zero_is_noop) { } TEST(SpanModifiers, remove_prefix_partial) { - int arr[] = {1, 2, 3, 4, 5}; + int arr[] = {1, 2, 3, 4, 5}; bsp::span s(arr); s.remove_prefix(2); EXPECT_EQ(s.size(), 3u); @@ -438,7 +438,7 @@ TEST(SpanModifiers, remove_prefix_partial) { } TEST(SpanModifiers, remove_prefix_full_empties_span) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); s.remove_prefix(s.size()); EXPECT_EQ(s.size(), 0u); @@ -446,7 +446,7 @@ TEST(SpanModifiers, remove_prefix_full_empties_span) { } TEST(SpanModifiers, remove_suffix_zero_is_noop) { - int arr[] = {1, 2, 3, 4, 5}; + int arr[] = {1, 2, 3, 4, 5}; bsp::span s(arr); s.remove_suffix(0); EXPECT_EQ(s.size(), 5u); @@ -454,7 +454,7 @@ TEST(SpanModifiers, remove_suffix_zero_is_noop) { } TEST(SpanModifiers, remove_suffix_partial) { - int arr[] = {1, 2, 3, 4, 5}; + int arr[] = {1, 2, 3, 4, 5}; bsp::span s(arr); s.remove_suffix(2); EXPECT_EQ(s.size(), 3u); @@ -464,7 +464,7 @@ TEST(SpanModifiers, remove_suffix_partial) { } TEST(SpanModifiers, remove_suffix_full_empties_span) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); s.remove_suffix(s.size()); EXPECT_EQ(s.size(), 0u); @@ -472,7 +472,7 @@ TEST(SpanModifiers, remove_suffix_full_empties_span) { } TEST(SpanModifiers, remove_prefix_and_suffix_combined) { - int arr[] = {10, 20, 30, 40, 50, 60}; + int arr[] = {10, 20, 30, 40, 50, 60}; bsp::span s(arr); s.remove_prefix(1); s.remove_suffix(2); @@ -486,19 +486,15 @@ template struct has_remove_prefix : std::false_type {}; template -struct has_remove_prefix< - T, - std::void_t().remove_prefix(std::size_t{0}))>> - : std::true_type {}; +struct has_remove_prefix().remove_prefix(std::size_t{0}))>> : std::true_type { +}; template struct has_remove_suffix : std::false_type {}; template -struct has_remove_suffix< - T, - std::void_t().remove_suffix(std::size_t{0}))>> - : std::true_type {}; +struct has_remove_suffix().remove_suffix(std::size_t{0}))>> : std::true_type { +}; } // namespace modifiers_detail TEST(SpanModifiers, fixed_extent_has_no_modifiers) {