diff --git a/include/array_view.h b/include/array_view.h index bce3691..6af4d8d 100644 --- a/include/array_view.h +++ b/include/array_view.h @@ -81,10 +81,14 @@ namespace details using value_type = ValueType; static const unsigned int rank = Rank; _CONSTEXPR coordinate_facade() _NOEXCEPT + { + static_assert(std::is_base_of::value, "ConcreteType must be derived from coordinate_facade."); + } + _CONSTEXPR coordinate_facade(const value_type(&values)[rank]) _NOEXCEPT { static_assert(std::is_base_of::value, "ConcreteType must be derived from coordinate_facade."); for (unsigned int i = 0; i < rank; ++i) - elems[i] = {}; + elems[i] = values[i]; } _CONSTEXPR coordinate_facade(value_type e0) _NOEXCEPT { @@ -96,7 +100,7 @@ namespace details _CONSTEXPR coordinate_facade(std::initializer_list il) { static_assert(std::is_base_of::value, "ConcreteType must be derived from coordinate_facade."); - fail_fast_assert(il.size() == rank); + fail_fast_assert(il.size() == rank, "The size of the initializer list must match the rank of the array"); for (unsigned int i = 0; i < rank; ++i) { elems[i] = begin(il)[i]; @@ -119,13 +123,13 @@ namespace details // Preconditions: component_idx < rank _CONSTEXPR reference operator[](unsigned int component_idx) { - fail_fast_assert(component_idx < rank); + fail_fast_assert(component_idx < rank, "Component index must be less than rank"); return elems[component_idx]; } // Preconditions: component_idx < rank _CONSTEXPR const_reference operator[](unsigned int component_idx) const { - fail_fast_assert(component_idx < rank); + fail_fast_assert(component_idx < rank, "Component index must be less than rank"); return elems[component_idx]; } _CONSTEXPR bool operator==(const ConcreteType& rhs) const _NOEXCEPT @@ -230,7 +234,7 @@ namespace details elems[i] /= v; return to_concrete(); } - value_type elems[rank]; + value_type elems[rank] = {}; private: _CONSTEXPR const ConcreteType& to_concrete() const _NOEXCEPT { @@ -266,7 +270,8 @@ class index : private details::coordinate_facade, ValueTy { using Base = details::coordinate_facade, ValueType, Rank>; friend Base; - + template + friend class index; public: using Base::rank; using reference = typename Base::reference; @@ -274,15 +279,18 @@ public: using size_type = typename Base::value_type; using value_type = typename Base::value_type; _CONSTEXPR index() _NOEXCEPT : Base(){} - template > - _CONSTEXPR index(value_type e0) _NOEXCEPT : Base(e0){} - _CONSTEXPR index(std::initializer_list il) : Base(il){} + _CONSTEXPR index(const value_type (&values)[rank]) _NOEXCEPT : Base(values) {} + _CONSTEXPR index(std::initializer_list il) : Base(il) {} _CONSTEXPR index(const index &) = default; template _CONSTEXPR index(const index &other) : Base(other) { + } + _CONSTEXPR static index shift_left(const index& other) _NOEXCEPT + { + return (value_type(&)[rank])other.elems[1]; } using Base::operator[]; @@ -318,10 +326,13 @@ public: _CONSTEXPR index(value_type e0) _NOEXCEPT : value(e0) { } + _CONSTEXPR index(const value_type(&values)[1]) _NOEXCEPT : index(values[0]) + { + } // Preconditions: il.size() == rank _CONSTEXPR index(std::initializer_list il) { - fail_fast_assert(il.size() == rank); + fail_fast_assert(il.size() == rank, "Size of the initializer list must match the rank of the array"); value = begin(il)[0]; } @@ -334,18 +345,21 @@ public: value = static_cast(other.value); } - + _CONSTEXPR static index shift_left(const index& other) _NOEXCEPT + { + return other.elems[1]; + } // Preconditions: component_idx < rank _CONSTEXPR reference operator[](size_type component_idx) _NOEXCEPT { - fail_fast_assert(component_idx == 0); + fail_fast_assert(component_idx == 0, "Component index must be less than rank"); (void)(component_idx); return value; } // Preconditions: component_idx < rank _CONSTEXPR const_reference operator[](size_type component_idx) const _NOEXCEPT { - fail_fast_assert(component_idx == 0); + fail_fast_assert(component_idx == 0, "Component index must be less than rank"); (void)(component_idx); return value; } @@ -645,7 +659,7 @@ namespace details template SizeType linearize(const T & arr) const { - fail_fast_assert(arr[Dim] < CurrentRange); + fail_fast_assert(arr[Dim] < CurrentRange, "Index is out of range"); return static_cast(this->Base::totalSize()) * arr[Dim] + this->Base::template linearize(arr); } @@ -782,7 +796,8 @@ public: _CONSTEXPR static_bounds(std::initializer_list il) : m_ranges(il.begin()) { - fail_fast_assert(MyRanges::DynamicNum == il.size() && m_ranges.totalSize() <= details::SizeTypeTraits::max_value); + fail_fast_assert(MyRanges::DynamicNum == il.size(), "Size of the initializer list must match the rank of the array"); + fail_fast_assert(m_ranges.totalSize() <= details::SizeTypeTraits::max_value, "Size of the range is larger than the max element of the size type"); } _CONSTEXPR static_bounds() = default; @@ -807,6 +822,11 @@ public: { return static_cast(m_ranges.totalSize()); } + + _CONSTEXPR size_type total_size() const _NOEXCEPT + { + return static_cast(m_ranges.totalSize()); + } _CONSTEXPR size_type linearize(const index_type & idx) const { @@ -826,6 +846,7 @@ public: template _CONSTEXPR size_type extent() const _NOEXCEPT { + static_assert(Dim < rank, "dimension should be less than rank (dimension count starts from 0)"); return details::createTypeListIndexer(m_ranges).template get().elementNum(); } @@ -866,12 +887,9 @@ class strided_bounds : private details::coordinate_facade, { using Base = details::coordinate_facade, SizeType, Rank>; friend Base; - _CONSTEXPR void makeRegularStriae() _NOEXCEPT - { - strides[rank - 1] = 1; - for (int i = rank - 2; i >= 0; i--) - strides[i] = strides[i + 1] * Base::elems[i + 1]; - } + template + friend class strided_bounds; + public: using Base::rank; using reference = typename Base::reference; @@ -886,37 +904,40 @@ public: static const size_t static_size = dynamic_range; using sliced_type = std::conditional_t, void>; using mapping_type = generalized_mapping_tag; - _CONSTEXPR strided_bounds() _NOEXCEPT : Base(), strides() {} _CONSTEXPR strided_bounds(const strided_bounds &) = default; template - _CONSTEXPR strided_bounds(const strided_bounds &other) : Base(other) + _CONSTEXPR strided_bounds(const strided_bounds &other) + : Base(other), m_strides(other.strides) { } - _CONSTEXPR strided_bounds(const index_type &extents, const index_type &stride) - : strides(stride) + _CONSTEXPR strided_bounds(const index_type &extents, const index_type &strides) + : m_strides(strides) { for (unsigned int i = 0; i < rank; i++) Base::elems[i] = extents[i]; } - _CONSTEXPR strided_bounds(std::initializer_list il) - : Base(il) + _CONSTEXPR strided_bounds(const value_type(&values)[rank], index_type strides) + : Base(values), m_strides(std::move(strides)) { -#ifndef NDEBUG - for (const auto& v : il) - { - fail_fast_assert(v >= 0); - } -#endif - makeRegularStriae(); } - index_type strides; - _CONSTEXPR size_type size() const _NOEXCEPT + _CONSTEXPR index_type strides() const _NOEXCEPT + { + return m_strides; + } + _CONSTEXPR size_type total_size() const _NOEXCEPT { size_type ret = 0; for (unsigned int i = 0; i < rank; ++i) - ret += (Base::elems[i] - 1) * strides[i]; + ret += (Base::elems[i] - 1) * m_strides[i]; + return ret + 1; + } + _CONSTEXPR size_type size() const _NOEXCEPT + { + size_type ret = 1; + for (unsigned int i = 0; i < rank; ++i) + ret *= Base::elems[i]; return ret; } _CONSTEXPR bool contains(const index_type& idx) const _NOEXCEPT @@ -933,32 +954,29 @@ public: size_type ret = 0; for (unsigned int i = 0; i < rank; i++) { - fail_fast_assert(idx[i] < Base::elems[i]); - ret += idx[i] * strides[i]; + fail_fast_assert(idx[i] < Base::elems[i], "index is out of bounds of the array"); + ret += idx[i] * m_strides[i]; } return ret; } + _CONSTEXPR size_type stride() const _NOEXCEPT + { + return m_strides[0]; + } + template 1), typename Ret = std::enable_if_t> _CONSTEXPR sliced_type slice() const { - sliced_type ret; - for (unsigned int i = 1; i < rank; ++i) - { - ret.elems[i - 1] = Base::elems[i]; - ret.strides[i - 1] = strides[i]; - } - return ret; + return{ (value_type(&)[rank - 1])Base::elems[1], sliced_type::index_type::shift_left(m_strides) }; } template _CONSTEXPR size_type extent() const _NOEXCEPT { + static_assert(Dim < Rank, "dimension should be less than rank (dimension count starts from 0)"); return Base::elems[Dim]; } _CONSTEXPR index_type index_bounds() const _NOEXCEPT { - index_type extents; - for (unsigned int i = 0; i < rank; ++i) - extents[i] = (*this)[i]; - return extents; + return index_type(Base::elems); } const_iterator begin() const _NOEXCEPT { @@ -968,6 +986,8 @@ public: { return const_iterator{ *this, index_bounds() }; } +private: + index_type m_strides; }; template @@ -1296,7 +1316,7 @@ namespace details template _CONSTEXPR std::enable_if_t::value, typename Bounds::index_type> make_stride(const Bounds& bnd) _NOEXCEPT { - return bnd.strides; + return bnd.strides(); } // Make a stride vector from bounds, assuming continugous memory. @@ -1358,6 +1378,7 @@ public: template _CONSTEXPR size_type extent() const _NOEXCEPT { + static_assert(Dim < rank, "dimension should be less than rank (dimension count starts from 0)"); return m_bounds.template extent(); } _CONSTEXPR size_type size() const _NOEXCEPT @@ -1372,11 +1393,13 @@ public: { return m_pdata; } - template > + template 1), typename Ret = std::enable_if_t> _CONSTEXPR Ret operator[](size_type idx) const { + fail_fast_assert(idx < m_bounds.size(), "index is out of bounds of the array"); const size_type ridx = idx * m_bounds.stride(); - fail_fast_assert(ridx < m_bounds.size()); + + fail_fast_assert(ridx < m_bounds.total_size(), "index is out of bounds of the underlying data"); return Ret {m_pdata + ridx, m_bounds.slice()}; } @@ -1422,39 +1445,39 @@ public: template , std::remove_cv_t>::value>> _CONSTEXPR bool operator== (const basic_array_view & other) const _NOEXCEPT { - return m_bounds.size() == other.m_bounds.size() && - (m_pdata == other.m_pdata || std::equal(this->begin(), this->end(), other.begin())); + return m_bounds.size() == other.m_bounds.size() && + (m_pdata == other.m_pdata || std::equal(this->begin(), this->end(), other.begin())); } - template , std::remove_cv_t>::value>> - _CONSTEXPR bool operator!= (const basic_array_view & other) const _NOEXCEPT - { - return !(*this == other); - } + template , std::remove_cv_t>::value>> + _CONSTEXPR bool operator!= (const basic_array_view & other) const _NOEXCEPT + { + return !(*this == other); + } - template , std::remove_cv_t>::value>> - _CONSTEXPR bool operator< (const basic_array_view & other) const _NOEXCEPT - { - return std::lexicographical_compare(this->begin(), this->end(), other.begin(), other.end()); - } + template , std::remove_cv_t>::value>> + _CONSTEXPR bool operator< (const basic_array_view & other) const _NOEXCEPT + { + return std::lexicographical_compare(this->begin(), this->end(), other.begin(), other.end()); + } - template , std::remove_cv_t>::value>> - _CONSTEXPR bool operator<= (const basic_array_view & other) const _NOEXCEPT - { - return !(other < *this); - } + template , std::remove_cv_t>::value>> + _CONSTEXPR bool operator<= (const basic_array_view & other) const _NOEXCEPT + { + return !(other < *this); + } - template , std::remove_cv_t>::value>> - _CONSTEXPR bool operator> (const basic_array_view & other) const _NOEXCEPT - { - return (other < *this); - } + template , std::remove_cv_t>::value>> + _CONSTEXPR bool operator> (const basic_array_view & other) const _NOEXCEPT + { + return (other < *this); + } - template , std::remove_cv_t>::value>> - _CONSTEXPR bool operator>= (const basic_array_view & other) const _NOEXCEPT - { - return !(*this < other); - } + template , std::remove_cv_t>::value>> + _CONSTEXPR bool operator>= (const basic_array_view & other) const _NOEXCEPT + { + return !(*this < other); + } public: template friend class array_view; - using Base = basic_array_view::value_type, - static_bounds::size_type, FirstDimension, RestDimensions...>>; + using Base = basic_array_view::value_type, + static_bounds::size_type, FirstDimension, RestDimensions... >>; public: using typename Base::bounds_type; @@ -1622,6 +1645,8 @@ public: using typename Base::pointer; using typename Base::value_type; using typename Base::index_type; + using typename Base::iterator; + using typename Base::const_iterator; using Base::rank; public: @@ -1644,28 +1669,28 @@ public: _CONSTEXPR array_view() : Base(nullptr, bounds_type()) { } - + // from n-dimensions dynamic array (e.g. new int[m][4]) (precedence will be lower than the 1-dimension pointer) template , - typename Dummy = std::enable_if_t::value - && std::is_convertible::value>> - _CONSTEXPR array_view(T * const & data, size_type size) : Base(data, typename Helper::bounds_type{size}) + typename Dummy = std::enable_if_t::value + && std::is_convertible::value >> + _CONSTEXPR array_view(T * const & data, size_type size) : Base(data, typename Helper::bounds_type{ size }) { } // from n-dimensions static array template , typename Dummy = std::enable_if_t::value - && std::is_convertible::value>> - _CONSTEXPR array_view (T (&arr)[N]) : Base(arr, typename Helper::bounds_type()) + && std::is_convertible::value >> + _CONSTEXPR array_view(T(&arr)[N]) : Base(arr, typename Helper::bounds_type()) { } // from n-dimensions static array with size template , typename Dummy = std::enable_if_t::value - && std::is_convertible::value >> - _CONSTEXPR array_view(T(&arr)[N], size_type size) : Base(arr, typename Helper::bounds_type{ size }) + && std::is_convertible::value >> + _CONSTEXPR array_view(T(&arr)[N], size_type size) : Base(arr, typename Helper::bounds_type{ size }) { fail_fast_assert(size <= N); } @@ -1693,17 +1718,17 @@ public: // from containers. It must has .size() and .data() two function signatures template ::value - && std::is_convertible::value - && std::is_convertible, typename Base::bounds_type>::value - && std::is_same().size(), *std::declval().data())>, DataType>::value> + && std::is_convertible::value + && std::is_convertible, typename Base::bounds_type>::value + && std::is_same().size(), *std::declval().data())>, DataType>::value> > - _CONSTEXPR array_view (Cont& cont) : Base(static_cast(cont.data()), details::newBoundsHelper(cont.size())) + _CONSTEXPR array_view(Cont& cont) : Base(static_cast(cont.data()), details::newBoundsHelper(cont.size())) { } - + _CONSTEXPR array_view(const array_view &) = default; - + // convertible template ::value_type, static_bounds::size_type, FirstDimension, RestDimensions...>>, @@ -1717,30 +1742,29 @@ public: _CONSTEXPR array_view as_array_view(Dimensions2... dims) { static_assert(sizeof...(Dimensions2) > 0, "the target array_view must have at least one dimension."); - using BoundsType = typename array_view::bounds_type; + using BoundsType = typename array_view::bounds_type; auto tobounds = details::static_as_array_view_helper(dims..., details::Sep{}); details::verifyBoundsReshape(this->bounds(), tobounds); - return {this->data(), tobounds}; + return{ this->data(), tobounds }; } // to bytes array template ::value_type>>::value> - _CONSTEXPR auto as_bytes() const _NOEXCEPT -> + _CONSTEXPR auto as_bytes() const _NOEXCEPT -> array_view, static_cast(details::StaticSizeHelper::value)> { static_assert(Enabled, "The value_type of array_view must be standarded layout"); - return { reinterpret_cast(this->data()), this->bytes() }; + return{ reinterpret_cast(this->data()), this->bytes() }; } template ::value_type>>::value> - _CONSTEXPR auto as_writeable_bytes() const _NOEXCEPT -> + _CONSTEXPR auto as_writeable_bytes() const _NOEXCEPT -> array_view, static_cast(details::StaticSizeHelper::value)> { static_assert(Enabled, "The value_type of array_view must be standarded layout"); - return { reinterpret_cast(this->data()), this->bytes() }; + return{ reinterpret_cast(this->data()), this->bytes() }; } - // from bytes array template::value, typename Dummy = std::enable_if_t> _CONSTEXPR auto as_array_view() const _NOEXCEPT -> array_view(dynamic_range))> @@ -1748,7 +1772,7 @@ public: static_assert(std::is_standard_layout::value && (Base::bounds_type::static_size == dynamic_range || Base::bounds_type::static_size % sizeof(U) == 0), "Target type must be standard layout and its size must match the byte array size"); fail_fast_assert((this->bytes() % sizeof(U)) == 0); - return { reinterpret_cast(this->data()), this->bytes() / sizeof(U) }; + return{ reinterpret_cast(this->data()), this->bytes() / sizeof(U) }; } template::value, typename Dummy = std::enable_if_t> @@ -1757,22 +1781,22 @@ public: static_assert(std::is_standard_layout::value && (Base::bounds_type::static_size == dynamic_range || Base::bounds_type::static_size % sizeof(U) == 0), "Target type must be standard layout and its size must match the byte array size"); fail_fast_assert((this->bytes() % sizeof(U)) == 0); - return { reinterpret_cast(this->data()), this->bytes() / sizeof(U) }; + return{ reinterpret_cast(this->data()), this->bytes() / sizeof(U) }; } - + // section on linear space template _CONSTEXPR array_view first() const _NOEXCEPT { static_assert(bounds_type::static_size == dynamic_range || Count <= bounds_type::static_size, "Index is out of bound"); fail_fast_assert(bounds_type::static_size != dynamic_range || Count <= this->size()); // ensures we only check condition when needed - return { this->data(), Count }; + return{ this->data(), Count }; } _CONSTEXPR array_view first(size_type count) const _NOEXCEPT { fail_fast_assert(count <= this->size()); - return { this->data(), count }; + return{ this->data(), count }; } template @@ -1780,13 +1804,13 @@ public: { static_assert(bounds_type::static_size == dynamic_range || Count <= bounds_type::static_size, "Index is out of bound"); fail_fast_assert(bounds_type::static_size != dynamic_range || Count <= this->size()); - return { this->data() + this->size() - Count, Count }; + return{ this->data() + this->size() - Count, Count }; } _CONSTEXPR array_view last(size_type count) const _NOEXCEPT { fail_fast_assert(count <= this->size()); - return { this->data() + this->size() - count, count }; + return{ this->data() + this->size() - count, count }; } template @@ -1794,13 +1818,13 @@ public: { static_assert(bounds_type::static_size == dynamic_range || ((Offset == 0 || Offset < bounds_type::static_size) && Offset + Count <= bounds_type::static_size), "Index is out of bound"); fail_fast_assert(bounds_type::static_size != dynamic_range || ((Offset == 0 || Offset < this->size()) && Offset + Count <= this->size())); - return { this->data() + Offset, Count }; + return{ this->data() + Offset, Count }; } _CONSTEXPR array_view sub(size_type offset, size_type count) const _NOEXCEPT { fail_fast_assert((offset == 0 || offset < this->size()) && offset + count <= this->size()); - return { this->data() + offset, count }; + return{ this->data() + offset, count }; } // size @@ -1824,15 +1848,26 @@ public: // section _CONSTEXPR strided_array_view section(index_type origin, index_type extents) const { - return { &this->operator[](origin), strided_bounds {extents, details::make_stride(Base::bounds())}}; + size_type size = bounds().total_size() - bounds().linearize(origin); + return{ &this->operator[](origin), size, strided_bounds {extents, details::make_stride(Base::bounds())} }; + } + _CONSTEXPR reference operator[](const index_type& idx) const + { + return Base::operator[](idx); + } + template 1), typename Dummy = std::enable_if_t> + _CONSTEXPR array_view operator[](size_type idx) const + { + auto ret = Base::operator[](idx); + return{ ret.data(), ret.bounds() }; } - using Base::operator==; - using Base::operator!=; - using Base::operator<; - using Base::operator<=; - using Base::operator>; - using Base::operator>=; + using Base::operator==; + using Base::operator!=; + using Base::operator<; + using Base::operator<=; + using Base::operator>; + using Base::operator>=; }; template @@ -1889,6 +1924,9 @@ template class strided_array_view : public basic_array_view::value_type, strided_bounds::size_type>> { using Base = basic_array_view::value_type, strided_bounds::size_type>>; + + template + friend class strided_array_view; public: using Base::rank; using typename Base::bounds_type; @@ -1896,18 +1934,102 @@ public: using typename Base::pointer; using typename Base::value_type; using typename Base::index_type; + using typename Base::iterator; + using typename Base::const_iterator; + + // from static array of size N + template + strided_array_view(value_type(&values)[N], bounds_type bounds) : Base(values, std::move(bounds)) + { + fail_fast_assert(this->bounds().total_size() <= N, "Bounds cross data boundaries"); + } - strided_array_view (pointer ptr, bounds_type bounds): Base(ptr, std::move(bounds)) + // from raw data + strided_array_view(pointer ptr, size_type size, bounds_type bounds): Base(ptr, std::move(bounds)) { + fail_fast_assert(this->bounds().total_size() <= size, "Bounds cross data boundaries"); } + + // from array view template > - strided_array_view (array_view av, index_type strides): Base(av.data(), bounds_type{av.bounds().index_bounds(), strides}) + strided_array_view(array_view av, bounds_type bounds) : Base(av.data(), std::move(bounds)) + { + fail_fast_assert(this->bounds().total_size() <= av.bounds().total_size(), "Bounds cross data boundaries"); + } + + // convertible + template ::value_type, strided_bounds::size_type>>, + typename OtherBaseType = basic_array_view::value_type, strided_bounds::size_type>>, + typename Dummy = std::enable_if_t::value> + > + _CONSTEXPR strided_array_view(const strided_array_view &av): Base(static_cast::Base &>(av)) // static_cast is required { } - // section + + // convert from bytes + template ::value>> + strided_array_view as_strided_array_view() const + { + static_assert((sizeof(OtherValueType) >= sizeof(value_type)) && (sizeof(OtherValueType) % sizeof(value_type) == 0), "OtherValueType should have a size to contain a multiple of ValueTypes"); + auto d = sizeof(OtherValueType) / sizeof(value_type); + + size_type size = bounds().total_size() / d; + return{ (OtherValueType*)data(), size, bounds_type{ resize_extent(bounds().index_bounds(), d), resize_stride(bounds().strides(), d)} }; + } + strided_array_view section(index_type origin, index_type extents) const { - return { &this->operator[](origin), bounds_type {extents, details::make_stride(Base::bounds())}}; + size_type size = bounds().total_size() - bounds().linearize(origin); + return { &this->operator[](origin), size, bounds_type {extents, details::make_stride(Base::bounds())}}; + } + + _CONSTEXPR reference operator[](const index_type& idx) const + { + return Base::operator[](idx); + } + + template 1), typename Dummy = std::enable_if_t> + _CONSTEXPR strided_array_view operator[](size_type idx) const + { + auto ret = Base::operator[](idx); + return{ ret.data(), ret.bounds().total_size(), ret.bounds() }; + } + +private: + static index_type resize_extent(const index_type& extent, size_t d) + { + fail_fast_assert(extent[rank - 1] >= d && (extent[rank-1] % d == 0), "The last dimension of the array needs to contain a multiple of new type elements"); + + index_type ret = extent; + ret[rank - 1] /= d; + + return ret; + } + + template > + static index_type resize_stride(const index_type& strides, size_t d, void *p = 0) + { + fail_fast_assert(strides[rank - 1] == 1, "Only strided arrays with regular strides can be resized"); + + return strides; + } + + template 1), typename Dummy = std::enable_if_t> + static index_type resize_stride(const index_type& strides, size_t d) + { + fail_fast_assert(strides[rank - 1] == 1, "Only strided arrays with regular strides can be resized"); + fail_fast_assert(strides[rank - 2] >= d && (strides[rank - 2] % d == 0), "The strides must have contiguous chunks of memory that can contain a multiple of new type elements"); + + for (int i = rank - 2; i >= 0; --i) + { + fail_fast_assert((strides[i] >= strides[i + 1]) && (strides[i] % strides[i + 1] == 0), "Only strided arrays with regular strides can be resized"); + } + + index_type ret = strides / d; + ret[rank - 1] = 1; + + return ret; } }; @@ -1926,7 +2048,7 @@ private: const ArrayView * m_validator; void validateThis() const { - fail_fast_assert(m_pdata >= m_validator->m_pdata && m_pdata < m_validator->m_pdata + m_validator->size()); + fail_fast_assert(m_pdata >= m_validator->m_pdata && m_pdata < m_validator->m_pdata + m_validator->size(), "iterator is out of range of the array"); } contiguous_array_view_iterator (const ArrayView *container, bool isbegin = false) : m_pdata(isbegin ? container->m_pdata : container->m_pdata + container->size()), m_validator(container) { } @@ -2043,7 +2165,7 @@ private: template friend class basic_array_view; const ArrayView * m_container; - typename ArrayView::iterator m_itr; + typename ArrayView::bounds_type::iterator m_itr; general_array_view_iterator(const ArrayView *container, bool isbegin = false) : m_container(container), m_itr(isbegin ? m_container->bounds().begin() : m_container->bounds().end()) { diff --git a/include/fail_fast.h b/include/fail_fast.h index bd93cb7..ff5dbc4 100644 --- a/include/fail_fast.h +++ b/include/fail_fast.h @@ -27,12 +27,22 @@ namespace Guide // #if defined(SAFER_CPP_TESTING) -struct fail_fast : public std::exception {}; +struct fail_fast : public std::exception +{ + fail_fast() = default; + + explicit fail_fast(char const* const message) : + std::exception(message) + {} +}; + inline void fail_fast_assert(bool cond) { if (!cond) throw fail_fast(); } +inline void fail_fast_assert(bool cond, const char* const message) { if (!cond) throw fail_fast(message); } #else inline void fail_fast_assert(bool cond) { if (!cond) std::terminate(); } +inline void fail_fast_assert(bool cond, const char* const message) { if (!cond) std::terminate(); } #endif // SAFER_CPP_TESTING diff --git a/tests/array_view_tests.cpp b/tests/array_view_tests.cpp index 10c798e..6b35ab9 100644 --- a/tests/array_view_tests.cpp +++ b/tests/array_view_tests.cpp @@ -100,7 +100,7 @@ SUITE(array_view_tests) // out of bounds CHECK_THROW(av[1][3] = 3, fail_fast); - CHECK_THROW((av[{1, 3}] = 3), fail_fast); + CHECK_THROW((av[index<2>{1, 3}] = 3), fail_fast); CHECK_THROW(av[10][2], fail_fast); CHECK_THROW((av[{10,2}]), fail_fast); @@ -265,6 +265,877 @@ SUITE(array_view_tests) auto subsub = sub.section({1, 0, 0}, Guide::index<3>{1, 1, 1}); } + TEST(array_view_section) + { + std::vector data(5 * 10); + std::iota(begin(data), end(data), 0); + const array_view av = as_array_view(data).as_array_view(dim<5>(), dim<10>()); + + strided_array_view av_section_1 = av.section({ 1, 2 }, { 3, 4 }); + CHECK((av_section_1[{0, 0}] == 12)); + CHECK((av_section_1[{0, 1}] == 13)); + CHECK((av_section_1[{1, 0}] == 22)); + CHECK((av_section_1[{2, 3}] == 35)); + + strided_array_view av_section_2 = av_section_1.section({ 1, 2 }, { 2,2 }); + CHECK((av_section_2[{0, 0}] == 24)); + CHECK((av_section_2[{0, 1}] == 25)); + CHECK((av_section_2[{1, 0}] == 34)); + } + + TEST(strided_array_view_constructors) + { + // Check stride constructor + { + int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + const int carr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + strided_array_view sav1{ arr, {{9}, {1}} }; // T -> T + CHECK(sav1.bounds().index_bounds() == index<1>{ 9 }); + CHECK(sav1.bounds().stride() == 1); + CHECK(sav1[0] == 1 && sav1[8] == 9); + + + strided_array_view sav2{ carr, {{ 4 }, { 2 }} }; // const T -> const T + CHECK(sav2.bounds().index_bounds() == index<1>{ 4 }); + CHECK(sav2.bounds().strides() == index<1>{2}); + CHECK(sav2[0] == 1 && sav2[3] == 7); + + strided_array_view sav3{ arr, {{ 2, 2 },{ 6, 2 }} }; // T -> const T + CHECK((sav3.bounds().index_bounds() == index<2>{ 2, 2 })); + CHECK((sav3.bounds().strides() == index<2>{ 6, 2 })); + CHECK((sav3[{0, 0}] == 1 && sav3[{0, 1}] == 3 && sav3[{1, 0}] == 7)); + } + + // Check array_view constructor + { + int arr[] = { 1, 2 }; + + // From non-cv-qualified source + { + const array_view src{ arr }; + + strided_array_view sav{ src, {2, 1} }; + CHECK(sav.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav.bounds().strides() == index<1>{ 1 }); + CHECK(sav[1] == 2); + + strided_array_view sav_c{ {src}, {2, 1} }; + CHECK(sav_c.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_c.bounds().strides() == index<1>{ 1 }); + CHECK(sav_c[1] == 2); + + strided_array_view sav_v{ {src}, {2, 1} }; + CHECK(sav_v.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_v.bounds().strides() == index<1>{ 1 }); + CHECK(sav_v[1] == 2); + + strided_array_view sav_cv{ {src}, {2, 1} }; + CHECK(sav_cv.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_cv.bounds().strides() == index<1>{ 1 }); + CHECK(sav_cv[1] == 2); + } + + // From const-qualified source + { + const array_view src{ arr }; + + strided_array_view sav_c{ src, {2, 1} }; + CHECK(sav_c.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_c.bounds().strides() == index<1>{ 1 }); + CHECK(sav_c[1] == 2); + + strided_array_view sav_cv{ {src}, {2, 1} }; + CHECK(sav_cv.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_cv.bounds().strides() == index<1>{ 1 }); + CHECK(sav_cv[1] == 2); + } + + // From volatile-qualified source + { + const array_view src{ arr }; + + strided_array_view sav_v{ src, {2, 1} }; + CHECK(sav_v.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_v.bounds().strides() == index<1>{ 1 }); + CHECK(sav_v[1] == 2); + + strided_array_view sav_cv{ {src}, {2, 1} }; + CHECK(sav_cv.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_cv.bounds().strides() == index<1>{ 1 }); + CHECK(sav_cv[1] == 2); + } + + // From cv-qualified source + { + const array_view src{ arr }; + + strided_array_view sav_cv{ src, {2, 1} }; + CHECK(sav_cv.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav_cv.bounds().strides() == index<1>{ 1 }); + CHECK(sav_cv[1] == 2); + } + } + + // Check const-casting constructor + { + int arr[2] = { 4, 5 }; + + const array_view av(arr, 2); + array_view av2{ av }; + CHECK(av2[1] == 5); + + static_assert(std::is_convertible, array_view>::value, "ctor is not implicit!"); + + const strided_array_view src{ arr, {2, 1} }; + strided_array_view sav{ src }; + CHECK(sav.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav.bounds().stride() == 1); + CHECK(sav[1] == 5); + + static_assert(std::is_convertible, strided_array_view>::value, "ctor is not implicit!"); + } + + // Check copy constructor + { + int arr1[2] = { 3, 4 }; + const strided_array_view src1{ arr1, {2, 1} }; + strided_array_view sav1{ src1 }; + + CHECK(sav1.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav1.bounds().stride() == 1); + CHECK(sav1[0] == 3); + + int arr2[6] = { 1, 2, 3, 4, 5, 6 }; + const strided_array_view src2{ arr2, {{ 3, 2 }, { 2, 1 }} }; + strided_array_view sav2{ src2 }; + CHECK((sav2.bounds().index_bounds() == index<2>{ 3, 2 })); + CHECK((sav2.bounds().strides() == index<2>{ 2, 1 })); + CHECK((sav2[{0, 0}] == 1 && sav2[{2, 0}] == 5)); + } + + // Check const-casting assignment operator + { + int arr1[2] = { 1, 2 }; + int arr2[6] = { 3, 4, 5, 6, 7, 8 }; + + const strided_array_view src{ arr1, {{2}, {1}} }; + strided_array_view sav{ arr2, {{3}, {2}} }; + strided_array_view& sav_ref = (sav = src); + CHECK(sav.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav.bounds().strides() == index<1>{ 1 }); + CHECK(sav[0] == 1); + CHECK(&sav_ref == &sav); + } + + // Check copy assignment operator + { + int arr1[2] = { 3, 4 }; + int arr1b[1] = { 0 }; + const strided_array_view src1{ arr1, {2, 1} }; + strided_array_view sav1{ arr1b, {1, 1} }; + strided_array_view& sav1_ref = (sav1 = src1); + CHECK(sav1.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav1.bounds().strides() == index<1>{ 1 }); + CHECK(sav1[0] == 3); + CHECK(&sav1_ref == &sav1); + + const int arr2[6] = { 1, 2, 3, 4, 5, 6 }; + const int arr2b[1] = { 0 }; + const strided_array_view src2{ arr2, {{ 3, 2 },{ 2, 1 }} }; + strided_array_view sav2{ arr2b, {{ 1, 1 },{ 1, 1 }} }; + strided_array_view& sav2_ref = (sav2 = src2); + CHECK((sav2.bounds().index_bounds() == index<2>{ 3, 2 })); + CHECK((sav2.bounds().strides() == index<2>{ 2, 1 })); + CHECK((sav2[{0, 0}] == 1 && sav2[{2, 0}] == 5)); + CHECK(&sav2_ref == &sav2); + } + } + + TEST(strided_array_view_slice) + { + std::vector data(5 * 10); + std::iota(begin(data), end(data), 0); + const array_view src = as_array_view(data).as_array_view(dim<5>(), dim<10>()); + + const strided_array_view sav{ src, {{5, 10}, {10, 1}} }; +#ifdef CONFIRM_COMPILATION_ERRORS + const strided_array_view csav{ {src},{ { 5, 10 },{ 10, 1 } } }; +#endif + const strided_array_view csav{ array_view{ src }, { { 5, 10 },{ 10, 1 } } }; + + strided_array_view sav_sl = sav[2]; + CHECK(sav_sl[0] == 20); + CHECK(sav_sl[9] == 29); + + strided_array_view csav_sl = sav[3]; + CHECK(csav_sl[0] == 30); + CHECK(csav_sl[9] == 39); + + CHECK(sav[4][0] == 40); + CHECK(sav[4][9] == 49); + } + + TEST(strided_array_view_column_major) + { + // strided_array_view may be used to accomodate more peculiar + // use cases, such as column-major multidimensional array + // (aka. "FORTRAN" layout). + + int cm_array[3 * 5] = { + 1, 4, 7, 10, 13, + 2, 5, 8, 11, 14, + 3, 6, 9, 12, 15 + }; + strided_array_view cm_sav{ cm_array, {{ 5, 3 },{ 1, 5 }} }; + + // Accessing elements + CHECK((cm_sav[{0, 0}] == 1)); + CHECK((cm_sav[{0, 1}] == 2)); + CHECK((cm_sav[{1, 0}] == 4)); + CHECK((cm_sav[{4, 2}] == 15)); + + // Slice + strided_array_view cm_sl = cm_sav[3]; + + CHECK(cm_sl[0] == 10); + CHECK(cm_sl[1] == 11); + CHECK(cm_sl[2] == 12); + + // Section + strided_array_view cm_sec = cm_sav.section( { 2, 1 }, { 3, 2 }); + + CHECK((cm_sec.bounds().index_bounds() == index<2>{3, 2})); + CHECK((cm_sec[{0, 0}] == 8)); + CHECK((cm_sec[{0, 1}] == 9)); + CHECK((cm_sec[{1, 0}] == 11)); + CHECK((cm_sec[{2, 1}] == 15)); + } + + TEST(strided_array_view_bounds) + { + int arr[] = { 0, 1, 2, 3 }; + array_view av(arr); + + { + // incorrect sections + + CHECK_THROW(av.section(0, 0)[0], fail_fast); + CHECK_THROW(av.section(1, 0)[0], fail_fast); + CHECK_THROW(av.section(1, 1)[1], fail_fast); + + CHECK_THROW(av.section(2, 5), fail_fast); + CHECK_THROW(av.section(5, 2), fail_fast); + CHECK_THROW(av.section(5, 0), fail_fast); + CHECK_THROW(av.section(0, 5), fail_fast); + CHECK_THROW(av.section(5, 5), fail_fast); + } + + { + // zero stride + strided_array_view sav{ av, {{4}, {}} }; + CHECK(sav[0] == 0); + CHECK(sav[3] == 0); + CHECK_THROW(sav[4], fail_fast); + } + + { + // zero extent + strided_array_view sav{ av,{ {},{1} } }; + CHECK_THROW(sav[0], fail_fast); + } + + { + // zero extent and stride + strided_array_view sav{ av,{ {},{} } }; + CHECK_THROW(sav[0], fail_fast); + } + + { + // strided array ctor with matching strided bounds + strided_array_view sav{ arr,{ 4, 1 } }; + CHECK(sav.bounds().index_bounds() == index<1>{ 4 }); + CHECK(sav[3] == 3); + CHECK_THROW(sav[4], fail_fast); + } + + { + // strided array ctor with smaller strided bounds + strided_array_view sav{ arr,{ 2, 1 } }; + CHECK(sav.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav[1] == 1); + CHECK_THROW(sav[2], fail_fast); + } + + { + // strided array ctor with fitting irregular bounds + strided_array_view sav{ arr,{ 2, 3 } }; + CHECK(sav.bounds().index_bounds() == index<1>{ 2 }); + CHECK(sav[0] == 0); + CHECK(sav[1] == 3); + CHECK_THROW(sav[2], fail_fast); + } + + { + // bounds cross data boundaries - from static arrays + CHECK_THROW((strided_array_view { arr, { 3, 2 } }), fail_fast); + CHECK_THROW((strided_array_view { arr, { 3, 3 } }), fail_fast); + CHECK_THROW((strided_array_view { arr, { 4, 5 } }), fail_fast); + CHECK_THROW((strided_array_view { arr, { 5, 1 } }), fail_fast); + CHECK_THROW((strided_array_view { arr, { 5, 5 } }), fail_fast); + } + + { + // bounds cross data boundaries - from array view + CHECK_THROW((strided_array_view { av, { 3, 2 } }), fail_fast); + CHECK_THROW((strided_array_view { av, { 3, 3 } }), fail_fast); + CHECK_THROW((strided_array_view { av, { 4, 5 } }), fail_fast); + CHECK_THROW((strided_array_view { av, { 5, 1 } }), fail_fast); + CHECK_THROW((strided_array_view { av, { 5, 5 } }), fail_fast); + } + + { + // bounds cross data boundaries - from dynamic arrays + CHECK_THROW((strided_array_view { av.data(), 4, { 3, 2 } }), fail_fast); + CHECK_THROW((strided_array_view { av.data(), 4, { 3, 3 } }), fail_fast); + CHECK_THROW((strided_array_view { av.data(), 4, { 4, 5 } }), fail_fast); + CHECK_THROW((strided_array_view { av.data(), 4, { 5, 1 } }), fail_fast); + CHECK_THROW((strided_array_view { av.data(), 4, { 5, 5 } }), fail_fast); + CHECK_THROW((strided_array_view { av.data(), 2, { 2, 2 } }), fail_fast); + } + +#ifdef CONFIRM_COMPILATION_ERRORS + { + strided_array_view sav0{ av.data(), { 3, 2 } }; + strided_array_view sav1{ arr, { 1 } }; + strided_array_view sav2{ arr, { 1,1,1 } }; + strided_array_view sav3{ av, { 1 } }; + strided_array_view sav4{ av, { 1,1,1 } }; + strided_array_view sav5{ av.as_array_view(dim<2>(), dim<2>()), { 1 } }; + strided_array_view sav6{ av.as_array_view(dim<2>(), dim<2>()), { 1,1,1 } }; + strided_array_view sav7{ av.as_array_view(dim<2>(), dim<2>()), { { 1,1 },{ 1,1 },{ 1,1 } } }; + } +#endif + + { + // stride initializer list size should match the rank of the array + CHECK_THROW((index<1>{ 0,1 }), fail_fast); + CHECK_THROW((strided_array_view{ arr, {1, {1,1}} }), fail_fast); + CHECK_THROW((strided_array_view{ arr, {{1,1 }, {1,1}} }), fail_fast); + + CHECK_THROW((strided_array_view{ av, {1, {1,1}} }), fail_fast); + CHECK_THROW((strided_array_view{ av, {{1,1 }, {1,1}} }), fail_fast); + + CHECK_THROW((strided_array_view{ av.as_array_view(dim<2>(), dim<2>()), {{1}, {1}} }), fail_fast); + CHECK_THROW((strided_array_view{ av.as_array_view(dim<2>(), dim<2>()), {{1}, {1,1,1}} }), fail_fast); + CHECK_THROW((strided_array_view{ av.as_array_view(dim<2>(), dim<2>()), {{1,1,1}, {1}} }), fail_fast); + } + + } + + TEST(strided_array_view_type_conversion) + { + int arr[] = { 0, 1, 2, 3 }; + array_view av(arr); + + { + strided_array_view sav{ av.data(), av.size(), { av.size() / 2, 2 } }; +#ifdef CONFIRM_COMPILATION_ERRORS + strided_array_view lsav1 = sav.as_strided_array_view(); +#endif + } + { + strided_array_view sav{ av, { av.size() / 2, 2 } }; +#ifdef CONFIRM_COMPILATION_ERRORS + strided_array_view lsav1 = sav.as_strided_array_view(); +#endif + } + + array_view bytes = av.as_bytes(); + + // retype strided array with regular strides - from raw data + { + strided_bounds<2> bounds{ { 2, bytes.size() / 4 }, { bytes.size() / 2, 1 } }; + strided_array_view sav2{ bytes.data(), bytes.size(), bounds }; + strided_array_view sav3 = sav2.as_strided_array_view(); + CHECK(sav3[0][0] == 0); + CHECK(sav3[1][0] == 2); + CHECK_THROW(sav3[1][1], fail_fast); + CHECK_THROW(sav3[0][1], fail_fast); + } + + // retype strided array with regular strides - from array_view + { + strided_bounds<2> bounds{ { 2, bytes.size() / 4 }, { bytes.size() / 2, 1 } }; + array_view bytes2 = bytes.as_array_view(dim<2>(), dim<>(bytes.size() / 2)); + strided_array_view sav2{ bytes2, bounds }; + strided_array_view sav3 = sav2.as_strided_array_view(); + CHECK(sav3[0][0] == 0); + CHECK(sav3[1][0] == 2); + CHECK_THROW(sav3[1][1], fail_fast); + CHECK_THROW(sav3[0][1], fail_fast); + } + + // retype strided array with not enough elements - last dimension of the array is too small + { + strided_bounds<2> bounds{ { 4,2 },{ 4, 1 } }; + array_view bytes2 = bytes.as_array_view(dim<2>(), dim<>(bytes.size() / 2)); + strided_array_view sav2{ bytes2, bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + + // retype strided array with not enough elements - strides are too small + { + strided_bounds<2> bounds{ { 4,2 },{ 2, 1 } }; + array_view bytes2 = bytes.as_array_view(dim<2>(), dim<>(bytes.size() / 2)); + strided_array_view sav2{ bytes2, bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + + // retype strided array with not enough elements - last dimension does not divide by the new typesize + { + strided_bounds<2> bounds{ { 2,6 },{ 4, 1 } }; + array_view bytes2 = bytes.as_array_view(dim<2>(), dim<>(bytes.size() / 2)); + strided_array_view sav2{ bytes2, bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + + // retype strided array with not enough elements - strides does not divide by the new typesize + { + strided_bounds<2> bounds{ { 2, 1 },{ 6, 1 } }; + array_view bytes2 = bytes.as_array_view(dim<2>(), dim<>(bytes.size() / 2)); + strided_array_view sav2{ bytes2, bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + + // retype strided array with irregular strides - from raw data + { + strided_bounds<1> bounds{ bytes.size() / 2, 2 }; + strided_array_view sav2{ bytes.data(), bytes.size(), bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + + // retype strided array with irregular strides - from array_view + { + strided_bounds<1> bounds{ bytes.size() / 2, 2 }; + strided_array_view sav2{ bytes, bounds }; + CHECK_THROW(sav2.as_strided_array_view(), fail_fast); + } + } + + TEST(empty_arrays) + { +#ifdef CONFIRM_COMPILATION_ERRORS + { + array_view empty; + strided_array_view empty2; + strided_array_view empty3{ nullptr,{ 0, 1 } }; + } +#endif + + { + array_view empty_av(nullptr); + + CHECK(empty_av.bounds().index_bounds() == index<1>{ 0 }); + CHECK_THROW(empty_av[0], fail_fast); + CHECK_THROW(empty_av.begin()[0], fail_fast); + CHECK_THROW(empty_av.cbegin()[0], fail_fast); + + for (auto& v : empty_av) + { + CHECK(false); + } + } + + { + array_view empty_av = {}; + + CHECK(empty_av.bounds().index_bounds() == index<1>{ 0 }); + CHECK_THROW(empty_av[0], fail_fast); + CHECK_THROW(empty_av.begin()[0], fail_fast); + CHECK_THROW(empty_av.cbegin()[0], fail_fast); + + for (auto& v : empty_av) + { + CHECK(false); + } + } + + { + array_view empty_av(nullptr); + strided_array_view empty_sav{ empty_av, { 0, 1 } }; + + CHECK(empty_sav.bounds().index_bounds() == index<1>{ 0 }); + CHECK_THROW(empty_sav[0], fail_fast); + CHECK_THROW(empty_sav.begin()[0], fail_fast); + CHECK_THROW(empty_sav.cbegin()[0], fail_fast); + + for (auto& v : empty_sav) + { + CHECK(false); + } + } + + { + strided_array_view empty_sav{ nullptr, 0, { 0, 1 } }; + + CHECK(empty_sav.bounds().index_bounds() == index<1>{ 0 }); + CHECK_THROW(empty_sav[0], fail_fast); + CHECK_THROW(empty_sav.begin()[0], fail_fast); + CHECK_THROW(empty_sav.cbegin()[0], fail_fast); + + for (auto& v : empty_sav) + { + CHECK(false); + } + } + } + + TEST(index_constructor) + { + auto arr = new int[8]; + for (int i = 0; i < 4; ++i) + { + arr[2 * i] = 4 + i; + arr[2 * i + 1] = i; + } + + array_view av(arr, 8); + + size_t a[1] = { 0 }; + index<1> i = index<1>(a); + + CHECK(av[i] == 4); + + auto av2 = av.as_array_view(dim<4>(), dim<>(2)); + size_t a2[2] = { 0, 1 }; + index<2> i2 = index<2>(a2); + + CHECK(av2[i2] == 0); + CHECK(av2[0][i] == 4); + + delete[] arr; + } + + TEST(index_operations) + { + size_t a[3] = { 0, 1, 2 }; + size_t b[3] = { 3, 4, 5 }; + index<3> i = a; + index<3> j = b; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + + { + index<3> k = i + j; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 3); + CHECK(k[1] == 5); + CHECK(k[2] == 7); + } + + { + index<3> k = i * 3; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 0); + CHECK(k[1] == 3); + CHECK(k[2] == 6); + } + + { + index<2> k = index<2>::shift_left(i); + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 1); + CHECK(k[1] == 2); + } + + } + + void iterate_second_column(array_view av) + { + auto length = av.size() / 2; + + // view to the second column + auto section = av.section({ 0,1 }, { length,1 }); + + CHECK(section.size() == length); + for (unsigned int i = 0; i < section.size(); ++i) + { + CHECK(section[i][0] == av[i][1]); + } + + for (unsigned int i = 0; i < section.size(); ++i) + { + CHECK(section[index<2>({ i,0 })] == av[i][1]); + } + + CHECK(section.bounds().index_bounds()[0] == length); + CHECK(section.bounds().index_bounds()[1] == 1); + for (unsigned int i = 0; i < section.bounds().index_bounds()[0]; ++i) + { + for (unsigned int j = 0; j < section.bounds().index_bounds()[1]; ++j) + { + CHECK(section[index<2>({ i,j })] == av[i][1]); + } + } + + unsigned int idx = 0; + for (auto num : section) + { + CHECK(num == av[idx][1]); + idx++; + } + } + + TEST(array_view_section_iteration) + { + int arr[4][2] = { { 4,0 },{ 5,1 },{ 6,2 },{ 7,3 } }; + + // static bounds + { + array_view av = arr; + iterate_second_column(av); + } + // first bound is dynamic + { + array_view av = arr; + iterate_second_column(av); + } + // second bound is dynamic + { + array_view av = arr; + iterate_second_column(av); + } + // both bounds are dynamic + { + array_view av(arr, 4); + iterate_second_column(av); + } + } + + TEST(dynamic_array_view_section_iteration) + { + unsigned int height = 4, width = 2; + unsigned int size = height * width; + + auto arr = new int[size]; + for (int unsigned i = 0; i < size; ++i) + { + arr[i] = i; + } + + auto av = as_array_view(arr, size); + + // first bound is dynamic + { + array_view av2 = av.as_array_view(dim<>(height), dim<>(width)); + iterate_second_column(av2); + } + // second bound is dynamic + { + array_view av2 = av.as_array_view(dim<>(height), dim<>(width)); + iterate_second_column(av2); + } + // both bounds are dynamic + { + array_view av2 = av.as_array_view(dim<>(height), dim<>(width)); + iterate_second_column(av2); + } + + delete[] arr; + } + + void iterate_every_other_element(array_view av) + { + // pick every other element + + auto length = av.size() / 2; + auto bounds = strided_bounds<1>({ length }, { 2 }); + strided_array_view strided(&av.data()[1], av.size() - 1, bounds); + + CHECK(strided.size() == length); + CHECK(strided.bounds().index_bounds()[0] == length); + for (unsigned int i = 0; i < strided.size(); ++i) + { + CHECK(strided[i] == av[2 * i + 1]); + } + + int idx = 0; + for (auto num : strided) + { + CHECK(num == av[2 * idx + 1]); + idx++; + } + } + + TEST(strided_array_view_section_iteration) + { + int arr[8] = {4,0,5,1,6,2,7,3}; + + // static bounds + { + array_view av(arr, 8); + iterate_every_other_element(av); + } + + // dynamic bounds + { + array_view av(arr, 8); + iterate_every_other_element(av); + } + } + + TEST(dynamic_strided_array_view_section_iteration) + { + auto arr = new int[8]; + for (int i = 0; i < 4; ++i) + { + arr[2 * i] = 4 + i; + arr[2 * i + 1] = i; + } + + auto av = as_array_view(arr, 8); + iterate_every_other_element(av); + + delete[] arr; + } + + void iterate_second_slice(array_view av) + { + int expected[6] = { 2,3,10,11,18,19 }; + auto section = av.section({ 0,1,0 }, { 3,1,2 }); + + for (unsigned int i = 0; i < section.extent<0>(); ++i) + { + for (unsigned int j = 0; j < section.extent<1>(); ++j) + for (unsigned int k = 0; k < section.extent<2>(); ++k) + CHECK(section[index<3>({ i,j,k })] == expected[2 * i + 2 * j + k]); + } + + for (unsigned int i = 0; i < section.extent<0>(); ++i) + { + for (unsigned int j = 0; j < section.extent<1>(); ++j) + for (unsigned int k = 0; k < section.extent<2>(); ++k) + CHECK(section[i][j][k] == expected[2 * i + 2 * j + k]); + } + + int i = 0; + for (auto num : section) + { + CHECK(num == expected[i]); + i++; + } + } + + TEST(strided_array_view_section_iteration_3d) + { + int arr[3][4][2]; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 4; ++j) + for (unsigned int k = 0; k < 2; ++k) + arr[i][j][k] = 8 * i + 2 * j + k; + } + + { + array_view av = arr; + iterate_second_slice(av); + } + } + + TEST(dynamic_strided_array_view_section_iteration_3d) + { + unsigned int height = 12, width = 2; + unsigned int size = height * width; + + auto arr = new int[size]; + for (int unsigned i = 0; i < size; ++i) + { + arr[i] = i; + } + + { + auto av = as_array_view(arr, 24).as_array_view(dim<3>(),dim<4>(),dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_array_view(arr, 24).as_array_view(dim<>(3), dim<4>(), dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_array_view(arr, 24).as_array_view(dim<3>(), dim<>(4), dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_array_view(arr, 24).as_array_view(dim<3>(), dim<4>(), dim<>(2)); + iterate_second_slice(av); + } + delete[] arr; + } + + TEST(strided_array_view_conversion) + { + // get an array_view of 'c' values from the list of X's + + struct X { int a; int b; int c; }; + + X arr[4] = { { 0,1,2 },{ 3,4,5 },{ 6,7,8 },{ 9,10,11 } }; + + auto s = sizeof(int) / sizeof(byte); + auto d2 = 3 * s; + auto d1 = sizeof(int) * 12 / d2; + + // convert to 4x12 array of bytes + auto av = as_array_view(arr, 4).as_bytes().as_array_view(dim<>(d1), dim<>(d2)); + + CHECK(av.bounds().index_bounds()[0] == 4); + CHECK(av.bounds().index_bounds()[1] == 12); + + // get the last 4 columns + auto section = av.section({ 0, 2 * s }, { 4, s }); // { { arr[0].c[0], arr[0].c[1], arr[0].c[2], arr[0].c[3] } , { arr[1].c[0], ... } , ... } + + // convert to array 4x1 array of integers + auto cs = section.as_strided_array_view(); // { { arr[0].c }, {arr[1].c } , ... } + + CHECK(cs.bounds().index_bounds()[0] == 4); + CHECK(cs.bounds().index_bounds()[1] == 1); + + // transpose to 1x4 array + strided_bounds<2> reverse_bounds{ + { cs.bounds().index_bounds()[1] , cs.bounds().index_bounds()[0] }, + { cs.bounds().strides()[1], cs.bounds().strides()[0] } + }; + + strided_array_view transposed{ cs.data(), cs.bounds().total_size(), reverse_bounds }; + + // slice to get a one-dimensional array of c's + strided_array_view result = transposed[0]; + + CHECK(result.bounds().index_bounds()[0] == 4); + CHECK_THROW(result.bounds().index_bounds()[1], fail_fast); + + int i = 0; + for (auto& num : result) + { + CHECK(num == arr[i].c); + i++; + } + + } TEST(constructors) { @@ -303,8 +1174,6 @@ SUITE(array_view_tests) DerivedClass *p = nullptr; array_view av11(p, 0); #endif - - } TEST(copyandassignment) @@ -510,13 +1379,13 @@ SUITE(array_view_tests) array_view av = cav; #else array_view av = a; - array_view cav = av; + array_view cav = av; #endif AssertContentsMatch(av, cav); } TEST(FixedSizeConversions) - { + { int arr[] = { 1, 2, 3, 4 }; // converting to an array_view from an equal size array is ok @@ -598,7 +1467,7 @@ SUITE(array_view_tests) CHECK_THROW(f(), fail_fast); } - TEST(AsWriteableBytes) + TEST(AsWriteableBytes) { int a[] = { 1, 2, 3, 4 }; @@ -627,117 +1496,116 @@ SUITE(array_view_tests) } - TEST(ArrayViewComparison) { - { - int arr[10][2]; - auto av1 = as_array_view(arr); - array_view av2 = av1; + { + int arr[10][2]; + auto av1 = as_array_view(arr); + array_view av2 = av1; - CHECK(av1 == av2); + CHECK(av1 == av2); - array_view, 20> av3 = av1.as_array_view(dim<>(20)); - CHECK(av3 == av2 && av3 == av1); - } + array_view, 20> av3 = av1.as_array_view(dim<>(20)); + CHECK(av3 == av2 && av3 == av1); + } - { - auto av1 = nullptr; - auto av2 = nullptr; - CHECK(av1 == av2); - CHECK(!(av1 != av2)); - CHECK(!(av1 < av2)); - CHECK(av1 <= av2); - CHECK(!(av1 > av2)); - CHECK(av1 >= av2); - CHECK(av2 == av1); - CHECK(!(av2 != av1)); - CHECK(!(av2 < av1)); - CHECK(av2 <= av1); - CHECK(!(av2 > av1)); - CHECK(av2 >= av1); - } + { + auto av1 = nullptr; + auto av2 = nullptr; + CHECK(av1 == av2); + CHECK(!(av1 != av2)); + CHECK(!(av1 < av2)); + CHECK(av1 <= av2); + CHECK(!(av1 > av2)); + CHECK(av1 >= av2); + CHECK(av2 == av1); + CHECK(!(av2 != av1)); + CHECK(!(av2 < av1)); + CHECK(av2 <= av1); + CHECK(!(av2 > av1)); + CHECK(av2 >= av1); + } - { - int arr[] = { 2, 1 }; // bigger + { + int arr[] = { 2, 1 }; // bigger - array_view av1 = nullptr; - array_view av2 = arr; + array_view av1 = nullptr; + array_view av2 = arr; - CHECK(av1 != av2); - CHECK(av2 != av1); - CHECK(!(av1 == av2)); - CHECK(!(av2 == av1)); - CHECK(av1 < av2); - CHECK(!(av2 < av1)); - CHECK(av1 <= av2); - CHECK(!(av2 <= av1)); - CHECK(av2 > av1); - CHECK(!(av1 > av2)); - CHECK(av2 >= av1); - CHECK(!(av1 >= av2)); - } + CHECK(av1 != av2); + CHECK(av2 != av1); + CHECK(!(av1 == av2)); + CHECK(!(av2 == av1)); + CHECK(av1 < av2); + CHECK(!(av2 < av1)); + CHECK(av1 <= av2); + CHECK(!(av2 <= av1)); + CHECK(av2 > av1); + CHECK(!(av1 > av2)); + CHECK(av2 >= av1); + CHECK(!(av1 >= av2)); + } - { - int arr1[] = { 1, 2 }; - int arr2[] = { 1, 2 }; - array_view av1 = arr1; - array_view av2 = arr2; + { + int arr1[] = { 1, 2 }; + int arr2[] = { 1, 2 }; + array_view av1 = arr1; + array_view av2 = arr2; - CHECK(av1 == av2); - CHECK(!(av1 != av2)); - CHECK(!(av1 < av2)); - CHECK(av1 <= av2); - CHECK(!(av1 > av2)); - CHECK(av1 >= av2); - CHECK(av2 == av1); - CHECK(!(av2 != av1)); - CHECK(!(av2 < av1)); - CHECK(av2 <= av1); - CHECK(!(av2 > av1)); - CHECK(av2 >= av1); - } + CHECK(av1 == av2); + CHECK(!(av1 != av2)); + CHECK(!(av1 < av2)); + CHECK(av1 <= av2); + CHECK(!(av1 > av2)); + CHECK(av1 >= av2); + CHECK(av2 == av1); + CHECK(!(av2 != av1)); + CHECK(!(av2 < av1)); + CHECK(av2 <= av1); + CHECK(!(av2 > av1)); + CHECK(av2 >= av1); + } - { - int arr[] = { 1, 2, 3 }; + { + int arr[] = { 1, 2, 3 }; - array_view av1 = { &arr[0], 2 }; // shorter - array_view av2 = arr; // longer + array_view av1 = { &arr[0], 2 }; // shorter + array_view av2 = arr; // longer - CHECK(av1 != av2); - CHECK(av2 != av1); - CHECK(!(av1 == av2)); - CHECK(!(av2 == av1)); - CHECK(av1 < av2); - CHECK(!(av2 < av1)); - CHECK(av1 <= av2); - CHECK(!(av2 <= av1)); - CHECK(av2 > av1); - CHECK(!(av1 > av2)); - CHECK(av2 >= av1); - CHECK(!(av1 >= av2)); - } + CHECK(av1 != av2); + CHECK(av2 != av1); + CHECK(!(av1 == av2)); + CHECK(!(av2 == av1)); + CHECK(av1 < av2); + CHECK(!(av2 < av1)); + CHECK(av1 <= av2); + CHECK(!(av2 <= av1)); + CHECK(av2 > av1); + CHECK(!(av1 > av2)); + CHECK(av2 >= av1); + CHECK(!(av1 >= av2)); + } - { - int arr1[] = { 1, 2 }; // smaller - int arr2[] = { 2, 1 }; // bigger + { + int arr1[] = { 1, 2 }; // smaller + int arr2[] = { 2, 1 }; // bigger - array_view av1 = arr1; - array_view av2 = arr2; + array_view av1 = arr1; + array_view av2 = arr2; - CHECK(av1 != av2); - CHECK(av2 != av1); - CHECK(!(av1 == av2)); - CHECK(!(av2 == av1)); - CHECK(av1 < av2); - CHECK(!(av2 < av1)); - CHECK(av1 <= av2); - CHECK(!(av2 <= av1)); - CHECK(av2 > av1); - CHECK(!(av1 > av2)); - CHECK(av2 >= av1); - CHECK(!(av1 >= av2)); - } + CHECK(av1 != av2); + CHECK(av2 != av1); + CHECK(!(av1 == av2)); + CHECK(!(av2 == av1)); + CHECK(av1 < av2); + CHECK(!(av2 < av1)); + CHECK(av1 <= av2); + CHECK(!(av2 <= av1)); + CHECK(av2 > av1); + CHECK(!(av1 > av2)); + CHECK(av2 >= av1); + CHECK(!(av1 >= av2)); + } } }