diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 818447a..13b5f95 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace beman::span { @@ -315,6 +316,33 @@ 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 7137c21..8c23384 100644 --- a/tests/beman/span/span.test.cpp +++ b/tests/beman/span/span.test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -549,6 +550,58 @@ 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 {}; +} // namespace tuple_test_detail + +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) // ---------------------------------------------------------------------------