diff --git a/cpp/include/coconext/types/array.hpp b/cpp/include/coconext/types/array.hpp index 2a8c084..50920e5 100644 --- a/cpp/include/coconext/types/array.hpp +++ b/cpp/include/coconext/types/array.hpp @@ -143,6 +143,17 @@ class ArrayImpl { ); } + template + constexpr reference index() { + static_assert(find(R, I) != R.end(), "index is out of range"); + return (*this)[I]; + } + template + constexpr const_reference index() const { + static_assert(find(R, I) != R.end(), "index is out of range"); + return (*this)[I]; + } + constexpr iterator begin() noexcept { return data_.begin(); } constexpr const_iterator begin() const noexcept { return data_.begin(); } constexpr iterator end() noexcept { return data_.end(); } diff --git a/cpp/include/coconext/types/array_base.hpp b/cpp/include/coconext/types/array_base.hpp index c2a24cb..484c303 100644 --- a/cpp/include/coconext/types/array_base.hpp +++ b/cpp/include/coconext/types/array_base.hpp @@ -182,6 +182,11 @@ class ArraySliceImpl { return StaticArraySlice(arr_); } + template + constexpr reference index() const { + return (*this)[I]; + } + // Have to override this since the implicitly generated copy assignment acts as a // variable assignment rather than writing through to the underlying storage. constexpr ArraySliceImpl const& operator=(ArraySliceImpl const& other) const @@ -309,6 +314,12 @@ class StaticArraySliceImpl { return StaticArraySlice(arr_); } + template + constexpr reference index() const { + static_assert(find(R, I) != R.end(), "index is out of range"); + return (*this)[I]; + } + constexpr ArraySlice operator[](Range r) const { detail::subsequence_check(R, r); return ArraySlice(arr_, r); diff --git a/cpp/include/coconext/types/vector.hpp b/cpp/include/coconext/types/vector.hpp index 5f26944..3dcd5f3 100644 --- a/cpp/include/coconext/types/vector.hpp +++ b/cpp/include/coconext/types/vector.hpp @@ -181,6 +181,15 @@ class VectorImpl { ); } + template + COCONEXT_VECTOR_CONSTEXPR reference index() { + return (*this)[I]; + } + template + COCONEXT_VECTOR_CONSTEXPR const_reference index() const { + return (*this)[I]; + } + COCONEXT_VECTOR_CONSTEXPR iterator begin() noexcept { return data_.get(); } COCONEXT_VECTOR_CONSTEXPR const_iterator begin() const noexcept { return data_.get(); } COCONEXT_VECTOR_CONSTEXPR iterator end() noexcept { diff --git a/tests/cpp/test_array.cpp b/tests/cpp/test_array.cpp index bbe7f94..5a190c0 100644 --- a/tests/cpp/test_array.cpp +++ b/tests/cpp/test_array.cpp @@ -948,4 +948,45 @@ static_assert(!StaticRangedSequence, floa static_assert(!StaticRangedSequence>); static_assert(!StaticRangedSequence, int>); +// -- index() on dynamic-range types ------------------------------------- +// +// On Vector and ArraySlice the range is a runtime value, so index() can't +// static_assert anything -- it just delegates to operator[](I) for API +// consistency with the static-range siblings (Array::index(), +// StaticArraySlice::index()). + +TEST(TestVector, IndexOfVectorTO) { + Vector a({10, 20, 30, 40}, Range(0, Direction::TO, 3)); + EXPECT_EQ(a.index<0>(), 10); + EXPECT_EQ(a.index<3>(), 40); + a.index<2>() = 99; + EXPECT_EQ(a[2], 99); +} + +TEST(TestVector, IndexOfVectorOutOfRangeThrows) { + Vector a({10, 20, 30, 40}, Range(0, Direction::TO, 3)); + EXPECT_THROW((void)a.index<4>(), std::out_of_range); + EXPECT_THROW((void)a.index<-1>(), std::out_of_range); +} + +TEST(TestVector, IndexOfConstVector) { + Vector const a({10, 20, 30}); + EXPECT_EQ(a.index<1>(), 20); +} + +TEST(TestVector, IndexOfDynamicSlice) { + Vector a({10, 20, 30, 40, 50}); + auto s = a[{1, 3}]; // dynamic ArraySlice over indices 1..3 + EXPECT_EQ(s.index<1>(), 20); + EXPECT_EQ(s.index<3>(), 40); + s.index<2>() = 99; + EXPECT_EQ(a[2], 99); +} + +TEST(TestVector, IndexOfDynamicSliceOutOfRangeThrows) { + Vector a({10, 20, 30, 40, 50}); + auto s = a[{1, 3}]; + EXPECT_THROW((void)s.index<0>(), std::out_of_range); + EXPECT_THROW((void)s.index<4>(), std::out_of_range); +} // LCOV_EXCL_BR_STOP diff --git a/tests/cpp/test_logic_array.cpp b/tests/cpp/test_logic_array.cpp index b81723f..48d6aa7 100644 --- a/tests/cpp/test_logic_array.cpp +++ b/tests/cpp/test_logic_array.cpp @@ -1263,3 +1263,37 @@ TEST(TestBitArray, UdlBitUnderscore) { // Note: ""_b is a compile-time error (throw in constant evaluation), so // the UDL invalid-character path cannot be exercised by a runtime test. The // runtime invalid-character path is covered by ToBitArrayInvalidChar above. + +// -- index() on Logic/Bit specializations ------------------------------- +// +// The Logic/Bit Array, Vector, ArraySlice, and StaticArraySlice +// specializations all inherit from the corresponding *Impl base, so they +// pick up index() automatically. These tests confirm the inheritance +// and exercise the HDL-conventional DOWNTO indexing. + +TEST(TestLogicArray, IndexOfLogicArray) { + auto a = "01XZ"_l; // LogicArray with Range{3 DOWNTO 0} + EXPECT_EQ(a.index<3>(), '0'_l); + EXPECT_EQ(a.index<2>(), '1'_l); + EXPECT_EQ(a.index<1>(), 'X'_l); + EXPECT_EQ(a.index<0>(), 'Z'_l); +} + +TEST(TestLogicArray, IndexOfBitArray) { + auto a = "0110"_b; + EXPECT_EQ(a.index<3>(), '0'_b); + EXPECT_EQ(a.index<2>(), '1'_b); +} + +TEST(TestLogicArray, IndexOfLogicVector) { + auto a = to_logic_array("01XZ"); // Vector, DOWNTO {3..0} + EXPECT_EQ(a.index<3>(), '0'_l); + EXPECT_EQ(a.index<0>(), 'Z'_l); +} + +TEST(TestLogicArray, IndexOfLogicSlice) { + auto a = "01XZ"_l; + auto s = a.slice(); + EXPECT_EQ(s.index<2>(), '1'_l); + EXPECT_EQ(s.index<1>(), 'X'_l); +} diff --git a/tests/cpp/test_static_array.cpp b/tests/cpp/test_static_array.cpp index ff12410..211c85f 100644 --- a/tests/cpp/test_static_array.cpp +++ b/tests/cpp/test_static_array.cpp @@ -421,4 +421,51 @@ TEST(TestStaticArray, DynSliceStaticSubSliceFlattens) { s[2] = 99; EXPECT_EQ(a[2], 99); } + +// -- index() compile-time-checked element access ------------------------ +// +// On types with a compile-time range (Array, StaticArraySlice), index() +// static_asserts that I is within the range and then delegates to +// operator[](I). This catches out-of-range mistakes at the call site rather +// than at runtime, mirroring slice()'s compile-time subsequence check. + +TEST(TestStaticArray, IndexOfStaticArrayTO) { + Array a({10, 20, 30, 40}); + EXPECT_EQ(a.index<0>(), 10); + EXPECT_EQ(a.index<3>(), 40); + a.index<2>() = 99; + EXPECT_EQ(a[2], 99); +} + +TEST(TestStaticArray, IndexOfStaticArrayDOWNTO) { + Array a({10, 20, 30, 40}); + // DOWNTO: a[3] is the first element, a[0] is the last. + EXPECT_EQ(a.index<3>(), 10); + EXPECT_EQ(a.index<0>(), 40); +} + +TEST(TestStaticArray, IndexOfConstStaticArray) { + Array const a({10, 20, 30}); + int const& r = a.index<1>(); + EXPECT_EQ(r, 20); + static_assert(std::is_same_v()), int const&>); +} + +TEST(TestStaticArray, IndexOfStaticArraySlice) { + Array a({10, 20, 30, 40, 50, 60}); + auto s = a.slice(); + EXPECT_EQ(s.index<2>(), 30); + EXPECT_EQ(s.index<4>(), 50); + s.index<3>() = 99; + EXPECT_EQ(a[3], 99); +} + +// Compile-time bounds check: uncommenting any of these should produce a +// static_assert "index is out of range" failure. Kept here as a referenced +// reminder rather than as an enabled test (no C++ idiom for testing for a +// static_assert firing at compile time). +// Array a{}; +// a.index<4>(); // out of range high +// a.index<-1>(); // out of range low +// a.slice().index<3>(); // out of slice range // LCOV_EXCL_BR_STOP