From 27e2cc0cd2fff4ea5ac439b98608429a213d2893 Mon Sep 17 00:00:00 2001 From: Yuming He Date: Fri, 26 Jun 2026 15:09:13 +0800 Subject: [PATCH 1/2] feat(span): add iter ctors, CTAD, as_bytes, P1976R2 Signed-off-by: Yuming He --- src/linyaps_box/utils/span.h | 230 ++++++++++++++++++++++++++++++----- 1 file changed, 201 insertions(+), 29 deletions(-) diff --git a/src/linyaps_box/utils/span.h b/src/linyaps_box/utils/span.h index b2092e5..6bea3d2 100644 --- a/src/linyaps_box/utils/span.h +++ b/src/linyaps_box/utils/span.h @@ -4,7 +4,7 @@ #pragma once -// from stdc++20's std::span, compatible with c++17 +// from STL std::span, compatible with c++17 #include #include @@ -16,10 +16,73 @@ namespace linyaps_box::utils { namespace detail { -// std::remove_cvref was introduced at c++20 template using remove_cvref_t = std::remove_cv_t>; +template +struct type_identity +{ + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; + +template +using is_array_convertible = std::is_convertible; + +template +inline constexpr bool is_array_convertible_v = is_array_convertible::value; + +template +constexpr T *to_address(T *p) noexcept +{ + return p; +} + +template +struct has_pointer_traits_to_address : std::false_type +{ +}; + +template +struct has_pointer_traits_to_address< + Ptr, + std::void_t::to_address(std::declval()))>> + : std::true_type +{ +}; + +template +constexpr auto to_address(const Ptr &p) noexcept +{ + if constexpr (has_pointer_traits_to_address::value) { + return std::pointer_traits::to_address(p); + } else { + return to_address(p.operator->()); + } +} + +template +struct is_span_compatible_iter : std::false_type +{ +}; + +template +struct is_span_compatible_iter()), + decltype(std::declval() - std::declval()), + decltype(detail::to_address(std::declval()))>> + : std::conjunction< + is_array_convertible())>>, + std::is_convertible())), T *>> +{ +}; + +template +inline constexpr bool is_span_compatible_iter_v = is_span_compatible_iter::value; + struct view_base { }; @@ -57,8 +120,7 @@ struct is_compatible_container().data()), decltype(std::declval().size())>> - : std::is_convertible().data())> (*)[], - T (*)[]> + : is_array_convertible().data())>> { }; @@ -160,24 +222,78 @@ class span : public detail::view_base { } + template * = nullptr> constexpr span(pointer ptr, size_type count) noexcept : storage_(ptr, count) { - if constexpr (Extent != dynamic_extent) { - assert(count == Extent); - } } + template * = nullptr> + constexpr explicit span(pointer ptr, size_type count) noexcept + : storage_(ptr, count) + { + assert(count == E); + } + + template * = nullptr> constexpr span(pointer first, pointer last) noexcept : storage_(first, static_cast(last - first)) { - if constexpr (Extent != dynamic_extent) { - assert(static_cast(last - first) == Extent); - } + } + + template * = nullptr> + constexpr explicit span(pointer first, pointer last) noexcept + : storage_(first, static_cast(last - first)) + { + assert(static_cast(last - first) == E); + } + + // (It, count) for dynamic extent — implicit + template < + typename It, + std::size_t E = Extent, + std::enable_if_t> * = nullptr> + constexpr span(It first, size_type count) noexcept + : storage_(detail::to_address(first), count) + { + } + + // (It, count) for fixed extent — explicit + template < + typename It, + std::size_t E = Extent, + std::enable_if_t> * = nullptr> + constexpr explicit span(It first, size_type count) noexcept + : storage_(detail::to_address(first), count) + { + assert(count == E); + } + + // (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)) + { + } + + // (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 > - constexpr explicit span(T (&arr)[N]) noexcept + constexpr span(detail::type_identity_t (&arr)[N]) noexcept : storage_(arr, N) { } @@ -185,8 +301,8 @@ class span : public detail::view_base template >> - constexpr explicit span(std::array &arr) noexcept + && detail::is_array_convertible_v>> + constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) { } @@ -194,33 +310,44 @@ class span : public detail::view_base template >> - constexpr explicit span(const std::array &arr) noexcept + && detail::is_array_convertible_v>> + constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) { } - template < - typename Container, - typename = std::enable_if_t, span> - && !std::is_array_v> - && detail::is_compatible_container::value - && (std::is_lvalue_reference_v - || detail::is_view_v>)>> + template , span> + && !std::is_array_v> + && detail::is_compatible_container::value + && (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()) + { + } + + template , span> + && !std::is_array_v> + && detail::is_compatible_container::value + && (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()) { - if constexpr (Extent != dynamic_extent) { - assert(cont.size() == Extent); - } + assert(cont.size() == Extent); } template >> - constexpr explicit span(const span &other) noexcept + std::enable_if_t< + (Extent == dynamic_extent || OtherExtent == dynamic_extent || Extent == OtherExtent) + && detail::is_array_convertible_v + && (Extent == dynamic_extent || OtherExtent != dynamic_extent)> * = nullptr> + constexpr span(const span &other) noexcept : storage_(other.data(), other.size()) { if constexpr (Extent != dynamic_extent) { @@ -228,6 +355,18 @@ class span : public detail::view_base } } + template + && (Extent != dynamic_extent && OtherExtent == dynamic_extent)> * = nullptr> + constexpr explicit span(const span &other) noexcept + : storage_(other.data(), other.size()) + { + assert(other.size() == Extent); + } + ~span() = default; constexpr span(const span &) noexcept = default; constexpr span(span &&) noexcept = default; @@ -367,4 +506,37 @@ class span : public detail::view_base span_storage storage_; }; +template +span(T (&)[N]) -> span; + +template +span(std::array &) -> span; + +template +span(const std::array &) -> span; + +template +span(It, std::size_t) -> span())>>; + +template +span(It, End) -> span())>>; + +template , int> = 0> +[[nodiscard]] constexpr auto as_bytes(span s) noexcept +{ + using ReturnType = + span; + return ReturnType(reinterpret_cast(s.data()), s.size_bytes()); +} + +template && !std::is_volatile_v, int> = 0> +[[nodiscard]] constexpr auto as_writable_bytes(span s) noexcept +{ + using ReturnType = + span; + return ReturnType(reinterpret_cast(s.data()), s.size_bytes()); +} + } // namespace linyaps_box::utils From f348b07b1b1cb1c2e4feb4c49dc4252c4805f789 Mon Sep 17 00:00:00 2001 From: Yuming He Date: Fri, 26 Jun 2026 15:09:21 +0800 Subject: [PATCH 2/2] test(span): add more tests for span Signed-off-by: Yuming He --- tests/ll-box-ut/src/span_test.cpp | 219 ++++++++++++++++++++++++++++-- 1 file changed, 208 insertions(+), 11 deletions(-) diff --git a/tests/ll-box-ut/src/span_test.cpp b/tests/ll-box-ut/src/span_test.cpp index 3d2e1ab..5a67de7 100644 --- a/tests/ll-box-ut/src/span_test.cpp +++ b/tests/ll-box-ut/src/span_test.cpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace utils = linyaps_box::utils; @@ -28,7 +29,6 @@ TEST(SpanAt, OutOfRangeThrows) const utils::span s(arr); EXPECT_THROW(std::ignore = s.at(3), std::out_of_range); - EXPECT_THROW(std::ignore = s.at(100), std::out_of_range); } TEST(SpanAt, EmptySpanThrows) @@ -55,16 +55,7 @@ TEST(SpanAt, ConstSpanReadsCorrectly) EXPECT_EQ(s.at(1), 6); } -TEST(SpanAt, BoundaryIndex) -{ - std::array arr = { 99 }; - const utils::span s(arr); - - EXPECT_EQ(s.at(0), 99); - EXPECT_THROW(std::ignore = s.at(1), std::out_of_range); -} - -TEST(SpanAt, SubspanAt) +TEST(Subspan, Offset) { std::array raw{ 0, 1, 2, 3, 4 }; auto s = utils::span(raw).subspan(1, 3); @@ -73,3 +64,209 @@ TEST(SpanAt, SubspanAt) EXPECT_EQ(s.at(2), 3); EXPECT_THROW(std::ignore = s.at(3), std::out_of_range); } + +// P1976R2: fixed-size span construction from dynamic-size range should be explicit. +// Single-argument constructors are tested via is_convertible_v (which requires implicit +// conversion). Two-argument constructors (ptr,count) and (first,last) are tested via runtime +// positive tests. +static_assert(std::is_convertible_v &, utils::span>, + "dynamic span from container should be implicit"); +static_assert(!std::is_convertible_v &, utils::span>, + "fixed span from container should be explicit"); + +static_assert(std::is_convertible_v &, utils::span>, + "dynamic→dynamic span should be implicit"); +static_assert(!std::is_convertible_v &, utils::span>, + "dynamic→fixed span should be explicit"); +static_assert(std::is_convertible_v &, utils::span>, + "fixed→fixed (matching) span should be implicit"); +static_assert(std::is_convertible_v &, utils::span>, + "fixed→dynamic span should be implicit"); + +static_assert(std::is_convertible_v>, + "dynamic span from c-array should be implicit"); +static_assert(std::is_convertible_v>, + "fixed span from c-array (matching extent) should be implicit"); +static_assert(std::is_convertible_v &, utils::span>, + "fixed span from std::array (matching extent) should be implicit"); +static_assert(std::is_convertible_v &, utils::span>, + "fixed span from const std::array (matching extent) should be implicit"); + +static_assert(std::is_default_constructible_v>, + "dynamic span should be default-constructible"); +static_assert(!std::is_default_constructible_v>, + "fixed non-zero span should NOT be default-constructible"); + +TEST(SpanConstructorExplicit, PtrCountDynamic) +{ + const std::vector data{ 1, 2, 3 }; + const utils::span s(data.data(), 3); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); +} + +TEST(SpanConstructorExplicit, FirstLastDynamic) +{ + const std::vector data{ 1, 2, 3, 4 }; + const utils::span s(data.cbegin(), data.cend()); + EXPECT_EQ(s.size(), 4); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[3], 4); +} + +TEST(SpanConstructorExplicit, ContainerFixedExplicit) +{ + const std::vector v{ 1, 2, 3 }; + auto s = utils::span(v); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); +} + +TEST(SpanConstructorExplicit, ConvertingSpanFixedToDynamic) +{ + std::array arr{ 1, 2, 3 }; + const utils::span fixed(arr); + const utils::span dyn(fixed); + EXPECT_EQ(dyn.size(), 3); + EXPECT_EQ(dyn[0], 1); +} + +TEST(SpanConstructorExplicit, ConvertingSpanDynamicToFixed) +{ + std::vector arr{ 1, 2, 3 }; + const utils::span dyn(arr); + auto fixed = utils::span(dyn); + EXPECT_EQ(fixed.size(), 3); + EXPECT_EQ(fixed[0], 1); +} + +TEST(SpanConstructorExplicit, ItCountDynamic) +{ + const std::vector data{ 10, 20, 30 }; + const utils::span s(data.cbegin(), 2); + EXPECT_EQ(s.size(), 2); + EXPECT_EQ(s[0], 10); + EXPECT_EQ(s[1], 20); +} + +TEST(SpanConstructorExplicit, ItEndFixedExplicit) +{ + const std::vector data{ 1, 2, 3 }; + const utils::span s(data.cbegin(), data.cend()); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); +} + +TEST(SpanConstructorExplicit, ItCountFixedExplicit) +{ + const std::vector data{ 5, 6, 7 }; + const utils::span s(data.cbegin(), 3); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[0], 5); + EXPECT_EQ(s[2], 7); +} + +TEST(SpanCTAD, CArray) +{ + int arr[] = { 1, 2, 3 }; // NOLINT + const utils::span s(arr); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); + static_assert(decltype(s)::extent == 3); +} + +TEST(SpanCTAD, StdArray) +{ + std::array arr{ 10, 20 }; + const utils::span s(arr); + EXPECT_EQ(s.size(), 2); + EXPECT_EQ(s[0], 10); + static_assert(decltype(s)::extent == 2); +} + +TEST(SpanCTAD, ConstStdArray) +{ + const std::array arr{ 1, 2, 3, 4 }; + const utils::span s(arr); + EXPECT_EQ(s.size(), 4); + EXPECT_EQ(s[3], 4); + static_assert(decltype(s)::extent == 4); +} + +TEST(SpanCTAD, ItCount) +{ + std::vector v{ 5, 5, 5 }; + const utils::span s(v.begin(), 2); + EXPECT_EQ(s.size(), 2); + EXPECT_EQ(s[0], 5); + static_assert(decltype(s)::extent == utils::dynamic_extent); +} + +TEST(SpanCTAD, ItEnd) +{ + const std::vector v{ 7, 8, 9 }; + const utils::span s(v.cbegin(), v.cend()); + EXPECT_EQ(s.size(), 3); + EXPECT_EQ(s[2], 9); + static_assert(decltype(s)::extent == utils::dynamic_extent); +} + +namespace { + +template +struct has_as_bytes : std::false_type +{ +}; + +template +struct has_as_bytes>()))>> + : std::true_type +{ +}; + +template +struct has_as_writable_bytes : std::false_type +{ +}; + +template +struct has_as_writable_bytes< + T, + std::void_t>()))>> : std::true_type +{ +}; + +} // anonymous namespace + +static_assert(has_as_bytes::value, "as_bytes should accept int"); +static_assert(has_as_bytes::value, "as_bytes should accept const int"); +static_assert(!has_as_bytes::value, "as_bytes should reject volatile"); +static_assert(has_as_writable_bytes::value, "as_writable_bytes should accept int"); +static_assert(!has_as_writable_bytes::value, "as_writable_bytes should reject const"); +static_assert(!has_as_writable_bytes::value, + "as_writable_bytes should reject volatile"); + +TEST(SpanBytes, AsBytes) +{ + const std::array arr{ 0x01020304, 0x05060708 }; + const utils::span s(arr); + auto bytes = utils::as_bytes(s); + EXPECT_EQ(bytes.size(), sizeof(arr)); + EXPECT_EQ(bytes.data(), reinterpret_cast(s.data())); +} + +TEST(SpanBytes, AsWritableBytes) +{ + std::array arr{ 0x01020304 }; + const utils::span s(arr); + auto bytes = utils::as_writable_bytes(s); + EXPECT_EQ(bytes.size(), sizeof(int)); + memset(bytes.data(), 0, sizeof(int)); + EXPECT_EQ(arr[0], 0); + bytes[0] = std::byte(1); + EXPECT_EQ(arr[0], 1); +}