diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 823c398..c4e5a92 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace beman::span { @@ -104,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); } @@ -115,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); } @@ -130,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, @@ -177,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); } @@ -191,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); } @@ -357,6 +358,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 3e54341..dedb574 100644 --- a/tests/beman/span/span.test.cpp +++ b/tests/beman/span/span.test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -657,6 +658,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) // ---------------------------------------------------------------------------