From 59689f0feb75fe365f0617b6394d96c417548ce1 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Sun, 26 Apr 2026 13:58:50 -0500 Subject: [PATCH 1/3] Tuple protocol for fixed-size span (P3786R2) Adds the three pieces from P3786R2: - std::tuple_size> - integral_constant - std::tuple_element> - using type = T& - get(span) returning T&, in beman::span so ADL finds it for structured bindings All three are gated on N != dynamic_extent via constrained partial specialization, so std::tuple_size> falls back to the undefined primary template and remains SFINAE-friendly for the tuple-like concept. Top-level cv on the span is handled automatically by the existing std::tuple_size / std::tuple_element partial specs. Six new tests cover tuple_size value, tuple_element returning references (including const-element-type and top-level-const cases), get returning a reference to the underlying element, structured bindings binding by reference, and the dynamic_extent SFINAE behaviour. Fixes #4 --- include/beman/span/span.hpp | 30 ++++++++++++++++++ tests/beman/span/span.test.cpp | 58 ++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index f211b8c..9344619 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace beman::span { @@ -323,6 +324,35 @@ auto as_writable_bytes(span s) noexcept return return_type{reinterpret_cast(s.data()), s.size_bytes()}; } +// [span.tuple] Tuple interface for fixed-size span (P3786R2). +// Lives in beman::span so ADL picks it up for structured bindings on span. +template +constexpr ElementType& get(span s) noexcept { + static_assert(Extent != dynamic_extent, + "beman::span::get requires a fixed-extent span"); + static_assert(I < Extent, "beman::span::get: index out of range"); + return s[I]; +} + } // namespace beman::span +// std::tuple_size / std::tuple_element specializations for fixed-size span (P3786R2). +// dynamic_extent is excluded via constrained partial specialization, so +// std::tuple_size> falls back to the (undefined) primary template - +// SFINAE-friendly for the tuple-like concept. +namespace std { + +template + requires(Extent != ::beman::span::dynamic_extent) +struct tuple_size<::beman::span::span> + : integral_constant {}; + +template + requires(Extent != ::beman::span::dynamic_extent && I < Extent) +struct tuple_element> { + using type = ElementType&; +}; + +} // namespace std + #endif // BEMAN_SPAN_SPAN_HPP diff --git a/tests/beman/span/span.test.cpp b/tests/beman/span/span.test.cpp index 1ca77d0..c8b379e 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; @@ -411,6 +413,62 @@ TEST(SpanConstexpr, size_and_access) { EXPECT_EQ(s.size(), 3u); } +namespace tuple_test_detail { +template +struct has_tuple_size : std::false_type {}; + +template +struct has_tuple_size::value)>> + : std::true_type {}; +} + +TEST(SpanTuple, tuple_size_value) { + static_assert(std::tuple_size_v> == 3); + static_assert(std::tuple_size_v> == 5); + static_assert(std::tuple_size_v> == 0); +} + +TEST(SpanTuple, tuple_size_top_level_const_ignored) { + static_assert(std::tuple_size_v> == 3); +} + +TEST(SpanTuple, tuple_element_yields_reference) { + static_assert(std::is_same_v>, int&>); + static_assert(std::is_same_v>, + const double&>); + static_assert(std::is_same_v>, int&>); +} + +TEST(SpanTuple, get_returns_reference_to_underlying_element) { + int arr[] = {10, 20, 30}; + bsp::span s(arr); + + static_assert(std::is_same_v(s)), int&>); + EXPECT_EQ(get<0>(s), 10); + EXPECT_EQ(get<2>(s), 30); + + get<1>(s) = 99; + EXPECT_EQ(arr[1], 99); +} + +TEST(SpanTuple, structured_binding) { + int arr[] = {1, 2, 3}; + bsp::span s(arr); + + auto& [a, b, c] = s; + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2); + EXPECT_EQ(c, 3); + + a = 100; + EXPECT_EQ(arr[0], 100); +} + +TEST(SpanTuple, dynamic_extent_excluded_from_tuple_protocol) { + static_assert(tuple_test_detail::has_tuple_size>::value); + static_assert(!tuple_test_detail::has_tuple_size>::value); +} + // --------------------------------------------------------------------------- // Comparison with std::span (when available) // --------------------------------------------------------------------------- From 08f967ab2f248d2a405816a02ac3fa233500de92 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Fri, 1 May 2026 09:05:48 -0500 Subject: [PATCH 2/3] Apply clang-format after merging main Pure formatter output. Reflows the new tuple_size / tuple_element / get specializations and tightens up the SpanTuple tests under the .clang-format AlignConsecutiveDeclarations and ColumnLimit rules. Also picks up the at() test alignment carried in via the merge. --- include/beman/span/span.hpp | 6 ++---- tests/beman/span/span.test.cpp | 28 ++++++++++++---------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 56ff0bb..d58b812 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -305,8 +305,7 @@ auto as_writable_bytes(span s) noexcept // Lives in beman::span so ADL picks it up for structured bindings on span. template constexpr ElementType& get(span s) noexcept { - static_assert(Extent != dynamic_extent, - "beman::span::get requires a fixed-extent span"); + static_assert(Extent != dynamic_extent, "beman::span::get requires a fixed-extent span"); static_assert(I < Extent, "beman::span::get: index out of range"); return s[I]; } @@ -321,8 +320,7 @@ namespace std { template requires(Extent != ::beman::span::dynamic_extent) -struct tuple_size<::beman::span::span> - : integral_constant {}; +struct tuple_size<::beman::span::span> : integral_constant {}; template requires(Extent != ::beman::span::dynamic_extent && I < Extent) diff --git a/tests/beman/span/span.test.cpp b/tests/beman/span/span.test.cpp index 74368de..68666ef 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); @@ -465,9 +465,8 @@ template struct has_tuple_size : std::false_type {}; template -struct has_tuple_size::value)>> - : std::true_type {}; -} +struct has_tuple_size::value)>> : std::true_type {}; +} // namespace tuple_test_detail TEST(SpanTuple, tuple_size_value) { static_assert(std::tuple_size_v> == 3); @@ -475,19 +474,16 @@ TEST(SpanTuple, tuple_size_value) { static_assert(std::tuple_size_v> == 0); } -TEST(SpanTuple, tuple_size_top_level_const_ignored) { - static_assert(std::tuple_size_v> == 3); -} +TEST(SpanTuple, tuple_size_top_level_const_ignored) { static_assert(std::tuple_size_v> == 3); } TEST(SpanTuple, tuple_element_yields_reference) { static_assert(std::is_same_v>, int&>); - static_assert(std::is_same_v>, - const double&>); + static_assert(std::is_same_v>, const double&>); static_assert(std::is_same_v>, int&>); } TEST(SpanTuple, get_returns_reference_to_underlying_element) { - int arr[] = {10, 20, 30}; + int arr[] = {10, 20, 30}; bsp::span s(arr); static_assert(std::is_same_v(s)), int&>); @@ -499,7 +495,7 @@ TEST(SpanTuple, get_returns_reference_to_underlying_element) { } TEST(SpanTuple, structured_binding) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); auto& [a, b, c] = s; From d25df508c6453702dd86b7a3f0bf78804792abd2 Mon Sep 17 00:00:00 2001 From: SamareshSingh Date: Wed, 12 Aug 2026 11:22:40 -0500 Subject: [PATCH 3/3] Fix -Wreorder: list size_holder base before data_ member The compressed_size base is always initialized before the data_ member regardless of how the mem-initializer list is written, so listing data_ first made every constructor trip -Wreorder / -Wreorder-ctor. This is a hard error under the Debug.Werror CI jobs. Reorder all nine constructor initializer lists to match the actual initialization order. No behavioral change: no initializer reads the other subobject. --- include/beman/span/span.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 75cffa4..c4e5a92 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -105,10 +105,10 @@ class span : public detail::compressed_size { // Default constructor: only valid when Extent == 0 or Extent == dynamic_extent template = 0> - constexpr span() noexcept : data_(nullptr), size_holder(0) {} + constexpr span() noexcept : size_holder(0), data_(nullptr) {} // Pointer + count constructor. - constexpr explicit(Extent != dynamic_extent) span(pointer ptr, size_type count) : data_(ptr), size_holder(count) { + constexpr explicit(Extent != dynamic_extent) span(pointer ptr, size_type count) : size_holder(count), data_(ptr) { if constexpr (Extent != dynamic_extent) { assert(count == Extent); } @@ -116,7 +116,7 @@ class span : public detail::compressed_size { // Pointer pair constructor. constexpr explicit(Extent != dynamic_extent) span(pointer first, pointer last) - : data_(first), size_holder(static_cast(last - first)) { + : size_holder(static_cast(last - first)), data_(first) { if constexpr (Extent != dynamic_extent) { assert(static_cast(last - first) == Extent); } @@ -131,21 +131,21 @@ class span : public detail::compressed_size { ElementType (*)[]>, int> = 0> // NOLINTNEXTLINE(modernize-avoid-c-arrays) - constexpr span(ElementType (&arr)[N]) noexcept : data_(arr), size_holder(N) {} + constexpr span(ElementType (&arr)[N]) noexcept : size_holder(N), data_(arr) {} // std::array constructor (fixed-size) template = 0, std::enable_if_t, int> = 0> - constexpr span(std::array& arr) noexcept : data_(arr.data()), size_holder(N) {} + constexpr span(std::array& arr) noexcept : size_holder(N), data_(arr.data()) {} // const std::array constructor template = 0, std::enable_if_t, int> = 0> - constexpr span(const std::array& arr) noexcept : data_(arr.data()), size_holder(N) {} + constexpr span(const std::array& arr) noexcept : size_holder(N), data_(arr.data()) {} // Range constructor (generic contiguous range) template { std::enable_if_t || std::is_const_v, int> = 0, std::enable_if_t, int> = 0> constexpr explicit(Extent != dynamic_extent) span(Range&& r) - : data_(std::ranges::data(r)), size_holder(std::ranges::size(r)) { + : size_holder(std::ranges::size(r)), data_(std::ranges::data(r)) { if constexpr (Extent != dynamic_extent) { if constexpr (requires { std::integral_constant{}; }) { static_assert(std::ranges::size(r) == Extent, @@ -178,7 +178,7 @@ class span : public detail::compressed_size { std::enable_if_t, int> = 0, std::enable_if_t>, int> = 0> constexpr explicit(Extent != dynamic_extent) span(std::initializer_list il) - : data_(il.begin()), size_holder(il.size()) { + : size_holder(il.size()), data_(il.begin()) { if constexpr (Extent != dynamic_extent) { assert(il.size() == Extent); } @@ -192,7 +192,7 @@ class span : public detail::compressed_size { int> = 0> constexpr explicit(Extent != dynamic_extent && OtherExtent == dynamic_extent) span(const span& s) noexcept - : data_(s.data()), size_holder(s.size()) { + : size_holder(s.size()), data_(s.data()) { if constexpr (Extent != dynamic_extent) { assert(s.size() == Extent); }