Add spvtools::utils::IndexRange (#6106)
It's a span, but without the base pointer.
Use the 'apply' method on a base pointer to generate a corresponding span.
diff --git a/source/util/index_range.h b/source/util/index_range.h
new file mode 100644
index 0000000..ee2299f
--- /dev/null
+++ b/source/util/index_range.h
@@ -0,0 +1,72 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_UTIL_INDEX_RANGE_H_
+#define SOURCE_UTIL_INDEX_RANGE_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "source/util/span.h"
+
+namespace spvtools {
+namespace utils {
+
+// Implement a range of indicies, to index over an array of values of type T,
+// but whose base pointer is supplied externally. Think of this as a span
+// but without the base pointer, which is to be applied later. Parameterization
+// by T makes usage more readable and less error-prone.
+template <typename T, class IndexType = uint32_t, class CountType = IndexType>
+class IndexRange {
+ public:
+ static_assert(std::is_integral<IndexType>::value);
+ static_assert(std::is_unsigned<IndexType>::value);
+ static_assert(std::is_integral<CountType>::value);
+ static_assert(std::is_unsigned<CountType>::value);
+ using value_type = T;
+ using index_type = IndexType;
+ using size_type = CountType;
+
+ constexpr IndexRange() {}
+ constexpr IndexRange(index_type first, size_type count)
+ : first_(first), count_(count) {}
+
+ size_type count() const { return count_; }
+ bool empty() const { return count() == size_type(0); }
+
+ IndexType first() const { return first_; }
+
+ // Returns the span of indexed elements using the given base pointer.
+ template <typename E>
+ spvtools::utils::Span<E> apply(E* base) const {
+ using span_type = spvtools::utils::Span<E>;
+ return base ? span_type(base + first_, count_) : span_type();
+ }
+ // This specialization lets us pass in a null pointer, which yields
+ // an empty span.
+ template <typename E = int>
+ spvtools::utils::Span<E> apply(std::nullptr_t) const {
+ using span_type = spvtools::utils::Span<E>;
+ return span_type();
+ }
+
+ private:
+ index_type first_ = 0;
+ size_type count_ = 0;
+};
+
+} // namespace utils
+} // namespace spvtools
+
+#endif // SOURCE_UTIL_INDEX_RANGE_H_
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt
index 33bb931..c5cbc17 100644
--- a/test/util/CMakeLists.txt
+++ b/test/util/CMakeLists.txt
@@ -17,6 +17,7 @@
bit_vector_test.cpp
bitutils_test.cpp
hash_combine_test.cpp
+ index_range_test.cpp
small_vector_test.cpp
span_test.cpp
LIBS SPIRV-Tools-opt
diff --git a/test/util/index_range_test.cpp b/test/util/index_range_test.cpp
new file mode 100644
index 0000000..0ae3954
--- /dev/null
+++ b/test/util/index_range_test.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/util/index_range.h"
+
+#include "gmock/gmock.h"
+
+namespace spvtools {
+namespace utils {
+namespace {
+
+using IndexRangeTest = ::testing::Test;
+
+using ushort = unsigned short;
+
+TEST(IndexRangeTest, Initialize_Default) {
+ double sentinel_a = 0.0;
+ double sentinel_b = 1.0;
+ const IndexRange<double, unsigned, ushort> ir;
+
+ EXPECT_EQ(ir.first(), unsigned(0));
+ EXPECT_EQ(ir.count(), ushort(0));
+ EXPECT_TRUE(ir.empty());
+
+ auto span_null = ir.apply(nullptr);
+ EXPECT_EQ(span_null.data(), nullptr);
+ EXPECT_EQ(span_null.size(), 0);
+ EXPECT_TRUE(span_null.empty());
+
+ auto span_a = ir.apply(&sentinel_a);
+ EXPECT_EQ(span_a.data(), &sentinel_a);
+ EXPECT_EQ(span_a.size(), 0);
+ EXPECT_TRUE(span_a.empty());
+
+ auto span_b = ir.apply(&sentinel_b);
+ EXPECT_EQ(span_b.data(), &sentinel_b);
+ EXPECT_EQ(span_b.size(), 0);
+ EXPECT_TRUE(span_b.empty());
+}
+
+TEST(IndexRangeTest, Initialize_NonEmpty) {
+ const IndexRange<double, unsigned, ushort> ir(1, 2);
+
+ EXPECT_EQ(ir.first(), unsigned(1));
+ EXPECT_EQ(ir.count(), ushort(2));
+ EXPECT_FALSE(ir.empty());
+
+ auto span_null = ir.apply(nullptr);
+ EXPECT_EQ(span_null.data(), nullptr);
+ EXPECT_EQ(span_null.size(), 0);
+ EXPECT_TRUE(span_null.empty());
+
+ double arr[] = {0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0};
+
+ auto span_a = ir.apply(arr);
+ EXPECT_EQ(span_a.begin(), arr + 1);
+ EXPECT_EQ(span_a.end(), arr + 3);
+ EXPECT_FALSE(span_a.empty());
+
+ auto span_b = ir.apply(arr + 3);
+ EXPECT_EQ(span_b.begin(), arr + 4);
+ EXPECT_EQ(span_b.end(), arr + 6);
+ EXPECT_FALSE(span_a.empty());
+}
+
+} // namespace
+} // namespace utils
+} // namespace spvtools