Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cpp/include/coconext/types/array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ class ArrayImpl {
);
}

template <index_type I>
constexpr reference index() {
static_assert(find(R, I) != R.end(), "index is out of range");
return (*this)[I];
}
template <index_type I>
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(); }
Expand Down
11 changes: 11 additions & 0 deletions cpp/include/coconext/types/array_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ class ArraySliceImpl {
return StaticArraySlice<ArrayT, R>(arr_);
}

template <index_type I>
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
Expand Down Expand Up @@ -309,6 +314,12 @@ class StaticArraySliceImpl {
return StaticArraySlice<ArrayT, R2>(arr_);
}

template <index_type I>
constexpr reference index() const {
static_assert(find(R, I) != R.end(), "index is out of range");
return (*this)[I];
}

constexpr ArraySlice<ArrayT> operator[](Range r) const {
detail::subsequence_check(R, r);
return ArraySlice<ArrayT>(arr_, r);
Expand Down
9 changes: 9 additions & 0 deletions cpp/include/coconext/types/vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ class VectorImpl {
);
}

template <index_type I>
COCONEXT_VECTOR_CONSTEXPR reference index() {
return (*this)[I];
}
template <index_type I>
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 {
Expand Down
41 changes: 41 additions & 0 deletions tests/cpp/test_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -948,4 +948,45 @@ static_assert(!StaticRangedSequence<Array<int, Range{0, Direction::TO, 3}>, floa
static_assert(!StaticRangedSequence<Vector<int>>);
static_assert(!StaticRangedSequence<Vector<int>, int>);

// -- index<I>() on dynamic-range types -------------------------------------
//
// On Vector and ArraySlice the range is a runtime value, so index<I>() can't
// static_assert anything -- it just delegates to operator[](I) for API
// consistency with the static-range siblings (Array::index<I>(),
// StaticArraySlice::index<I>()).

TEST(TestVector, IndexOfVectorTO) {
Vector<int> 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<int> 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<int> const a({10, 20, 30});
EXPECT_EQ(a.index<1>(), 20);
}

TEST(TestVector, IndexOfDynamicSlice) {
Vector<int> 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<int> 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
34 changes: 34 additions & 0 deletions tests/cpp/test_logic_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1263,3 +1263,37 @@ TEST(TestBitArray, UdlBitUnderscore) {
// Note: "<bad>"_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<I>() 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<I>() 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<Logic>, 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<Range{2, Direction::DOWNTO, 1}>();
EXPECT_EQ(s.index<2>(), '1'_l);
EXPECT_EQ(s.index<1>(), 'X'_l);
}
47 changes: 47 additions & 0 deletions tests/cpp/test_static_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,51 @@ TEST(TestStaticArray, DynSliceStaticSubSliceFlattens) {
s[2] = 99;
EXPECT_EQ(a[2], 99);
}

// -- index<I>() compile-time-checked element access ------------------------
//
// On types with a compile-time range (Array, StaticArraySlice), index<I>()
// 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<R>()'s compile-time subsequence check.

TEST(TestStaticArray, IndexOfStaticArrayTO) {
Array<int, Range{0, Direction::TO, 3}> 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<int, Range{3, Direction::DOWNTO, 0}> 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<int, Range{0, Direction::TO, 2}> const a({10, 20, 30});
int const& r = a.index<1>();
EXPECT_EQ(r, 20);
static_assert(std::is_same_v<decltype(a.index<1>()), int const&>);
}

TEST(TestStaticArray, IndexOfStaticArraySlice) {
Array<int, Range{0, Direction::TO, 5}> a({10, 20, 30, 40, 50, 60});
auto s = a.slice<Range{2, 4}>();
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<int, Range{0, Direction::TO, 3}> a{};
// a.index<4>(); // out of range high
// a.index<-1>(); // out of range low
// a.slice<Range{0, 2}>().index<3>(); // out of slice range
// LCOV_EXCL_BR_STOP
Loading