iterator.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // key ideas:
  2. // offsets are the distances from the beginnings of the current row, page, book (just like a pointer is the distance from beginning of memory)
  3. // step is the distance to get from the end of a row to begin of next row (pitch - flat_size)
  4. // (pitch here is an image processing jargon, it the image size including padding)
  5. // flat_size is the total number of elements in given row/page/book, just like offset
  6. // flat_size can be obtained with (end - begin) of a given range, the relevant calculation(width*height*depth*...) performed when forming the end pointer, unless it might be otherwise formed, through iteration for example
  7. // done_mask (it == end) indicates which dimensions are at their end and need to be stepped forward,
  8. // for example in 2D case
  9. // (false, false) - we are safely inside the range
  10. // (true, false) - we reached the end of the row
  11. // (true, true) - we reached the end of the whole range
  12. // (false, true) - not possible, invariant broken
  13. // in boolean context it reduces by conjunction
  14. // iterator::equality is an optimization of this logic, exploiting the patterns of iteration
  15. // begin != end is similar and thus the following pattern
  16. // while (begin != end) ++begin;
  17. // will only take you till the end of the row,
  18. // afterwords the step will need to be added (next, advance) according to done_mask
  19. // in iterator this is also optimized to a plain bool, so the dimensional information is lost
  20. // TODO: compare to Boost.GIL which stores a pointer and pitch, various advantages and disadvantages, reimplement all the relevant examples from there!!!
  21. #ifndef SIMPLE_GEOM_ITERATOR_HPP
  22. #define SIMPLE_GEOM_ITERATOR_HPP
  23. #include "vector.hpp"
  24. namespace simple::geom
  25. {
  26. //TODO: in image processing step/pitch can be in bytes not in sizeof(value_type)s, for memory alignment purposes, which means that offsets will need to be in bytes as well, but maybe that's out of scope here, in general step/pitch is necessary for sub-ranges/regions
  27. // a more sophisticated and somewhat optimized version (see vectirator)
  28. // the underlying iterator FlatIt serves as the last offset
  29. // so the logical structure is
  30. // 2D - vector(row_offset, flat_iterator)
  31. // 3D - vector(row_offset, page_offset, flat_iterator)
  32. // 4D - vector(row_offset, page_offset, book_offset, flat_iterator)
  33. // etc
  34. // advantages:
  35. // performs better in practice,
  36. // disadvantages:
  37. // complex implementation
  38. // very specific interface
  39. template <typename FlatIt, size_t Dimensions = 2>
  40. class iterator
  41. {
  42. public:
  43. using offset_type = vector<std::uintptr_t, Dimensions-1>;
  44. using scalar_difference_type = typename std::iterator_traits<FlatIt>::difference_type;
  45. using difference_type = vector<scalar_difference_type, Dimensions>;
  46. using value_type = typename std::iterator_traits<FlatIt>::value_type;
  47. using reference = typename std::iterator_traits<FlatIt>::reference;
  48. using pointer = typename std::iterator_traits<FlatIt>::pointer;
  49. // NOTE: if FlatIt is random access, we are partially random access - adding and subtracting offsets is cheap, but offsets are multidimensional, so the required equivalence with repeated increments doesn't work, that is you can decrement the offset while it's not equal (by conjunction) to default constructed offset, incrementing the pointer, and it will be a valid operation, but not the same as adding the offset directly, the (conjunctive) inequality check will hit a wall on the end of first dimension, while the offset addition can get you across
  50. // in more general terms something like binary search wouldn't work, cause halfing the offset is meaningless, when pitch is not zero
  51. // TODO: we are actually input if FlatIt is input, output if output, forward if forward, bidirectional if anything higher, and otherwise not an iterator at all... need the meta-programming here and the proper free functions used on FlatIt member instead of operators
  52. using iterator_category = std::bidirectional_iterator_tag;
  53. //TODO: iterator_concept
  54. constexpr iterator(FlatIt flat, offset_type offsets)
  55. : offsets(offsets), flat(flat)
  56. {}
  57. constexpr iterator(FlatIt flat)
  58. : offsets{}, flat(flat)
  59. {}
  60. struct equality
  61. {
  62. size_t first_false;
  63. operator bool() { return first_false == Dimensions; }
  64. };
  65. [[nodiscard]] constexpr
  66. equality operator==(const iterator& other) const
  67. {
  68. // this is find_if, but pointers make optimizer comfooze
  69. for(size_t i = 0; i < offsets.dimensions; ++i)
  70. if(offsets[i] != other.offsets[i])
  71. return {i};
  72. if(flat != other.flat)
  73. return {Dimensions - 1};
  74. return {Dimensions};
  75. }
  76. [[nodiscard]] constexpr
  77. bool operator!=(const iterator& other) const
  78. {
  79. // this is inconsistent with ==, not just because it's an optimization,
  80. // this is a conjunction, while it should be a disjunction
  81. // see the corresponding operator in vectirator for explanation
  82. // now how is this a conjunction?
  83. // it is the invariant of this iterator that if it reaches another iterator in any dimension it would also reach it in all lower dimensions and x is the lowest, therefore if we're not equals in x we can't be equal in any other dimension. In other words x != is true when all other dimensions != is true as well, making the conjunction true, and if x != is false the conjunction is obviously false, regardless of other dimensions.
  84. return offsets.x() != other.offsets.x();
  85. }
  86. // TODO: relational operators, defining a lexicographic order (can just compare FlatIt), as opposed to partial order of vectors
  87. [[nodiscard]] constexpr
  88. decltype(auto) operator*() const { return *flat; }
  89. constexpr FlatIt operator->() const { return flat; }
  90. constexpr iterator& operator++()
  91. {
  92. ++offsets;
  93. ++flat;
  94. return *this;
  95. }
  96. [[nodiscard]] constexpr iterator operator++(int)
  97. {
  98. iterator old = *this;
  99. ++(*this);
  100. return old;
  101. }
  102. constexpr iterator& operator--()
  103. {
  104. --offsets;
  105. --flat;
  106. return *this;
  107. }
  108. [[nodiscard]] constexpr iterator operator--(int)
  109. {
  110. iterator old = *this;
  111. --(*this);
  112. return old;
  113. }
  114. [[nodiscard]] constexpr
  115. difference_type operator-(const iterator& other) const
  116. {
  117. return combine_binary_op<scalar_difference_type>(other, std::minus<>{});
  118. }
  119. [[nodiscard]] constexpr
  120. friend iterator operator+(iterator it, const difference_type& diff)
  121. {
  122. it.split_binary_op(diff, std::plus<>());
  123. return it;
  124. }
  125. [[nodiscard]] constexpr
  126. friend iterator operator+(const difference_type& diff, iterator it)
  127. { return it + diff; }
  128. [[nodiscard]] constexpr
  129. friend iterator operator-(iterator it, const difference_type& diff)
  130. {
  131. it.split_binary_op(diff, std::minus<>());
  132. return it;
  133. }
  134. [[nodiscard]] constexpr
  135. friend iterator operator-(const difference_type& diff, iterator it)
  136. { return it - diff; }
  137. // TODO: in place arithmetic ops
  138. constexpr void next(const offset_type& step, size_t dimension)
  139. {
  140. // dimension == 0 means we are in the beginning or somewhere in the middle, and just need to increment
  141. assert(dimension > 0);
  142. // dimension == Dimensions means we are at the end of the range, nowhere to go
  143. assert(dimension < Dimensions);
  144. for(size_t i = 0; i < dimension; ++i)
  145. offsets[i] = 0;
  146. for(size_t i = dimension; i < offset_type::dimensions; ++i)
  147. offsets[i] += step[dimension - 1];
  148. flat += step[dimension - 1];
  149. }
  150. constexpr void next(const offset_type& step, equality done)
  151. {
  152. next(step, done.first_false);
  153. }
  154. constexpr void prev(offset_type step, size_t dimension); // TODO
  155. constexpr void prev(offset_type step, equality done); // TODO
  156. constexpr void advance(vector<scalar_difference_type, offset_type::dimensions> step, size_t dimentins); // TODO
  157. constexpr void advance(vector<scalar_difference_type, offset_type::dimensions> step, equality done); // TODO
  158. private:
  159. offset_type offsets;
  160. FlatIt flat;
  161. template <typename Result, typename BinaryOp>
  162. [[nodiscard]] constexpr
  163. vector<Result,Dimensions> combine_binary_op(const iterator& other, BinaryOp&& bop) const
  164. {
  165. return vector<Result, offset_type::dimensions>(bop(offsets,other.offsets))
  166. .template concat(bop(flat,other.flat));
  167. }
  168. template <typename Combined, typename BinaryOp>
  169. constexpr void split_binary_op(Combined value, BinaryOp&& bop)
  170. {
  171. offsets = bop(offsets, value.template first<offset_type::dimensions>());
  172. flat = bop(flat, get<offset_type::dimensions>(value));
  173. }
  174. };
  175. // a more straight forward version
  176. // all offset as present, along with the underlying iterator that serves as a reference to the beginning of the range
  177. // so the logical structure is
  178. // 2D - vector(row_offset, page_offset), begin_iterator
  179. // 3D - vector(row_offset, page_offset, book_offset), begin_iterator
  180. // 4D - vector(row_offset, page_offset, book_offset, shelf_offset), begin_iterator
  181. // etc
  182. // advantages:
  183. // simple implementation (in theory may be easier for compilers to optimize, and any future geom::vector optimizations will adhere)
  184. // inevitably carries more information, which may be of use in some contexts
  185. // generic interface
  186. // disadvantages:
  187. // slow in practice
  188. // inevitably carries more information, which makes it more bulky
  189. // TODO: compatibility with other version geom::iterator
  190. template <typename BeginIt, size_t Dimensions = 2>
  191. class vectirator
  192. {
  193. public:
  194. using offset_type = vector<std::uintptr_t, Dimensions>;
  195. using scalar_difference_type = typename std::iterator_traits<BeginIt>::difference_type;
  196. using difference_type = vector<scalar_difference_type, Dimensions>;
  197. using value_type = typename std::iterator_traits<BeginIt>::value_type;
  198. using reference = typename std::iterator_traits<BeginIt>::reference;
  199. using pointer = typename std::iterator_traits<BeginIt>::pointer;
  200. // TODO: constrain BeginIt to not be input or output iterator
  201. // TODO: use generic iterator function instead of the operators in the implementation
  202. // NOTE: category for this is always bidirectional or partially random access(see geom::iterator), however if BeginIt is forward iterator dereference will be expensive
  203. using iterator_category = std::bidirectional_iterator_tag;
  204. //TODO: iterator_concept
  205. constexpr vectirator(BeginIt origin, offset_type offsets)
  206. : offsets(offsets), origin(origin)
  207. {}
  208. constexpr vectirator(BeginIt origin)
  209. : offsets{}, origin(origin)
  210. {}
  211. [[nodiscard]] constexpr
  212. auto operator==(const vectirator& other) const
  213. {
  214. return offsets == other.offsets;
  215. }
  216. [[nodiscard]] constexpr
  217. auto operator!=(const vectirator& other) const
  218. {
  219. // this is an unfortunate thing with standard loops that they use != and thus in multidimensional case to stop with the shortest sequence we need a conjunction, which is totally counter-intuitive since, when it comes to actually comparing the values, you expect a disjunction
  220. // as a result we now have this inconsistent with ==, to keep == sane for normal comparison... ideally standard loops conditions would have a customization point other than != that would default to !=, but alas... we are here now, if you want to do logic use !(x==b), x!=b is special customization point reserved for loops... technically i must wreck == too since begin != end is just a common convention not a requirement, but I'll hold on to it for as long as I can
  221. // why do we want to support standard loops anyway? because in our case they correspond to a loop over a row, which is a very common thing, and with large arrays is the part that actually matters in terms of performance, so that's where all the fancy algorithms will come in while the outer higher dimensional loops can be their own thing using next(step, mask) et al
  222. // TODO: on the other hand since there is always going to be a fancy outer loop, we can switch types for the inner loop and keep == and != consistent, sane for the outer loop and outside world in general and insane only for the short lived inner loop
  223. return to_conjunction(offsets != other.offsets);
  224. }
  225. // TODO: relational operators (as geom::iterator)
  226. constexpr BeginIt operator->() const { return origin + *(offsets.end()-1); }
  227. [[nodiscard]] constexpr
  228. decltype(auto) operator*() const { return *(operator->()); }
  229. constexpr vectirator& operator++()
  230. {
  231. ++offsets;
  232. return *this;
  233. }
  234. [[nodiscard]] constexpr vectirator operator++(int)
  235. {
  236. vectirator old = *this;
  237. ++(*this);
  238. return old;
  239. }
  240. constexpr vectirator& operator--()
  241. {
  242. --offsets;
  243. return *this;
  244. }
  245. [[nodiscard]] constexpr vectirator operator--(int)
  246. {
  247. vectirator old = *this;
  248. --(*this);
  249. return old;
  250. }
  251. [[nodiscard]] constexpr
  252. difference_type operator-(const vectirator& other) const
  253. {
  254. return offsets - other.offsets;
  255. }
  256. [[nodiscard]] constexpr friend
  257. vectirator operator+(vectirator it, const difference_type& diff)
  258. {
  259. it.offsets += diff;
  260. return it;
  261. }
  262. [[nodiscard]] constexpr friend
  263. vectirator operator+(const difference_type& diff, vectirator it)
  264. { return it + diff; }
  265. [[nodiscard]] constexpr friend
  266. vectirator operator-(vectirator it, const difference_type& diff)
  267. {
  268. it.offsets -= diff;
  269. return it;
  270. }
  271. [[nodiscard]] constexpr friend
  272. vectirator operator-(const difference_type& diff, vectirator it)
  273. { return it - diff; }
  274. constexpr vectirator& operator+=(const difference_type& diff)
  275. { offsets += diff; return *this; }
  276. constexpr vectirator& operator-=(const difference_type& diff)
  277. { offsets -= diff; return *this; }
  278. constexpr void next(const offset_type& step, vector<bool, Dimensions> done_mask)
  279. {
  280. auto begin = std::begin(done_mask);
  281. auto end = std::end(done_mask);
  282. auto first_false = std::find(begin, end, false) - begin;
  283. assert(first_false < step.dimensions);
  284. offsets += step[first_false];
  285. offsets *= ~done_mask;
  286. }
  287. constexpr void prev(offset_type step, vector<bool, Dimensions> done_mask); // TODO
  288. constexpr void advance(const offset_type& step, vector<bool, Dimensions> done_mask); // TODO
  289. private:
  290. offset_type offsets;
  291. BeginIt origin;
  292. };
  293. } // namespace simple::geom
  294. #endif /* end of include guard */