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/2] 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/2] 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;