From 8b1110c61c9090e9535d106c9cdcee6e46db1dc8 Mon Sep 17 00:00:00 2001
From: Steven Le Rouzic <steven.lerouzic@gmail.com>
Date: Mon, 4 Nov 2024 22:09:50 +0100
Subject: Add span::subspan

---
 MODULE.bazel.lock        |  12 ++---
 asl/span.hpp             |  55 ++++++++++++++++++---
 asl/tests/span_tests.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+), 12 deletions(-)

diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 21b358e..d62a47c 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -64,19 +64,19 @@
     "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
       "general": {
         "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=",
-        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
+        "usagesDigest": "+hz7IHWN6A1oVJJWNDB6yZRG+RYhF76wAYItpAeIUIg=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},
         "envVariables": {},
         "generatedRepoSpecs": {
-          "local_config_apple_cc": {
+          "local_config_apple_cc_toolchains": {
             "bzlFile": "@@apple_support~//crosstool:setup.bzl",
-            "ruleClassName": "_apple_cc_autoconf",
+            "ruleClassName": "_apple_cc_autoconf_toolchains",
             "attributes": {}
           },
-          "local_config_apple_cc_toolchains": {
+          "local_config_apple_cc": {
             "bzlFile": "@@apple_support~//crosstool:setup.bzl",
-            "ruleClassName": "_apple_cc_autoconf_toolchains",
+            "ruleClassName": "_apple_cc_autoconf",
             "attributes": {}
           }
         },
@@ -92,7 +92,7 @@
     "@@platforms//host:extension.bzl%host_platform": {
       "general": {
         "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
-        "usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=",
+        "usagesDigest": "pCYpDQmqMbmiiPI1p2Kd3VLm5T48rRAht5WdW0X2GlA=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},
         "envVariables": {},
diff --git a/asl/span.hpp b/asl/span.hpp
index efc9eec..5ffcf68 100644
--- a/asl/span.hpp
+++ b/asl/span.hpp
@@ -13,7 +13,12 @@ static constexpr int64_t dynamic_size = -1;
 template<is_object T, int64_t kSize = dynamic_size>
 class span
 {
-    static constexpr bool kIsDynamic = kSize < 0;
+    static constexpr bool is_dynamic(int64_t size)
+    {
+        return size < 0;
+    }
+    
+    static constexpr bool kIsDynamic = is_dynamic(kSize);
 
     using SizeType = select_t<kIsDynamic, int64_t, empty>;
 
@@ -21,7 +26,7 @@ class span
     ASL_NO_UNIQUE_ADDRESS SizeType m_size{};
 
 public:
-    constexpr span() = default;
+    constexpr span() requires (kIsDynamic || kSize == 0) = default;
 
     constexpr span(T* data, int64_t size)
         requires kIsDynamic
@@ -55,7 +60,7 @@ public:
         requires (
             (
                 kIsDynamic ||
-                span<U, kOtherSize>::kIsDynamic ||
+                is_dynamic(kOtherSize) ||
                 kOtherSize == kSize
             ) && convertible_from<T(&)[], U(&)[]>
         )
@@ -92,11 +97,49 @@ public:
         return m_data[i]; // NOLINT(*-pointer-arithmetic)
     }
 
-    // @Todo subspan, first, last
+    // @Todo first, last
     // @Todo as_(mutable_)bytes
 
-    template<is_object U, int64_t kOtherSize>
-    friend class span;
+    template<int64_t kOffset, int64_t kSubSize = dynamic_size>
+    constexpr auto subspan() const
+        requires (
+            kOffset >= 0 &&
+            (kIsDynamic || kOffset <= kSize) &&
+            (kIsDynamic || is_dynamic(kSubSize) || kSubSize <= kSize - kOffset)
+        )
+    {
+        ASL_ASSERT(kOffset <= size());
+
+        if constexpr (is_dynamic(kSubSize))
+        {
+            if constexpr (kIsDynamic)
+            {
+                return span<T>(data() + kOffset, size() - kOffset);
+            }
+            else
+            {
+                return span<T, kSize - kOffset>(data() + kOffset, size() - kOffset);
+            }
+        }
+        else
+        {
+            ASL_ASSERT(kSubSize <= size() - kOffset);
+            return span<T, kSubSize>(data() + kOffset, kSubSize);
+        }
+    }
+
+    constexpr span<T> subspan(int64_t offset, int64_t sub_size = dynamic_size) const
+    {
+        ASL_ASSERT(offset <= size());
+        
+        if (is_dynamic(sub_size))
+        {
+            return span<T>{ data() + offset, size() - offset };
+        }
+        
+        ASL_ASSERT(sub_size <= size() - offset);
+        return span<T>{ data() + offset, sub_size };
+    }
 };
 
 } // namespace asl
diff --git a/asl/tests/span_tests.cpp b/asl/tests/span_tests.cpp
index 11510c6..3ede397 100644
--- a/asl/tests/span_tests.cpp
+++ b/asl/tests/span_tests.cpp
@@ -38,6 +38,10 @@ ASL_TEST(from_array_dynamic)
     ASL_TEST_EXPECT(span[2] == 3);
 }
 
+static_assert(asl::default_constructible<asl::span<int>>);
+static_assert(asl::default_constructible<asl::span<int, 0>>);
+static_assert(!asl::default_constructible<asl::span<int, 8>>);
+
 static_assert(asl::constructible_from<asl::span<int32_t>, int32_t(&)[8]>);
 static_assert(!asl::constructible_from<asl::span<int32_t>, const int32_t(&)[8]>);
 static_assert(asl::constructible_from<asl::span<const int32_t>, int32_t(&)[8]>);
@@ -89,3 +93,125 @@ ASL_TEST(conversion)
     ASL_TEST_EXPECT(span4[1] == 2);
     ASL_TEST_EXPECT(span4[2] == 3);
 }
+
+template<typename Span, int64_t kOffset, int64_t kSize = asl::dynamic_size>
+[[maybe_unused]] static auto try_static_subspan(int)
+    -> decltype(asl::declval<Span>().template subspan<kOffset, kSize>());
+
+template<typename, int64_t, int64_t>
+[[maybe_unused]] static auto try_static_subspan(...) -> asl::empty;
+
+template<typename Span, int64_t kOffset, int64_t kSize = asl::dynamic_size>
+concept invalid_subspan = asl::same_as<decltype(try_static_subspan<Span, kOffset, kSize>(0)), asl::empty>;
+
+static_assert(asl::same_as<asl::span<int, 4>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<0>())>);
+
+static_assert(asl::same_as<asl::span<int, 3>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<1>())>);
+
+static_assert(asl::same_as<asl::span<int, 2>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<2>())>);
+
+static_assert(asl::same_as<asl::span<int, 1>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<3>())>);
+
+static_assert(asl::same_as<asl::span<int, 0>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<4>())>);
+
+static_assert(invalid_subspan<asl::span<int, 4>, 5>);
+
+static_assert(asl::same_as<asl::span<int>,
+     decltype(asl::declval<asl::span<int>>().subspan<0>())>);
+
+static_assert(asl::same_as<asl::span<int, 4>,
+     decltype(asl::declval<asl::span<int>>().subspan<0, 4>())>);
+
+static_assert(asl::same_as<asl::span<int, 4>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<0, 4>())>);
+
+static_assert(asl::same_as<asl::span<int, 2>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<1, 2>())>);
+
+static_assert(asl::same_as<asl::span<int, 2>,
+     decltype(asl::declval<asl::span<int, 4>>().subspan<2, 2>())>);
+
+static_assert(invalid_subspan<asl::span<int, 4>, 2, 3>);
+
+ASL_TEST(subspan_static_from_static)
+{
+    int array[] = {1, 2, 3, 4};
+    asl::span<int, 4> span{array};
+
+    auto s1 = span.subspan<0>();
+    ASL_TEST_EXPECT(s1.size() == 4);
+    ASL_TEST_ASSERT(s1[0] == 1);
+    ASL_TEST_ASSERT(s1[1] == 2);
+    ASL_TEST_ASSERT(s1[2] == 3);
+    ASL_TEST_ASSERT(s1[3] == 4);
+
+    auto s2 = span.subspan<2>();
+    ASL_TEST_EXPECT(s2.size() == 2);
+    ASL_TEST_ASSERT(s2[0] == 3);
+    ASL_TEST_ASSERT(s2[1] == 4);
+
+    auto s3 = span.subspan<4>();
+    ASL_TEST_EXPECT(s3.size() == 0);
+
+    auto s4 = span.subspan<1, 2>();
+    ASL_TEST_EXPECT(s4.size() == 2);
+    ASL_TEST_ASSERT(s4[0] == 2);
+    ASL_TEST_ASSERT(s4[1] == 3);
+}
+
+ASL_TEST(subspan_static_from_dynamic)
+{
+    int array[] = {1, 2, 3, 4};
+    asl::span<int> span{array};
+
+    auto s1 = span.subspan<0>();
+    ASL_TEST_EXPECT(s1.size() == 4);
+    ASL_TEST_ASSERT(s1[0] == 1);
+    ASL_TEST_ASSERT(s1[1] == 2);
+    ASL_TEST_ASSERT(s1[2] == 3);
+    ASL_TEST_ASSERT(s1[3] == 4);
+
+    auto s2 = span.subspan<2>();
+    ASL_TEST_EXPECT(s2.size() == 2);
+    ASL_TEST_ASSERT(s2[0] == 3);
+    ASL_TEST_ASSERT(s2[1] == 4);
+
+    auto s3 = span.subspan<4>();
+    ASL_TEST_EXPECT(s3.size() == 0);
+
+    auto s4 = span.subspan<1, 2>();
+    ASL_TEST_EXPECT(s4.size() == 2);
+    ASL_TEST_ASSERT(s4[0] == 2);
+    ASL_TEST_ASSERT(s4[1] == 3);
+}
+
+ASL_TEST(subspan_dynamic)
+{
+    int array[] = {1, 2, 3, 4};
+    asl::span<int> span{array};
+
+    auto s1 = span.subspan(0);
+    ASL_TEST_EXPECT(s1.size() == 4);
+    ASL_TEST_ASSERT(s1[0] == 1);
+    ASL_TEST_ASSERT(s1[1] == 2);
+    ASL_TEST_ASSERT(s1[2] == 3);
+    ASL_TEST_ASSERT(s1[3] == 4);
+
+    auto s2 = span.subspan(2);
+    ASL_TEST_EXPECT(s2.size() == 2);
+    ASL_TEST_ASSERT(s2[0] == 3);
+    ASL_TEST_ASSERT(s2[1] == 4);
+
+    auto s3 = span.subspan(4);
+    ASL_TEST_EXPECT(s3.size() == 0);
+
+    auto s4 = span.subspan(1, 2);
+    ASL_TEST_EXPECT(s4.size() == 2);
+    ASL_TEST_ASSERT(s4[0] == 2);
+    ASL_TEST_ASSERT(s4[1] == 3);
+}
-- 
cgit