diff --git a/src/linyaps_box/utils/span.h b/src/linyaps_box/utils/span.h index 6bea3d2..cf69d19 100644 --- a/src/linyaps_box/utils/span.h +++ b/src/linyaps_box/utils/span.h @@ -83,6 +83,47 @@ struct is_span_compatible_iter inline constexpr bool is_span_compatible_iter_v = is_span_compatible_iter::value; +template +struct is_contiguous_iterator : std::false_type +{ +}; + +template +struct is_contiguous_iterator()), + decltype(std::declval() - std::declval()), + decltype(detail::to_address(std::declval()))>> + : std::true_type +{ +}; + +template +inline constexpr bool is_contiguous_iterator_v = is_contiguous_iterator::value; + +template +struct has_data_and_size : std::false_type +{ +}; + +template +struct has_data_and_size< + It, + std::void_t())), decltype(std::size(std::declval()))>> + : std::true_type +{ +}; + +template +inline constexpr bool has_data_and_size_v = has_data_and_size::value; + +template +constexpr auto span_to_address(It &&it) noexcept +{ + static_assert(is_contiguous_iterator_v>, + "span: It must be a contiguous iterator"); + return detail::to_address(std::forward(it)); +} + struct view_base { }; @@ -118,9 +159,10 @@ struct is_compatible_container : std::false_type template struct is_compatible_container().data()), - decltype(std::declval().size())>> - : is_array_convertible().data())>> + std::void_t())), + decltype(std::size(std::declval()))>> + : is_array_convertible()))>> { }; @@ -178,15 +220,6 @@ template class span : public detail::view_base { private: - struct internal_static_construct_tag - { - }; - - constexpr span(T *ptr, [[maybe_unused]] internal_static_construct_tag tag) noexcept - : storage_(ptr, Extent) - { - } - template static constexpr std::size_t subspan_extent() { @@ -202,13 +235,12 @@ class span : public detail::view_base public: using element_type = T; using value_type = std::remove_cv_t; - using pointer = T *; - using const_pointer = const T *; - using reference = T &; - using const_reference = const T &; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - + using pointer = element_type *; + using const_pointer = const element_type *; + using reference = element_type &; + using const_reference = const element_type &; using iterator = T *; using const_iterator = const T *; using reverse_iterator = std::reverse_iterator; @@ -226,119 +258,169 @@ class span : public detail::view_base constexpr span(pointer ptr, size_type count) noexcept : storage_(ptr, count) { + assert((count == 0 || ptr != nullptr) && "span: null pointer with non-zero count"); } template * = nullptr> constexpr explicit span(pointer ptr, size_type count) noexcept : storage_(ptr, count) { - assert(count == E); + assert(count == E && "span: size mismatch for fixed-extent span"); + assert((count == 0 || ptr != nullptr) && "span: null pointer with non-zero count"); } template * = nullptr> constexpr span(pointer first, pointer last) noexcept : storage_(first, static_cast(last - first)) { + assert(first <= last && "span: (first,last) last precedes first"); } template * = nullptr> constexpr explicit span(pointer first, pointer last) noexcept : storage_(first, static_cast(last - first)) { - assert(static_cast(last - first) == E); + assert(first <= last && "span: (first,last) last precedes first"); + assert(static_cast(last - first) == E + && "span: size mismatch for fixed-extent span"); } // (It, count) for dynamic extent — implicit - template < - typename It, - std::size_t E = Extent, - std::enable_if_t> * = nullptr> + template * = nullptr> constexpr span(It first, size_type count) noexcept - : storage_(detail::to_address(first), count) + : storage_(detail::span_to_address(first), count) { + static_assert( + detail::is_array_convertible_v())>>, + "span: iterator value type is not compatible"); + static_assert( + std::is_convertible_v())), T *>, + "span: iterator to_address result is not convertible to span pointer"); + assert((count == 0 || detail::to_address(first) != nullptr) + && "span: null pointer with non-zero count"); } // (It, count) for fixed extent — explicit - template < - typename It, - std::size_t E = Extent, - std::enable_if_t> * = nullptr> + template * = nullptr> constexpr explicit span(It first, size_type count) noexcept - : storage_(detail::to_address(first), count) + : storage_(detail::span_to_address(first), count) { - assert(count == E); + static_assert( + detail::is_array_convertible_v())>>, + "span: iterator value type is not compatible"); + static_assert( + std::is_convertible_v())), T *>, + "span: iterator to_address result is not convertible to span pointer"); + assert(count == E && "span: size mismatch for fixed-extent span"); + assert((count == 0 || detail::to_address(first) != nullptr) + && "span: null pointer with non-zero count"); } // (It, End) for dynamic extent — implicit - template - && detail::is_span_compatible_iter_v> * = nullptr> - constexpr span(It first, End last) noexcept - : storage_(detail::to_address(first), static_cast(last - first)) + template < + typename It, + typename End, + std::size_t E = Extent, + std::enable_if_t> * = nullptr> + constexpr span(It first, End last) noexcept(noexcept(last - first)) + : storage_(detail::span_to_address(first), static_cast(last - first)) { + static_assert( + detail::is_array_convertible_v())>>, + "span: iterator value type is not compatible"); + static_assert( + std::is_convertible_v())), T *>, + "span: iterator to_address result is not convertible to span pointer"); + assert((last - first >= 0) && "span: (It,End) last precedes first"); } // (It, End) for fixed extent — explicit - template - && detail::is_span_compatible_iter_v> * = nullptr> - constexpr explicit span(It first, End last) noexcept - : storage_(detail::to_address(first), static_cast(last - first)) - { - assert(static_cast(last - first) == E); - } - - template > + template < + typename It, + typename End, + std::size_t E = Extent, + std::enable_if_t> * = nullptr> + constexpr explicit span(It first, End last) noexcept(noexcept(last - first)) + : storage_(detail::span_to_address(first), static_cast(last - first)) + { + static_assert( + detail::is_array_convertible_v())>>, + "span: iterator value type is not compatible"); + static_assert( + std::is_convertible_v())), T *>, + "span: iterator to_address result is not convertible to span pointer"); + assert((last - first >= 0) && "span: (It,End) last precedes first"); + assert(static_cast(last - first) == E + && "span: size mismatch for fixed-extent span"); + } + + template constexpr span(detail::type_identity_t (&arr)[N]) noexcept : storage_(arr, N) { + static_assert(Extent == dynamic_extent || Extent == N, + "span: array size does not match fixed extent"); } template >> + typename = std::enable_if_t> constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) { + static_assert(detail::is_array_convertible_v, + "span: array element type is not compatible"); } template >> + typename = std::enable_if_t> constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) { + static_assert(detail::is_array_convertible_v, + "span: array element type is not compatible"); } template , span> && !std::is_array_v> - && detail::is_compatible_container::value + && detail::has_data_and_size_v && (std::is_lvalue_reference_v || detail::is_view_v>) && Extent == dynamic_extent> * = nullptr> constexpr span(Container &&cont) : storage_(std::forward(cont).data(), std::forward(cont).size()) { + static_assert(detail::is_array_convertible_v< + T, + std::remove_pointer_t()))>>, + "span: container element type is not compatible"); } template , span> && !std::is_array_v> - && detail::is_compatible_container::value + && detail::has_data_and_size_v && (std::is_lvalue_reference_v || detail::is_view_v>) && Extent != dynamic_extent> * = nullptr> constexpr explicit span(Container &&cont) : storage_(std::forward(cont).data(), std::forward(cont).size()) { - assert(cont.size() == Extent); + static_assert(detail::is_array_convertible_v< + T, + std::remove_pointer_t()))>>, + "span: container element type is not compatible"); + assert(cont.size() == Extent && "span: container size mismatch for fixed-extent span"); } template &other) noexcept : storage_(other.data(), other.size()) { - assert(other.size() == Extent); + assert(other.size() == Extent && "span: span size mismatch for fixed-extent span"); } ~span() = default; @@ -386,7 +468,7 @@ class span : public detail::view_base [[nodiscard]] constexpr reference operator[](size_type idx) const noexcept { - assert(idx < size()); + assert(idx < size() && "span: index out of range"); return data()[idx]; } @@ -400,13 +482,13 @@ class span : public detail::view_base [[nodiscard]] constexpr reference front() const noexcept { - assert(!empty()); + assert(!empty() && "span: front() called on empty span"); return data()[0]; } [[nodiscard]] constexpr reference back() const noexcept { - assert(!empty()); + assert(!empty() && "span: back() called on empty span"); return data()[size() - 1]; } @@ -428,28 +510,38 @@ class span : public detail::view_base return reverse_iterator(begin()); } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(begin()); + } + template [[nodiscard]] constexpr span first() const noexcept { if constexpr (Extent == dynamic_extent) { - assert(Count <= size()); + assert(Count <= size() && "span::first(): Count out of range"); } else { static_assert(Count <= Extent, "Count out of bounds in span::first()"); } - return { data(), internal_static_construct_tag{ } }; + return span(data(), Count); } template [[nodiscard]] constexpr span last() const noexcept { if constexpr (Extent == dynamic_extent) { - assert(Count <= size()); + assert(Count <= size() && "span::last(): Count out of range"); } else { static_assert(Count <= Extent, "Count out of bounds in span::last()"); } - return { data() + (size() - Count), internal_static_construct_tag{ } }; + return span(data() + (size() - Count), Count); } template @@ -457,7 +549,7 @@ class span : public detail::view_base -> span()> { if constexpr (Extent == dynamic_extent) { - assert(Offset <= size()); + assert(Offset <= size() && "span::subspan(): Offset out of range"); } else { static_assert(Offset <= Extent, "Offset out of bounds in span::subspan()"); } @@ -466,13 +558,14 @@ class span : public detail::view_base if constexpr (E != dynamic_extent) { if constexpr (Extent == dynamic_extent && Count != dynamic_extent) { - assert(Count <= (size() - Offset)); + assert(Count <= (size() - Offset) + && "span::subspan(): Count out of range"); } else if constexpr (Extent != dynamic_extent) { static_assert(Count == dynamic_extent || Count <= (Extent - Offset), "Count out of bounds"); } - return { data() + Offset, internal_static_construct_tag{ } }; + return span(data() + Offset, E); } else { return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count }; } @@ -480,25 +573,25 @@ class span : public detail::view_base [[nodiscard]] constexpr span first(size_type count) const noexcept { - assert(count <= size()); + assert(count <= size() && "span::first(count): count out of range"); return { data(), count }; } [[nodiscard]] constexpr span last(size_type count) const noexcept { - assert(count <= size()); + assert(count <= size() && "span::last(count): count out of range"); return { data() + (size() - count), count }; } [[nodiscard]] constexpr span subspan(size_type offset, size_type count = dynamic_extent) const noexcept { - assert(offset <= size()); + assert(offset <= size() && "span::subspan(offset,count): offset out of range"); if (count == dynamic_extent) { return { data() + offset, size() - offset }; } - assert(offset + count <= size()); + assert(count <= size() - offset && "span::subspan(offset,count): count out of range"); return { data() + offset, count }; } @@ -521,6 +614,9 @@ span(It, std::size_t) -> span span(It, End) -> span())>>; +template +span(Container &&) -> span()))>>; + template , int> = 0> [[nodiscard]] constexpr auto as_bytes(span s) noexcept { diff --git a/tests/ll-box-ut/src/span_test.cpp b/tests/ll-box-ut/src/span_test.cpp index 5a67de7..2abe3de 100644 --- a/tests/ll-box-ut/src/span_test.cpp +++ b/tests/ll-box-ut/src/span_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace utils = linyaps_box::utils; @@ -215,6 +216,26 @@ TEST(SpanCTAD, ItEnd) static_assert(decltype(s)::extent == utils::dynamic_extent); } +TEST(SpanCTAD, Container) +{ + std::vector v{ 1, 2, 3 }; + const utils::span s(v); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[1], 2); + static_assert(std::is_same_v); + static_assert(decltype(s)::extent == utils::dynamic_extent); +} + +TEST(SpanCTAD, ConstContainer) +{ + const std::vector v{ 4, 5 }; + const utils::span s(v); + EXPECT_EQ(s.size(), 2); + EXPECT_EQ(s[0], 4); + static_assert(std::is_same_v); + static_assert(decltype(s)::extent == utils::dynamic_extent); +} + namespace { template @@ -270,3 +291,208 @@ TEST(SpanBytes, AsWritableBytes) bytes[0] = std::byte(1); EXPECT_EQ(arr[0], 1); } + +TEST(SpanElementAccess, OperatorIndex) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); + s[1] = 42; + EXPECT_EQ(arr[1], 42); +} + +TEST(SpanElementAccess, FrontBack) +{ + std::array arr{ 10, 20, 30 }; + const utils::span s(arr); + EXPECT_EQ(s.front(), 10); + EXPECT_EQ(s.back(), 30); + s.front() = 1; + s.back() = 3; + EXPECT_EQ(arr[0], 1); + EXPECT_EQ(arr[2], 3); +} + +TEST(SpanElementAccess, FrontBackConstSpan) +{ + const std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + EXPECT_EQ(s.front(), 1); + EXPECT_EQ(s.back(), 3); +} + +TEST(SpanIterators, ForwardIteration) +{ + std::array arr{ 10, 20, 30 }; + const utils::span s(arr); + + auto sum = std::accumulate(s.cbegin(), s.cend(), 0); + EXPECT_EQ(sum, 60); + + int count = 0; + for ([[maybe_unused]] auto val : s) { + ++count; + } + EXPECT_EQ(count, 3); + + *s.begin() = 99; + EXPECT_EQ(arr[0], 99); +} + +TEST(SpanIterators, ReverseIteration) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + auto rit = s.rbegin(); + EXPECT_EQ(*rit, 3); + ++rit; + EXPECT_EQ(*rit, 2); + ++rit; + EXPECT_EQ(*rit, 1); + ++rit; + EXPECT_EQ(rit, s.rend()); +} + +TEST(SpanIterators, ConstIterators) +{ + const std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + EXPECT_EQ(*s.cbegin(), 1); + EXPECT_EQ(s.cend(), s.cbegin() + 3); + EXPECT_EQ(*s.crbegin(), 3); + EXPECT_EQ(s.crend(), s.crbegin() + 3); +} + +TEST(SpanIterators, ReverseMutate) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + *s.rbegin() = 99; + EXPECT_EQ(arr[2], 99); +} + +TEST(SpanSubviews, TemplateFirst) +{ + std::array arr{ 1, 2, 3, 4, 5 }; + const utils::span s(arr); + auto first2 = s.first<2>(); + EXPECT_EQ(first2.size(), 2); + EXPECT_EQ(first2[0], 1); + EXPECT_EQ(first2[1], 2); + static_assert(decltype(first2)::extent == 2); +} + +TEST(SpanSubviews, TemplateLast) +{ + std::array arr{ 1, 2, 3, 4, 5 }; + const utils::span s(arr); + auto last3 = s.last<3>(); + EXPECT_EQ(last3.size(), 3); + EXPECT_EQ(last3[0], 3); + EXPECT_EQ(last3[2], 5); + static_assert(decltype(last3)::extent == 3); +} + +TEST(SpanSubviews, TemplateSubspan) +{ + std::array arr{ 0, 1, 2, 3, 4 }; + const utils::span s(arr); + auto sub = s.subspan<1, 3>(); + EXPECT_EQ(sub.size(), 3); + EXPECT_EQ(sub[0], 1); + EXPECT_EQ(sub[2], 3); + static_assert(decltype(sub)::extent == 3); +} + +TEST(SpanSubviews, RuntimeFirst) +{ + std::array arr{ 1, 2, 3, 4, 5 }; + const utils::span s(arr); + auto first2 = s.first(2); + EXPECT_EQ(first2.size(), 2); + EXPECT_EQ(first2[0], 1); + EXPECT_EQ(first2[1], 2); +} + +TEST(SpanSubviews, RuntimeLast) +{ + std::array arr{ 1, 2, 3, 4, 5 }; + const utils::span s(arr); + auto last3 = s.last(3); + EXPECT_EQ(last3.size(), 3); + EXPECT_EQ(last3[0], 3); + EXPECT_EQ(last3[2], 5); +} + +TEST(SpanSubviews, RuntimeSubspanAll) +{ + std::array arr{ 0, 1, 2, 3, 4 }; + const utils::span s(arr); + auto sub = s.subspan(0); + EXPECT_EQ(sub.size(), 5); + EXPECT_EQ(sub[0], 0); + EXPECT_EQ(sub[4], 4); +} + +TEST(SpanObservers, SizeBytesAndEmpty) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s(arr); + EXPECT_EQ(s.size_bytes(), sizeof(int) * 3); + EXPECT_FALSE(s.empty()); + EXPECT_EQ(s.data(), arr.data()); + + const utils::span empty; + EXPECT_TRUE(empty.empty()); + EXPECT_EQ(empty.size_bytes(), 0); + EXPECT_EQ(empty.data(), nullptr); +} + +TEST(SpanConstructor, DefaultConstructed) +{ + const utils::span s; + EXPECT_EQ(s.size(), 0); + EXPECT_EQ(s.data(), nullptr); + EXPECT_TRUE(s.empty()); +} + +TEST(SpanConstructor, ConstConversion) +{ + std::array arr{ 1, 2, 3 }; + const utils::span mutable_s(arr); + const utils::span const_s(mutable_s); + EXPECT_EQ(const_s.size(), 3); + EXPECT_EQ(const_s[0], 1); + EXPECT_EQ(const_s[2], 3); +} + +TEST(SpanCopy, CopyConstructor) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s1(arr); + const utils::span s2(s1); + EXPECT_EQ(s1.size(), s2.size()); + EXPECT_EQ(s1.data(), s2.data()); + EXPECT_EQ(s2[1], 2); +} + +TEST(SpanCopy, CopyAssignment) +{ + std::array arr{ 1, 2, 3 }; + const utils::span s1(arr); + utils::span s2; + s2 = s1; + EXPECT_EQ(s1.size(), s2.size()); + EXPECT_EQ(s1.data(), s2.data()); + EXPECT_EQ(s2[0], 1); +} + +TEST(SpanBytes, AsBytesFixedExtent) +{ + const std::array arr{ 0x01020304 }; + const utils::span s(arr); + auto bytes = utils::as_bytes(s); + EXPECT_EQ(bytes.size(), sizeof(int)); + static_assert(decltype(bytes)::extent == sizeof(int)); +}