├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md └── array_view ├── array_view.h └── array_view_test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]uild* 2 | bin 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(array_view) 3 | 4 | option(AV_BUILD_TESTS "Build unit tests. Requires GTest." ON) 5 | option(AV_BUILD_GTEST "Build GTest along with this project (ON) or use an existing installation (OFF)." ON) 6 | 7 | # An interface target for array_view 8 | add_library(array_view INTERFACE) 9 | add_library(array_view::array_view ALIAS array_view) 10 | 11 | target_include_directories(array_view INTERFACE .) 12 | target_compile_features(array_view INTERFACE cxx_std_14) 13 | 14 | # Export target, importable from build directory 15 | export(TARGETS array_view 16 | NAMESPACE array_view:: 17 | FILE array_viewConfig.cmake 18 | ) 19 | 20 | if(AV_BUILD_TESTS) 21 | 22 | add_executable(av_test "array_view/array_view_test.cpp") 23 | target_link_libraries(av_test array_view::array_view ) 24 | 25 | if(AV_BUILD_GTEST) 26 | set(GTEST_ROOT $ENV{GTEST_ROOT} CACHE PATH "Path to GTest directory") 27 | if("${GTEST_ROOT}" STREQUAL "") 28 | message(FATAL_ERROR "To build GTest, set GTEST_ROOT to point to your GTest folder, " 29 | "or disable building with this project.") 30 | endif() 31 | 32 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/gtest") 33 | add_subdirectory("${GTEST_ROOT}" "${CMAKE_BINARY_DIR}/gtest") 34 | include_directories("${GTEST_ROOT}/include") 35 | target_link_libraries(av_test gtest gtest_main pthread) 36 | else() 37 | find_package(GTest REQUIRED) 38 | target_link_libraries(av_test GTest::GTest GTest::Main) 39 | endif() 40 | 41 | endif() 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-clause “Simplified” License 2 | 3 | Copyright (c) 2015, Tom Ward 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## array_view 2 | 3 | An implementation of the ISO C++ proposal [_Multidimensional bounds, offset and array_view_][1] by Mendakiewicz & Sutter. This implements the original proposal [N3851][1] up to the last [revision 7][3]. As I understand it, the proposal was with an aim for inclusion in C++17 although is no longer under consideration in it's current form. For later contributions, see the successive proposal [p0122r0][4] (and related [GSL][5] implementation), and alternative proposals such as [P0546][6]. 4 | 5 | Principles behind the proposal are the representation of a multidimensional array as a view over contiguous (or strided) data and the straightforward expression for multidimensional indexing into these arrays. The result is a safe, bounded view that works naturally with existing algorithms while maintaining a nice, expressive syntax. 6 | 7 | The library is header-only with no external dependencies. If you want to build the tests there is a CMakeLists.txt for building with CMake and Google Test. 8 | 9 | 10 | ### Example usage 11 | 12 | The following is a _very brief_ overview of some typical usage. For more details, see the [original proposal][1]. 13 | 14 | ```cpp 15 | int X = 12; 16 | int Y = 8; 17 | int Z = 6; 18 | 19 | vector vec(X*Y*Z); 20 | 21 | bounds<3> bnds = {X,Y,Z}; 22 | array_view av(vec, bnds); 23 | ``` 24 | 25 | A `bounds` defines a multidimensional bounds into an array space of rank `Rank` over which we wish to construct a view. Above, we construct an `array_view` over our `vec` using the bounds `bnds`. Note that a view has reference semantics so our `vec` must stay valid for the lifetime of `av`. 26 | 27 | An `offset` represents a multidimensional index, an integral vector into this array space. Both `offset` and `bounds` share similar interfaces. To access an element: 28 | 29 | ```cpp 30 | offset<3> idx = {5,3,2}; 31 | 32 | av[idx] = 28; 33 | av[{5,3,2}] = 28; // also fine 34 | ``` 35 | 36 | The `bounds` class provides `begin()` and `end()` member functions that return iterators that iterate over each index of the bounds in turn. Iteration increments first along the least significant dimension (contiguous for non-strided data). This facility allows iteration with a simple range-based for loop: 37 | 38 | ```cpp 39 | for (auto& idx : av.bounds()) 40 | { 41 | auto i = idx[0]; 42 | auto j = idx[1]; 43 | auto k = idx[2]; // least significant dimension, incremented first 44 | 45 | av[idx] = i**2 + j - k; 46 | } 47 | ``` 48 | 49 | Above, `bounds()` returns the current bounds to the array_view. An alternative would be to construct a `bounds_iterator` directly. A `bounds_iterator` is a (nearly) random access iterator whose `value_type` is an `offset` into the array space. Iterating a `bounds_iterator` is typically done over the range defined by `bounds`: 50 | 51 | ```cpp 52 | bounds_iterator<3> first = begin(av.bounds()); 53 | bounds_iterator<3> last = end(av.bounds()); 54 | 55 | // Dereferencing an iterator returns a `const offset`. Indices are always immutable. 56 | for_each(first, last, [&av](const offset<3>& idx) { 57 | auto i = idx[0]; 58 | auto j = idx[1]; 59 | auto k = idx[2]; 60 | assert(av[idx] == i**2 + j - k); 61 | }); 62 | ``` 63 | 64 | #### Slicing 65 | 66 | Slicing returns a lower dimensional 'slice' as a new view of the same data. Slices always slice from the most significant dimensions (here, `x`): 67 | 68 | ```cpp 69 | int x0 = 5, y0 = 3; 70 | array_view slice2d = av[x0]; // a 2d slice in the yz plane 71 | array_view slice1d = av[x0][y0]; // a row in z, also the contiguous dimension 72 | 73 | assert( slice2d[{3,2}] == 28 ); 74 | ``` 75 | 76 | For dimensions of rank 1, you can omit the `initializer_list`: 77 | 78 | ```cpp 79 | assert( slice1d[2] == 28 ); 80 | ``` 81 | 82 | #### Sectioning 83 | 84 | Sectioning creates a new view given a new bounds that fully subsumes the original. Sections must be of the same rank as the original. All views created from sections return a `strided_array_view`: 85 | 86 | ```cpp 87 | offset<3> origin = {6,3,2}; 88 | bounds<3> window = {3,3,2}; 89 | auto section = av.section(origin, window); 90 | 91 | // Work with just this section of the data 92 | int sum = std::accumulate(begin(section.bounds()), end(section.bounds()), 0, 93 | [&](int a, offset<3> idx) { 94 | return a + section[idx]; 95 | }); 96 | ``` 97 | 98 | #### Strided data 99 | 100 | An additional class `strided_array_view` relaxes the requirement that the least significant dimension of the referenced data must be contiguous. Strided views commonly arise by sectioning an `array_view`, but it is possible to construct a strided view directly with a pointer to data in memory for which you must provide the stride. 101 | 102 | For example, the following creates a new view of `vec` that includes every third element and is one-third the extent in the Z dimension: 103 | 104 | ```cpp 105 | array_view av = .. // as before 106 | 107 | // The strides in X and Y are as calculated as for `av`, but the stride in Z is no longer 1 108 | bounds<3> newExtents = {X, Y, Z/3}; 109 | offset<3> newStride = {av.stride()[0], av.stride()[1], 3}; 110 | strided_array_view sav(vec.data(), newExtents, newStride); 111 | 112 | for (auto& idx : sav.bounds()) 113 | { 114 | // do something with sav[idx] 115 | } 116 | ``` 117 | 118 | Taking sliced views of contiguous data, sections of sliced views and views of views all compose and work naturally as you would expect. 119 | 120 | 121 | ### Acknowledgements 122 | 123 | This implementation follows the original proposal by Mendakiewicz & Sutter and subsequent revisions (latest [revision 7][3]). As noted in the proposal, the proposal itself builds on previous work by Callahan, Levanoni and Sutter for their work designing the original interfaces for C++ AMP. 124 | 125 | 126 | ### Limitations 127 | 128 | My experience implementing the standard is zero, I expect this to fall way short of a conforming implementation. However, as a practical solution it has served sufficient for my purposes, and at least supports a working set of tests. Also I thought this might make an interesting exercise. All comments & suggestions welcome. 129 | 130 | [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3851.pdf 131 | [2]: https://msdn.microsoft.com/en-us/library/hh265137.aspx 132 | [3]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4512.html 133 | [4]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0122r0.pdf 134 | [5]: https://github.com/Microsoft/GSL 135 | [6]: https://github.com/kokkos/array_ref/blob/master/proposals/P0546.rst 136 | 137 | -------------------------------------------------------------------------------- /array_view/array_view.h: -------------------------------------------------------------------------------- 1 | /* 2 | * array_view -- https://github.com/wardw/array_view 3 | * 4 | * Copyright (c) 2015, Tom Ward - All rights reserved. 5 | * BSD 2-clause “Simplified” License 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * + Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * + Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | /* 36 | template 37 | class offset 38 | { 39 | public: 40 | // constants and types 41 | static constexpr size_t rank = Rank; 42 | using reference = ptrdiff_t&; 43 | using const_reference = const ptrdiff_t&; 44 | using size_type = size_t; 45 | using value_type = ptrdiff_t; 46 | 47 | static_assert(Rank > 0, "Size of Rank must be greater than 0"); 48 | 49 | // construction 50 | constexpr offset() noexcept; 51 | template > 52 | constexpr offset(value_type v) noexcept; 53 | constexpr offset(std::initializer_list il); 54 | 55 | // element access 56 | constexpr reference operator[](size_type n); 57 | constexpr const_reference operator[](size_type n) const; 58 | 59 | // arithmetic 60 | constexpr offset& operator+=(const offset& rhs); 61 | constexpr offset& operator-=(const offset& rhs); 62 | 63 | template > 64 | constexpr offset& operator++(); 65 | template > 66 | constexpr offset operator++(int); 67 | template > 68 | constexpr offset& operator--(); 69 | template > 70 | constexpr offset operator--(int); 71 | 72 | constexpr offset operator+() const noexcept; 73 | constexpr offset operator-() const; 74 | 75 | constexpr offset& operator*=(value_type v); 76 | constexpr offset& operator/=(value_type v); 77 | 78 | private: 79 | std::array offset_ = {}; 80 | }; 81 | 82 | // offset equality 83 | template 84 | constexpr bool operator==(const offset& lhs, const offset& rhs) noexcept; 85 | template 86 | constexpr bool operator!=(const offset& lhs, const offset& rhs) noexcept; 87 | 88 | // offset arithmetic 89 | template 90 | constexpr offset operator+(const offset& lhs, const offset& rhs); 91 | template 92 | constexpr offset operator-(const offset& lhs, const offset& rhs); 93 | template 94 | constexpr offset operator*(const offset& lhs, ptrdiff_t v); 95 | template 96 | constexpr offset operator*(ptrdiff_t v, const offset& rhs); 97 | template 98 | constexpr offset operator/(const offset& lhs, ptrdiff_t v); 99 | 100 | 101 | template 102 | class bounds 103 | { 104 | public: 105 | // constants and types 106 | static constexpr size_t rank = Rank; 107 | using reference = ptrdiff_t&; 108 | using const_reference = const ptrdiff_t&; 109 | using iterator = bounds_iterator; 110 | using const_iterator = bounds_iterator; 111 | using size_type = size_t; 112 | using value_type = ptrdiff_t; 113 | 114 | // construction 115 | constexpr bounds() noexcept; 116 | template > 117 | constexpr bounds(value_type v); 118 | constexpr bounds(std::initializer_list il); 119 | 120 | // observers 121 | constexpr size_type size() const noexcept; 122 | constexpr bool contains(const offset& idx) const noexcept; 123 | 124 | // iterators 125 | const_iterator begin() const noexcept; 126 | const_iterator end() const noexcept; 127 | 128 | // element access 129 | constexpr reference operator[](size_type n); 130 | constexpr const_reference operator[](size_type n) const; 131 | 132 | // arithmetic 133 | constexpr bounds& operator+=(const offset& rhs); 134 | constexpr bounds& operator-=(const offset& rhs); 135 | 136 | constexpr bounds& operator*=(value_type v); 137 | constexpr bounds& operator/=(value_type v); 138 | 139 | private: 140 | std::array bounds_ = {}; 141 | }; 142 | 143 | 144 | // bounds equality 145 | template 146 | constexpr bool operator==(const bounds& lhs, const bounds& rhs) noexcept; 147 | template 148 | constexpr bool operator!=(const bounds& lhs, const bounds& rhs) noexcept; 149 | 150 | // bounds arithmetic 151 | template 152 | constexpr bounds operator+(const bounds& lhs, const offset& rhs); 153 | template 154 | constexpr bounds operator+(const offset& lhs, const bounds& rhs); 155 | template 156 | constexpr bounds operator-(const bounds& lhs, const offset& rhs); 157 | template 158 | constexpr bounds operator*(const bounds& lhs, ptrdiff_t v); 159 | template 160 | constexpr bounds operator*(ptrdiff_t v, const bounds& rhs); 161 | template 162 | constexpr bounds operator/(const bounds& lhs, ptrdiff_t v); 163 | 164 | 165 | template 166 | class bounds_iterator 167 | { 168 | public: 169 | using iterator_category = unspecified; 170 | using value_type = offset; 171 | using difference_type = ptrdiff_t; 172 | using pointer = unspecified; 173 | using reference = const offset; 174 | 175 | bounds_iterator& operator++(); 176 | bounds_iterator operator++(int); 177 | bounds_iterator& operator--(); 178 | bounds_iterator operator--(int); 179 | 180 | bounds_iterator operator+(difference_type n) const; 181 | bounds_iterator& operator+=(difference_type n); 182 | bounds_iterator operator-(difference_type n) const; 183 | bounds_iterator& operator-=(difference_type n); 184 | 185 | difference_type operator-(const bounds_iterator& rhs) const; 186 | 187 | reference operator*() const; 188 | pointer operator->() const; 189 | reference operator[](difference_type n) const; 190 | }; 191 | 192 | template 193 | class array_view 194 | { 195 | public: 196 | static constexpr size_t rank = Rank; 197 | using offset_type = offset; 198 | using bounds_type = bounds; 199 | using size_type = size_t; 200 | using value_type = T; 201 | using pointer = T*; 202 | using reference = T&; 203 | 204 | constexpr array_view() noexcept; 205 | 206 | template // only if Rank == 1 207 | constexpr array_view(Viewable&& vw); 208 | 209 | template // only if Rank == 1 210 | constexpr array_view(const array_view& rhs) noexcept; 211 | 212 | template // only if Rank == 1 213 | constexpr array_view(value_type (&arr)[Extent]) noexcept; 214 | 215 | template 216 | constexpr array_view(const array_view& rhs) noexcept; 217 | 218 | template 219 | constexpr array_view(Viewable&& vw, bounds_type bounds); 220 | 221 | constexpr array_view(pointer ptr, bounds_type bounds); 222 | 223 | // observers 224 | constexpr bounds_type bounds() const noexcept; 225 | constexpr size_type size() const noexcept; 226 | constexpr offset_type stride() const noexcept; 227 | constexpr pointer data() const noexcept; 228 | 229 | constexpr reference operator[](const offset_type& idx) const; 230 | 231 | // slicing and sectioning 232 | template // only if Rank > 1 233 | constexpr array_view operator[](ptrdiff_t slice) const; 234 | 235 | constexpr strided_array_view 236 | section(const offset_type& origin, const bounds_type& section_bounds) const; 237 | 238 | constexpr strided_array_view 239 | section(const offset_type& origin) const; 240 | }; 241 | 242 | 243 | template 244 | class strided_array_view 245 | { 246 | public: 247 | // constants and types 248 | static constexpr size_t rank = Rank; 249 | using offset_type = offset; 250 | using bounds_type = bounds; 251 | using size_type = size_t; 252 | using value_type = T; 253 | using pointer = T*; 254 | using reference = T&; 255 | 256 | // constructors, copy, and assignment 257 | constexpr strided_array_view() noexcept; 258 | 259 | template 260 | constexpr strided_array_view(const array_view& rhs) noexcept; 261 | 262 | template 263 | constexpr strided_array_view(const strided_array_view& rhs) noexcept; 264 | 265 | constexpr strided_array_view(pointer ptr, bounds_type bounds, offset_type stride); 266 | 267 | // observers 268 | constexpr bounds_type bounds() const noexcept; 269 | constexpr size_type size() const noexcept; 270 | constexpr offset_type stride() const noexcept; 271 | 272 | // element access 273 | constexpr reference operator[](const offset_type& idx) const; 274 | 275 | // slicing and sectioning 276 | template // Only if Rank > 1 277 | constexpr strided_array_view operator[](ptrdiff_t slice) const; 278 | 279 | constexpr strided_array_view 280 | section(const offset_type& origin, const bounds_type& section_bounds) const; 281 | 282 | constexpr strided_array_view 283 | section(const offset_type& origin) const; 284 | }; 285 | */ 286 | 287 | namespace av 288 | { 289 | 290 | template class offset; 291 | template class bounds; 292 | template class bounds_iterator; 293 | template class array_view; 294 | template class strided_array_view; 295 | 296 | template 297 | class offset 298 | { 299 | public: 300 | // constants and types 301 | static constexpr size_t rank = Rank; 302 | using reference = std::ptrdiff_t&; 303 | using const_reference = const std::ptrdiff_t&; 304 | using size_type = size_t; 305 | using value_type = std::ptrdiff_t; 306 | 307 | static_assert(Rank > 0, "Size of Rank must be greater than 0"); 308 | 309 | // construction 310 | constexpr offset() noexcept {} 311 | template > 312 | constexpr offset(value_type v) noexcept { (*this)[0] = v; } 313 | constexpr offset(std::initializer_list il); 314 | 315 | // element access 316 | constexpr reference operator[](size_type n) { return offset_[n]; } 317 | constexpr const_reference operator[](size_type n) const { return offset_[n]; } 318 | 319 | // arithmetic 320 | template > 321 | constexpr offset& operator++() { return ++(*this)[0]; } 322 | template > 323 | constexpr offset operator++(int) { return offset{(*this)[0]++}; } 324 | template > 325 | constexpr offset& operator--() { return --(*this)[0]; } 326 | template > 327 | constexpr offset operator--(int) { return offset{(*this)[0]--}; } 328 | 329 | constexpr offset& operator+=(const offset& rhs); 330 | constexpr offset& operator-=(const offset& rhs); 331 | 332 | constexpr offset operator+() const noexcept { return *this; } 333 | constexpr offset operator-() const 334 | { 335 | offset copy{*this}; 336 | for (value_type& elem : copy.offset_) { 337 | elem *= -1; 338 | } 339 | return copy; 340 | } 341 | 342 | constexpr offset& operator*=(value_type v); 343 | constexpr offset& operator/=(value_type v); 344 | 345 | private: 346 | std::array offset_ = {}; 347 | }; 348 | 349 | template 350 | constexpr offset::offset(std::initializer_list il) 351 | { 352 | // Note `il` is not a constant expression, hence the runtime assert for now 353 | assert(il.size() == Rank); 354 | std::copy(il.begin(), il.end(), offset_.data()); 355 | } 356 | 357 | // arithmetic 358 | template 359 | constexpr offset& offset::operator+=(const offset& rhs) 360 | { 361 | for (size_type i=0; i 368 | constexpr offset& offset::operator-=(const offset& rhs) 369 | { 370 | for (size_type i=0; i 377 | constexpr offset& offset::operator*=(value_type v) 378 | { 379 | for (value_type& elem : offset_) { 380 | elem *= v; 381 | } 382 | return *this; 383 | } 384 | 385 | template 386 | constexpr offset& offset::operator/=(value_type v) 387 | { 388 | for (value_type& elem : offset_) { 389 | elem /= v; 390 | } 391 | return *this; 392 | } 393 | 394 | 395 | // Free functions 396 | 397 | // offset equality 398 | template 399 | constexpr bool operator==(const offset& lhs, const offset& rhs) noexcept 400 | { 401 | for (size_t i=0; i 408 | constexpr bool operator!=(const offset& lhs, const offset& rhs) noexcept 409 | { return !(lhs == rhs); } 410 | 411 | // offset arithmetic 412 | template 413 | constexpr offset operator+(const offset& lhs, const offset& rhs) 414 | { return offset{lhs} += rhs; } 415 | 416 | template 417 | constexpr offset operator-(const offset& lhs, const offset& rhs) 418 | { return offset{lhs} -= rhs; } 419 | 420 | template 421 | constexpr offset operator*(const offset& lhs, std::ptrdiff_t v) 422 | { return offset{lhs} *= v; } 423 | 424 | template 425 | constexpr offset operator*(std::ptrdiff_t v, const offset& rhs) 426 | { return offset{rhs} *= v; } 427 | 428 | template 429 | constexpr offset operator/(const offset& lhs, std::ptrdiff_t v) 430 | { return offset{lhs} /= v; } 431 | 432 | 433 | template 434 | class bounds 435 | { 436 | public: 437 | // constants and types 438 | static constexpr size_t rank = Rank; 439 | using reference = std::ptrdiff_t&; 440 | using const_reference = const std::ptrdiff_t&; 441 | using iterator = bounds_iterator; 442 | using const_iterator = bounds_iterator; 443 | using size_type = size_t; 444 | using value_type = std::ptrdiff_t; 445 | 446 | static_assert(Rank > 0, "Size of Rank must be greater than 0"); 447 | 448 | // construction 449 | constexpr bounds() noexcept {}; 450 | 451 | // Question: is there a reason this constructor is not `noexcept` ? 452 | template > 453 | constexpr bounds(value_type v) { (*this)[0] = v; postcondition(); } 454 | constexpr bounds(std::initializer_list il); 455 | 456 | // observers 457 | constexpr size_type size() const noexcept; 458 | constexpr bool contains(const offset& idx) const noexcept; 459 | 460 | // iterators 461 | const_iterator begin() const noexcept { return const_iterator{*this}; }; 462 | const_iterator end() const noexcept { 463 | iterator iter{*this}; 464 | return iter._setOffTheEnd(); 465 | } 466 | 467 | // element access 468 | constexpr reference operator[](size_type n) { return bounds_[n]; } 469 | constexpr const_reference operator[](size_type n) const { return bounds_[n]; } 470 | 471 | // arithmetic 472 | constexpr bounds& operator+=(const offset& rhs); 473 | constexpr bounds& operator-=(const offset& rhs); 474 | 475 | constexpr bounds& operator*=(value_type v); 476 | constexpr bounds& operator/=(value_type v); 477 | 478 | private: 479 | std::array bounds_ = {}; 480 | 481 | void postcondition() { /* todo */ }; 482 | }; 483 | 484 | // construction 485 | template 486 | constexpr bounds::bounds(const std::initializer_list il) 487 | { 488 | assert(il.size() == Rank); 489 | 490 | std::copy(il.begin(), il.end(), bounds_.data()); 491 | postcondition(); 492 | } 493 | 494 | // observers 495 | template 496 | constexpr size_t bounds::size() const noexcept 497 | { 498 | size_type product{1}; 499 | for (const value_type& elem : bounds_) { 500 | product *= elem; 501 | } 502 | return product; 503 | } 504 | 505 | template 506 | constexpr bool bounds::contains(const offset& idx) const noexcept 507 | { 508 | for (size_type i=0; i 519 | constexpr bounds& bounds::operator+=(const offset& rhs) 520 | { 521 | for (size_type i=0; i 529 | constexpr bounds& bounds::operator-=(const offset& rhs) 530 | { 531 | for (size_type i=0; i 539 | constexpr bounds& bounds::operator*=(value_type v) 540 | { 541 | for (value_type& elem : bounds_) { 542 | elem *= v; 543 | } 544 | postcondition(); 545 | return *this; 546 | } 547 | 548 | template 549 | constexpr bounds& bounds::operator/=(value_type v) 550 | { 551 | for (value_type& elem : bounds_) { 552 | elem /= v; 553 | } 554 | postcondition(); 555 | return *this; 556 | } 557 | 558 | 559 | // Free functions 560 | 561 | // bounds equality 562 | template 563 | constexpr bool operator==(const bounds& lhs, const bounds& rhs) noexcept 564 | { 565 | for (size_t i=0; i 572 | constexpr bool operator!=(const bounds& lhs, const bounds& rhs) noexcept 573 | { return !(lhs == rhs); } 574 | 575 | // bounds arithmetic 576 | template 577 | constexpr bounds operator+(const bounds& lhs, const offset& rhs) 578 | { return bounds{lhs} += rhs; } 579 | 580 | template 581 | constexpr bounds operator+(const offset& lhs, const bounds& rhs) 582 | { return bounds{rhs} += lhs; } 583 | 584 | template 585 | constexpr bounds operator-(const bounds& lhs, const offset& rhs) 586 | { return bounds{lhs} -= rhs; } 587 | 588 | template 589 | constexpr bounds operator*(const bounds& lhs, std::ptrdiff_t v) 590 | { return bounds{lhs} *= v; } 591 | 592 | template 593 | constexpr bounds operator*(std::ptrdiff_t v, const bounds& rhs) 594 | { return bounds{rhs} *= v; } 595 | 596 | template 597 | constexpr bounds operator/(const bounds& lhs, std::ptrdiff_t v) 598 | { return bounds{lhs} /= v; } 599 | 600 | template 601 | bounds_iterator begin(const bounds& b) noexcept 602 | { return b.begin(); } 603 | 604 | template 605 | bounds_iterator end(const bounds& b) noexcept 606 | { return b.end(); } 607 | 608 | 609 | template 610 | class bounds_iterator 611 | { 612 | public: 613 | using iterator_category = std::random_access_iterator_tag; // unspecified but satisfactory 614 | using value_type = offset; 615 | using difference_type = std::ptrdiff_t; 616 | using pointer = offset*; // unspecified but satisfactory (?) 617 | using reference = const offset; 618 | 619 | static_assert(Rank > 0, "Size of Rank must be greater than 0"); 620 | 621 | bounds_iterator(const bounds bounds, offset off = offset()) noexcept 622 | : bounds_(bounds), offset_(off) {} 623 | 624 | bool operator==(const bounds_iterator& rhs) const { 625 | // Requires *this and rhs are iterators over the same bounds object. 626 | return offset_ == rhs.offset_; 627 | } 628 | 629 | bounds_iterator& operator++(); 630 | bounds_iterator operator++(int); 631 | bounds_iterator& operator--(); 632 | bounds_iterator operator--(int); 633 | 634 | bounds_iterator operator+(difference_type n) const; 635 | bounds_iterator& operator+=(difference_type n); 636 | bounds_iterator operator-(difference_type n) const; 637 | bounds_iterator& operator-=(difference_type n); 638 | 639 | difference_type operator-(const bounds_iterator& rhs) const; 640 | 641 | // Note this iterator is not a true random access iterator, nor meets N4512 642 | // + operator* returns a value type (and not a reference) 643 | // + operator-> returns a pointer to the current value type, which breaks N4512 as this 644 | // must be considered invalidated after any subsequent operation on this iterator 645 | reference operator*() const { return offset_; } 646 | pointer operator->() const { return &offset_; } 647 | 648 | reference operator[](difference_type n) const { 649 | bounds_iterator iter(*this); 650 | return (iter += n).offset_; 651 | } 652 | 653 | bounds_iterator& _setOffTheEnd(); 654 | 655 | private: 656 | bounds bounds_; 657 | offset offset_; 658 | }; 659 | 660 | template 661 | bounds_iterator bounds_iterator::operator++(int) 662 | { 663 | bounds_iterator tmp(*this); 664 | ++(*this); 665 | return tmp; 666 | } 667 | 668 | template 669 | bounds_iterator& bounds_iterator::operator++() 670 | { 671 | // watchit: dim must be signed in order to fail the condition dim>=0 672 | for (int dim=(Rank-1); dim>=0; --dim) 673 | { 674 | if (++offset_[dim] < bounds_[dim]) 675 | return (*this); 676 | else 677 | offset_[dim] = 0; 678 | } 679 | 680 | // off-the-end value 681 | _setOffTheEnd(); 682 | 683 | return *this; 684 | } 685 | 686 | template 687 | bounds_iterator& bounds_iterator::operator--() 688 | { 689 | // watchit: dim must be signed in order to fail the condition dim>=0 690 | for (int dim=(Rank-1); dim>=0; --dim) 691 | { 692 | if (--offset_[dim] >= 0) 693 | return (*this); 694 | else 695 | offset_[dim] = bounds_[dim]-1; 696 | } 697 | 698 | // before-the-start value 699 | for (int dim=0; dim 707 | bounds_iterator bounds_iterator::operator--(int) 708 | { 709 | bounds_iterator tmp(*this); 710 | --(*this); 711 | return tmp; 712 | } 713 | 714 | template 715 | bounds_iterator& bounds_iterator::_setOffTheEnd() 716 | { 717 | for (size_t dim=0; dim 726 | bounds_iterator& bounds_iterator::operator+=(difference_type n) 727 | { 728 | for (int dim=(Rank-1); dim>=0; --dim) 729 | { 730 | difference_type remainder = (n + offset_[dim]) % bounds_[dim]; 731 | n = (n + offset_[dim]) / bounds_[dim]; 732 | offset_[dim] = remainder; 733 | } 734 | assert(n == 0); // no overflow 735 | return *this; 736 | } 737 | 738 | template 739 | bounds_iterator bounds_iterator::operator+(difference_type n) const 740 | { 741 | bounds_iterator iter(*this); 742 | return iter += n; 743 | } 744 | 745 | template 746 | bounds_iterator& bounds_iterator::operator-=(difference_type n) 747 | { 748 | // take (diminished) radix compliment 749 | auto diminishedRadixComplement = [&]() { 750 | for (int dim=(Rank-1); dim>=0; --dim) 751 | { 752 | offset_[dim] = bounds_[dim] - offset_[dim]; 753 | } 754 | }; 755 | 756 | diminishedRadixComplement(); 757 | *this += n; 758 | diminishedRadixComplement(); 759 | 760 | return *this; 761 | } 762 | 763 | template 764 | bounds_iterator bounds_iterator::operator-(difference_type n) const 765 | { 766 | bounds_iterator iter(*this); 767 | return iter -= n; 768 | } 769 | 770 | // Free functions 771 | 772 | template 773 | bool operator==(const bounds_iterator& lhs, const bounds_iterator& rhs) 774 | { return lhs.operator==(rhs); } 775 | 776 | template 777 | bool operator!=(const bounds_iterator& lhs, const bounds_iterator& rhs) 778 | { return !lhs.operator==(rhs); } 779 | 780 | template 781 | bool operator<(const bounds_iterator& lhs, const bounds_iterator& rhs) 782 | { return rhs - lhs > 0; } 783 | 784 | template 785 | bool operator<=(const bounds_iterator& lhs, const bounds_iterator& rhs) 786 | { return !(lhs > rhs); } 787 | 788 | template 789 | bool operator>(const bounds_iterator& lhs, const bounds_iterator& rhs) 790 | { return rhs < lhs; } 791 | 792 | template 793 | bool operator>=(const bounds_iterator& lhs, const bounds_iterator& rhs) 794 | { return !(lhs < rhs); } 795 | 796 | template 797 | bounds_iterator operator+(typename bounds_iterator::difference_type n, 798 | const bounds_iterator& rhs); 799 | 800 | namespace { 801 | 802 | template > 803 | using is_viewable_on_u = std::integral_constant::value && 805 | std::is_convertible>::value && 806 | std::is_same, std::remove_cv_t>::value 807 | 808 | >; 809 | 810 | template 811 | using is_viewable_value = std::integral_constant, std::add_pointer_t>::value && 813 | std::is_same, std::remove_cv_t>::value 814 | >; 815 | 816 | template 817 | constexpr T& view_access(T* data, const offset& idx, const offset& stride) 818 | { 819 | std::ptrdiff_t off{}; 820 | for (size_t i=0; i 830 | class array_view 831 | { 832 | public: 833 | static constexpr size_t rank = Rank; 834 | using offset_type = offset; 835 | using bounds_type = av::bounds; 836 | using size_type = size_t; 837 | using value_type = T; 838 | using pointer = T*; 839 | using reference = T&; 840 | 841 | static_assert(Rank > 0, "Size of Rank must be greater than 0"); 842 | 843 | constexpr array_view() noexcept : data_(nullptr) {} 844 | 845 | template ::value 848 | // todo: && decay_t is not a specialization of array_view 849 | > 850 | > 851 | // todo: assert static_cast(vw.data()) points to contigious data of at least vw.size() 852 | constexpr array_view(Viewable&& vw) : data_(vw.data()), bounds_(vw.size()) { 853 | } 854 | 855 | template ::value>> 857 | constexpr array_view(const array_view& rhs) noexcept 858 | : data_(rhs.data()), bounds_(rhs.bounds()) {} 859 | 860 | template > 862 | constexpr array_view(value_type (&arr)[Extent]) noexcept 863 | : data_(arr), bounds_(Extent) {} 864 | 865 | template ::value>> 867 | constexpr array_view(const array_view& rhs) noexcept 868 | : data_(rhs.data()), bounds_(rhs.bounds()) {} 869 | 870 | template ::value>> 872 | constexpr array_view(Viewable&& vw, bounds_type bounds) 873 | : data_(vw.data()), bounds_(bounds) 874 | { 875 | assert(bounds.size() <= vw.size()); 876 | } 877 | 878 | constexpr array_view(pointer ptr, bounds_type bounds) 879 | : data_(ptr), bounds_(bounds) {} 880 | 881 | // observers 882 | constexpr bounds_type bounds() const noexcept { return bounds_; } 883 | constexpr size_type size() const noexcept { return bounds().size(); } 884 | constexpr offset_type stride() const noexcept; 885 | constexpr pointer data() const noexcept { return data_; } 886 | 887 | constexpr reference operator[](const offset_type& idx) const 888 | { 889 | assert(bounds().contains(idx) == true); 890 | return view_access(data_, idx, stride()); 891 | } 892 | 893 | // slicing and sectioning 894 | template =2 >> 895 | constexpr array_view operator[](std::ptrdiff_t slice) const 896 | { 897 | assert(0 <= slice && slice < bounds()[0]); 898 | 899 | av::bounds new_bounds{}; 900 | for (size_t i=0; i(data_ + off, new_bounds); 907 | } 908 | 909 | constexpr strided_array_view 910 | section(const offset_type& origin, const bounds_type& section_bounds) const 911 | { 912 | // todo: requirement is for any idx in section_bounds (boundary fail) 913 | // assert(bounds().contains(origin + section_bounds) == true); 914 | return strided_array_view(&(*this)[origin], section_bounds, stride()); 915 | } 916 | 917 | constexpr strided_array_view 918 | section(const offset_type& origin) const 919 | { 920 | // todo: requires checking for any idx in bounds() - origin 921 | // assert(bounds().contains(bounds()) == true); 922 | return strided_array_view(&(*this)[origin], bounds() - origin, stride()); 923 | } 924 | 925 | private: 926 | pointer data_; 927 | bounds_type bounds_; 928 | }; 929 | 930 | template 931 | constexpr typename array_view::offset_type array_view::stride() const noexcept 932 | { 933 | offset_type stride{}; 934 | stride[rank-1] = 1; 935 | for (int dim=static_cast(rank)-2; dim>=0; --dim) 936 | { 937 | stride[dim] = stride[dim+1] * bounds()[dim + 1]; 938 | } 939 | return stride; 940 | } 941 | 942 | template 943 | class strided_array_view 944 | { 945 | public: 946 | // constants and types 947 | static constexpr size_t rank = Rank; 948 | using offset_type = offset; 949 | using bounds_type = av::bounds; 950 | using size_type = size_t; 951 | using value_type = T; 952 | using pointer = T*; 953 | using reference = T&; 954 | 955 | // constructors, copy, and assignment 956 | constexpr strided_array_view() noexcept 957 | : data_{nullptr}, bounds_{}, stride_{} {} 958 | 959 | template ::value>> 960 | constexpr strided_array_view(const array_view& rhs) noexcept 961 | : data_{rhs.data()}, bounds_{rhs.bounds()}, stride_{rhs.stride()} {} 962 | template ::value>> 963 | constexpr strided_array_view(const strided_array_view& rhs) noexcept 964 | : data_{rhs.data_}, bounds_{rhs.bounds()}, stride_{rhs.stride()} {} 965 | 966 | constexpr strided_array_view(pointer ptr, bounds_type bounds, offset_type stride) 967 | : data_(ptr), bounds_(bounds), stride_(stride) 968 | { 969 | // todo: assert that sum(idx[i] * stride[i]) fits in std::ptrdiff_t 970 | } 971 | 972 | // observers 973 | constexpr bounds_type bounds() const noexcept { return bounds_; } 974 | constexpr size_type size() const noexcept { return bounds_.size(); } 975 | constexpr offset_type stride() const noexcept { return stride_; } 976 | 977 | // element access 978 | constexpr reference operator[](const offset_type& idx) const 979 | { 980 | assert(bounds().contains(idx) == true); 981 | return view_access(data_, idx, stride_); 982 | } 983 | 984 | // slicing and sectioning 985 | template =2 >> 986 | constexpr strided_array_view operator[](std::ptrdiff_t slice) const 987 | { 988 | assert(0 <= slice && slice < bounds()[0]); 989 | 990 | av::bounds new_bounds{}; 991 | for (size_t i=0; i new_stride{}; 996 | for (size_t i=0; i(data_ + off, new_bounds, new_stride); 1003 | } 1004 | 1005 | constexpr strided_array_view 1006 | section(const offset_type& origin, const bounds_type& section_bounds) const 1007 | { 1008 | // todo: requirement is for any idx in section_bounds (boundary fail) 1009 | // assert(bounds().contains(origin + section_bounds) == true); 1010 | return strided_array_view(&(*this)[origin], section_bounds, stride()); 1011 | } 1012 | 1013 | constexpr strided_array_view 1014 | section(const offset_type& origin) const 1015 | { 1016 | // todo: requires checking for any idx in bounds() - origin 1017 | // assert(bounds().contains(bounds()) == true); 1018 | return strided_array_view(&(*this)[origin], bounds() - origin, stride()); 1019 | } 1020 | 1021 | private: 1022 | pointer data_; 1023 | bounds_type bounds_; 1024 | offset_type stride_; 1025 | }; 1026 | 1027 | } 1028 | -------------------------------------------------------------------------------- /array_view/array_view_test.cpp: -------------------------------------------------------------------------------- 1 | #include "array_view/array_view.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "gtest/gtest.h" 8 | 9 | using namespace std; 10 | using namespace av; 11 | 12 | template 13 | ostream& operator<<(ostream& os, const offset& off) 14 | { 15 | os << "(" 16 | << off[0]; 17 | 18 | for (size_t i=1; i{1,2,3}), ""); 29 | } 30 | #endif 31 | 32 | TEST(offset_test, Initialize) 33 | { 34 | offset<1> off0; 35 | EXPECT_EQ(0, off0[0]); 36 | 37 | offset<1> off1(4); 38 | EXPECT_EQ(4, off1[0]); 39 | 40 | offset<4> off2; 41 | EXPECT_EQ(0, off2[0]); 42 | EXPECT_EQ(0, off2[1]); 43 | EXPECT_EQ(0, off2[2]); 44 | EXPECT_EQ(0, off2[3]); 45 | 46 | offset<3> off3 = {1,2,3}; 47 | EXPECT_EQ(1, off3[0]); 48 | EXPECT_EQ(2, off3[1]); 49 | EXPECT_EQ(3, off3[2]); 50 | 51 | // Constraints 52 | //offset<0> off3; // Should not compile: Size of Rank must be greater than 0 53 | //offset<0> off3({}); // Should not compile: Size of Rank must be greater than 0 54 | } 55 | 56 | TEST(bounds_test, size) 57 | { 58 | bounds<3> b = {2,3,4}; 59 | EXPECT_EQ(24, b.size()); 60 | } 61 | 62 | TEST(bounds, contains) 63 | { 64 | bounds<3> b = {2,3,4}; 65 | EXPECT_TRUE(b.contains({0,0,0})); 66 | EXPECT_TRUE(b.contains({1,2,3})); 67 | 68 | EXPECT_FALSE(b.contains({1,2,4})); 69 | EXPECT_FALSE(b.contains({1,3,3})); 70 | EXPECT_FALSE(b.contains({2,2,3})); 71 | EXPECT_FALSE(b.contains({0,0,-1})); 72 | } 73 | 74 | TEST(bounds_iterator_test, increment) 75 | { 76 | bounds<3> b = {4,5,6}; 77 | bounds_iterator<3> iter{b}; 78 | 79 | for (int i=0; i<(4*5*6); i++) 80 | { 81 | // cout << *iter++ << endl; 82 | } 83 | // cout << "off-the-end: " << *iter << endl; 84 | // cout << "--off-the-end: " << *(--iter) << endl; 85 | } 86 | 87 | TEST(bounds_iterator_test, decrement) 88 | { 89 | bounds<3> b = {4,5,6}; 90 | bounds_iterator<3> iter{b, {3,4,6}}; // off-the-end value 91 | 92 | for (int i=0; i<(4*5*6); i++) 93 | { 94 | // cout << *(--iter) << endl; 95 | } 96 | // cout << "before-the-start: " << *(--iter) << endl; 97 | // cout << "++before-the-start" << *(++iter) << endl; 98 | } 99 | 100 | TEST(bounds_iterator_test, beginYend) 101 | { 102 | bounds<3> b = {4,5,6}; 103 | 104 | bounds_iterator<3> iter = b.begin(); 105 | while (iter != b.end()) 106 | { 107 | *iter++; 108 | // cout << *iter++ << endl; 109 | } 110 | // cout << "off-the-end: " << *iter << endl; 111 | // cout << "--off-the-end: " << *(--iter) << endl; 112 | 113 | EXPECT_EQ(b.begin(), begin(b)); 114 | EXPECT_EQ(b.end(), end(b)); 115 | } 116 | 117 | TEST(bounds_iterator_test, difference) 118 | { 119 | bounds<3> b = {4,5,9}; 120 | bounds_iterator<3> iter(b, {2,4,7}); 121 | iter += 25; 122 | 123 | EXPECT_EQ(3, (*iter)[0]); 124 | EXPECT_EQ(2, (*iter)[1]); 125 | EXPECT_EQ(5, (*iter)[2]); 126 | 127 | iter -= 25; 128 | EXPECT_EQ(2, (*iter)[0]); 129 | EXPECT_EQ(4, (*iter)[1]); 130 | EXPECT_EQ(7, (*iter)[2]); 131 | 132 | bounds_iterator<3> iter2 = iter + 25; 133 | EXPECT_EQ(3, (*iter2)[0]); 134 | EXPECT_EQ(2, (*iter2)[1]); 135 | EXPECT_EQ(5, (*iter2)[2]); 136 | 137 | bounds_iterator<3> iter3 = iter2 - 25; 138 | EXPECT_EQ(2, (*iter3)[0]); 139 | EXPECT_EQ(4, (*iter3)[1]); 140 | EXPECT_EQ(7, (*iter3)[2]); 141 | 142 | offset<3> off = iter3[25]; 143 | EXPECT_EQ(3, off[0]); 144 | EXPECT_EQ(2, off[1]); 145 | EXPECT_EQ(5, off[2]); 146 | } 147 | 148 | class ArrayViewTest : public ::testing::Test { 149 | public: 150 | ArrayViewTest() : 151 | vec(4*8*12), 152 | testBounds{4,8,12}, 153 | av(vec, testBounds), 154 | sav(av) 155 | { 156 | int n{}; 157 | std::generate(vec.begin(), vec.end(), [&]{ return n++; }); 158 | testStride = av.stride(); 159 | } 160 | 161 | protected: 162 | vector vec; 163 | 164 | bounds<3> testBounds; 165 | offset<3> testStride; 166 | 167 | // objects under test, one of each (but both under contigious data) 168 | array_view av; 169 | strided_array_view sav; 170 | }; 171 | 172 | using StridedArrayViewTest = ArrayViewTest; 173 | 174 | class StridedDataTest : public ArrayViewTest { 175 | public: 176 | using Avt = ArrayViewTest; 177 | 178 | StridedDataTest() : 179 | testBounds{Avt::testBounds[0], Avt::testBounds[1], Avt::testBounds[2] / 2}, 180 | testStride{Avt::testStride[0], Avt::testStride[1], 2}, 181 | strided_sav(vec.data(), testBounds, testStride) 182 | {} 183 | 184 | protected: 185 | bounds<3> testBounds; 186 | offset<3> testStride; 187 | 188 | // object under test, a strided class with alternate spacing in z (even's only) 189 | strided_array_view strided_sav; 190 | }; 191 | 192 | template 193 | void testSectioning(const ArrayView& av, const bounds<3>& testBounds, 194 | const offset<3>& testOrigin, 195 | const offset<3>& testStride) 196 | { 197 | int start{}; 198 | for (int i=0; i<3; i++) { 199 | start += testOrigin[i] * testStride[i]; 200 | } 201 | 202 | bounds_iterator<3> iter = begin(av.bounds()); 203 | for (int i=0; i idx = {i,j,k}; 210 | EXPECT_EQ(idx, *iter); 211 | 212 | int off{}; 213 | for (int d=0; d<3; d++) { 214 | off += idx[d] * testStride[d]; 215 | } 216 | 217 | EXPECT_EQ(start + off, av[*iter++]); 218 | } 219 | } 220 | } 221 | } 222 | 223 | template 224 | void testSlicing(const ArrayView& av, const offset<3>& testStride) 225 | { 226 | // Slices always fix the most significant dimension 227 | int x = 2; 228 | strided_array_view sliced = av[x]; 229 | int start = testStride[0] * x; 230 | for (bounds_iterator<2> iter = begin(sliced.bounds()); iter!=end(sliced.bounds()); ++iter) 231 | { 232 | EXPECT_EQ(start, sliced[*iter]); 233 | start += testStride[2]; 234 | } 235 | 236 | // Cascade slices 237 | int y = 3; 238 | strided_array_view sliced2 = av[x][y]; 239 | int start2 = testStride[0] * x + testStride[1] * y; 240 | for (bounds_iterator<1> iter = begin(sliced2.bounds()); iter!=end(sliced2.bounds()); ++iter) 241 | { 242 | EXPECT_EQ(start2, sliced2[*iter]); 243 | start2 += testStride[2]; 244 | } 245 | 246 | // Cascade to a single index 247 | int z = 3; 248 | int start3 = testStride[0] * x + testStride[1] * y + testStride[2] * z; 249 | EXPECT_EQ(start3, av[x][y][z]); 250 | } 251 | 252 | TEST_F(ArrayViewTest, Constructors) 253 | { 254 | int start{}; 255 | for (bounds_iterator<3> iter = begin(av.bounds()); iter!=end(av.bounds()); ++iter) 256 | { 257 | EXPECT_EQ(start++, av[*iter]); 258 | } 259 | } 260 | 261 | TEST_F(ArrayViewTest, Observers) 262 | { 263 | EXPECT_EQ(av.bounds(), testBounds); 264 | EXPECT_EQ(av.size(), testBounds[0] * testBounds[1] * testBounds[2]); 265 | EXPECT_EQ(av.stride(), testStride); 266 | } 267 | 268 | TEST_F(ArrayViewTest, Slicing) 269 | { 270 | testSlicing(av, testStride); 271 | } 272 | 273 | TEST_F(ArrayViewTest, Sectioning) 274 | { 275 | offset<3> origin{1,2,3}; 276 | 277 | // section with new bounds 278 | bounds<3> newBounds{2,3,4}; 279 | testSectioning(av.section(origin, newBounds), newBounds, origin, testStride); 280 | 281 | // section with bounds extending to extent of source view 282 | strided_array_view sectioned = av.section(origin); 283 | 284 | bounds<3> remainingBounds = testBounds - origin; 285 | EXPECT_EQ(remainingBounds, sectioned.bounds()); 286 | testSectioning(sectioned, remainingBounds, origin, testStride); 287 | } 288 | 289 | TEST_F(StridedArrayViewTest, Constructors) 290 | { 291 | // Default 292 | strided_array_view sav{}; 293 | EXPECT_EQ(0, sav.size()); 294 | 295 | // From array_view 296 | strided_array_view sav2(av); 297 | int ans{}; 298 | for (bounds_iterator<3> iter = begin(sav2.bounds()); iter!=end(sav2.bounds()); ++iter) { 299 | EXPECT_EQ(ans++, sav2[*iter]); 300 | } 301 | 302 | // From strided_array_view 303 | strided_array_view sav3(sav2); 304 | int ans2{}; 305 | for (bounds_iterator<3> iter = begin(sav3.bounds()); iter!=end(sav3.bounds()); ++iter) { 306 | EXPECT_EQ(ans2++, sav3[*iter]); 307 | } 308 | } 309 | 310 | TEST_F(StridedArrayViewTest, Observers) 311 | { 312 | EXPECT_EQ(sav.bounds(), testBounds); 313 | EXPECT_EQ(sav.size(), testBounds[0] * testBounds[1] * testBounds[2]); 314 | EXPECT_EQ(sav.stride(), testStride); 315 | } 316 | 317 | TEST_F(StridedArrayViewTest, Slicing) 318 | { 319 | testSlicing(sav, testStride); 320 | } 321 | 322 | TEST_F(StridedArrayViewTest, Sectioning) 323 | { 324 | offset<3> origin{1,2,3}; 325 | 326 | // section with new bounds 327 | bounds<3> newBounds{2,3,4}; 328 | testSectioning(sav.section(origin, newBounds), newBounds, origin, testStride); 329 | 330 | // section with bounds extending to extent of source view 331 | strided_array_view sectioned = sav.section(origin); 332 | 333 | bounds<3> remainingBounds = testBounds - origin; 334 | EXPECT_EQ(remainingBounds, sectioned.bounds()); 335 | testSectioning(sectioned, remainingBounds, origin, testStride); 336 | } 337 | 338 | TEST_F(StridedDataTest, Constructors) 339 | { 340 | int start{}; 341 | for (bounds_iterator<3> iter = begin(strided_sav.bounds()); iter!=end(strided_sav.bounds()); ++iter) { 342 | EXPECT_EQ(start, strided_sav[*iter]); 343 | start += 2; 344 | } 345 | } 346 | 347 | TEST_F(StridedDataTest, Observers) 348 | { 349 | EXPECT_EQ(strided_sav.bounds(), testBounds); 350 | EXPECT_EQ(strided_sav.size(), testBounds[0] * testBounds[1] * testBounds[2]); 351 | EXPECT_EQ(strided_sav.stride(), testStride); 352 | } 353 | 354 | TEST_F(StridedDataTest, Slicing) 355 | { 356 | testSlicing(strided_sav, testStride); 357 | } 358 | 359 | TEST_F(StridedDataTest, Sectioning) 360 | { 361 | offset<3> origin{1,2,3}; 362 | 363 | // section with new bounds 364 | bounds<3> newBounds{2,3,4}; 365 | testSectioning(strided_sav.section(origin, newBounds), newBounds, origin, testStride); 366 | 367 | // section with bounds extending to extent of source view 368 | strided_array_view sectioned = strided_sav.section(origin); 369 | 370 | bounds<3> remainingBounds = testBounds - origin; 371 | EXPECT_EQ(remainingBounds, sectioned.bounds()); 372 | testSectioning(sectioned, remainingBounds, origin, testStride); 373 | } 374 | 375 | TEST(ArrayView, Example) 376 | { 377 | int X = 12; 378 | int Y = 8; 379 | int Z = 6; 380 | 381 | vector vec(X*Y*Z); 382 | 383 | bounds<3> extents = {X,Y,Z}; 384 | array_view av(vec, extents); 385 | 386 | // Access an element. 387 | offset<3> idx = {5,3,2}; 388 | av[idx] = 30; 389 | 390 | // Iterate through each index of the view 391 | for (auto& idx : av.bounds()) 392 | { 393 | auto i = idx[0]; 394 | auto j = idx[1]; 395 | auto k = idx[2]; 396 | av[idx] = i * j * k; 397 | } 398 | 399 | // or use a bounds_iterator explicitly 400 | bounds_iterator<3> first = begin(av.bounds()); 401 | bounds_iterator<3> last = end(av.bounds()); 402 | 403 | for_each(first, last, [&av](const offset<3>& idx) { 404 | auto i = idx[0]; 405 | auto j = idx[1]; 406 | auto k = idx[2]; 407 | // cout << i << " " << j << " " << k << endl; 408 | EXPECT_EQ(i * j * k, av[idx]); 409 | }); 410 | 411 | // Slicing 412 | int x0 = 5; 413 | int y0 = 3; 414 | array_view slice2d = av[x0]; // a 2d slice in the yz plane 415 | array_view slice1d = av[x0][y0]; // a row in z (also the contigious dimension) 416 | 417 | EXPECT_EQ(30, ( slice2d[{3,2}] )); 418 | EXPECT_EQ(30, slice1d[2]); 419 | 420 | // Sectioning 421 | offset<3> origin = {6, 3, 2}; 422 | bounds<3> window = {3, 3, 2}; 423 | auto section = av.section(origin, window); 424 | 425 | int sum = std::accumulate(begin(section.bounds()), end(section.bounds()), 0, 426 | [&](int a, offset<3> idx) { 427 | return a + section[idx]; 428 | }); 429 | 430 | // Strided data 431 | offset<3> newStride = {av.stride()[0], av.stride()[1], 3}; 432 | bounds<3> newExtents = {X, Y, Z/3}; 433 | strided_array_view sav(vec.data(), newExtents, newStride); 434 | 435 | for (auto& idx : sav.bounds()) 436 | { 437 | EXPECT_EQ(0, sav[idx] % 3); 438 | } 439 | } 440 | --------------------------------------------------------------------------------