diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh index 316dda821a1..e408b3c0a70 100644 --- a/source/blender/blenlib/BLI_index_range.hh +++ b/source/blender/blenlib/BLI_index_range.hh @@ -41,6 +41,7 @@ #include #include "BLI_assert.h" +#include "BLI_random_access_iterator_mixin.hh" namespace blender { @@ -65,13 +66,11 @@ class IndexRange { BLI_assert(size >= 0); } - class Iterator { + class Iterator : public iterator::RandomAccessIteratorMixin { public: - using iterator_category = std::forward_iterator_tag; using value_type = int64_t; using pointer = const int64_t *; - using reference = const int64_t &; - using difference_type = std::ptrdiff_t; + using reference = int64_t; private: int64_t current_; @@ -79,38 +78,15 @@ class IndexRange { public: constexpr explicit Iterator(int64_t current) : current_(current) {} - constexpr Iterator &operator++() - { - current_++; - return *this; - } - - constexpr Iterator operator++(int) - { - Iterator copied_iterator = *this; - ++(*this); - return copied_iterator; - } - - constexpr friend bool operator!=(const Iterator &a, const Iterator &b) - { - return a.current_ != b.current_; - } - - constexpr friend bool operator==(const Iterator &a, const Iterator &b) - { - return a.current_ == b.current_; - } - - constexpr friend int64_t operator-(const Iterator &a, const Iterator &b) - { - return a.current_ - b.current_; - } - constexpr int64_t operator*() const { return current_; } + + const int64_t &iter_prop() const + { + return current_; + } }; constexpr Iterator begin() const diff --git a/source/blender/blenlib/BLI_offset_span.hh b/source/blender/blenlib/BLI_offset_span.hh index 747d0be3510..4978e1e1f60 100644 --- a/source/blender/blenlib/BLI_offset_span.hh +++ b/source/blender/blenlib/BLI_offset_span.hh @@ -4,6 +4,7 @@ #pragma once +#include "BLI_random_access_iterator_mixin.hh" #include "BLI_span.hh" namespace blender { @@ -70,7 +71,12 @@ template class OffsetSpan { return {offset_, data_.slice(start, size)}; } - class Iterator { + class Iterator : public iterator::RandomAccessIteratorMixin { + public: + using value_type = T; + using pointer = const T *; + using reference = T; + private: T offset_; const BaseT *data_; @@ -78,21 +84,14 @@ template class OffsetSpan { public: Iterator(const T offset, const BaseT *data) : offset_(offset), data_(data) {} - Iterator &operator++() - { - data_++; - return *this; - } - T operator*() const { return T(*data_) + offset_; } - friend bool operator!=(const Iterator &a, const Iterator &b) + const BaseT *const &iter_prop() const { - BLI_assert(a.offset_ == b.offset_); - return a.data_ != b.data_; + return data_; } }; diff --git a/source/blender/blenlib/BLI_random_access_iterator_mixin.hh b/source/blender/blenlib/BLI_random_access_iterator_mixin.hh new file mode 100644 index 00000000000..d8ac886a2af --- /dev/null +++ b/source/blender/blenlib/BLI_random_access_iterator_mixin.hh @@ -0,0 +1,133 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +namespace blender::iterator { + +/** + * Simplifies implementing a random-access-iterator. + * + * The actual iterator should derive from this class publicly. Additionally, it has to provide a + * const `iter_prop` method which returns a reference to the internal property that corresponds to + * the current position. This is typically a pointer or an index. + * + * Implementing some random-access-iterator is generally quite simple but requires a lot of + * boilerplate code because algorithms expect many operators to work on the iterator type. + * They are expected to behave similarly to pointers and thus have to implement many of the same + * operators. + */ +template class RandomAccessIteratorMixin { + public: + using iterator_category = std::random_access_iterator_tag; + using difference_type = std::ptrdiff_t; + + constexpr friend Derived &operator++(Derived &a) + { + ++a.iter_prop_mutable(); + return a; + } + + constexpr friend Derived operator++(Derived &a, int) + { + Derived copy = a; + ++a; + return copy; + } + + constexpr friend Derived &operator--(Derived &a) + { + --a.iter_prop_mutable(); + return a; + } + + constexpr friend Derived operator--(Derived &a, int) + { + Derived copy = a; + --a; + return copy; + } + + constexpr friend Derived &operator+=(Derived &a, const std::ptrdiff_t n) + { + a.iter_prop_mutable() += n; + return a; + } + + constexpr friend Derived &operator-=(Derived &a, const std::ptrdiff_t n) + { + a.iter_prop_mutable() -= n; + return a; + } + + constexpr friend Derived operator+(const Derived &a, const std::ptrdiff_t n) + { + Derived copy = a; + copy.iter_prop_mutable() += n; + return copy; + } + + constexpr friend Derived operator-(const Derived &a, const std::ptrdiff_t n) + { + Derived copy = a; + copy.iter_prop_mutable() -= n; + return copy; + } + + constexpr friend auto operator-(const Derived &a, const Derived &b) + { + return a.iter_prop() - b.iter_prop(); + } + + constexpr friend bool operator!=(const Derived &a, const Derived &b) + { + return a.iter_prop() != b.iter_prop(); + } + + constexpr friend bool operator==(const Derived &a, const Derived &b) + { + return a.iter_prop() == b.iter_prop(); + } + + constexpr friend bool operator<(const Derived &a, const Derived &b) + { + return a.iter_prop() < b.iter_prop(); + } + + constexpr friend bool operator>(const Derived &a, const Derived &b) + { + return a.iter_prop() > b.iter_prop(); + } + + constexpr friend bool operator<=(const Derived &a, const Derived &b) + { + return a.iter_prop() <= b.iter_prop(); + } + + constexpr friend bool operator>=(const Derived &a, const Derived &b) + { + return a.iter_prop() >= b.iter_prop(); + } + + constexpr decltype(auto) operator[](const std::ptrdiff_t i) + { + return *(*static_cast(this) + i); + } + + constexpr decltype(auto) operator[](const std::ptrdiff_t i) const + { + return *(*static_cast(this) + i); + } + + auto &iter_prop_mutable() + { + const auto &const_iter_prop = static_cast(this)->iter_prop(); + return const_cast> &>( + const_iter_prop); + } +}; + +} // namespace blender::iterator diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index d262bfaf653..a186ab883af 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -330,6 +330,7 @@ set(SRC BLI_quadric.h BLI_rand.h BLI_rand.hh + BLI_random_access_iterator_mixin.hh BLI_range.h BLI_rect.h BLI_resource_scope.hh @@ -545,6 +546,7 @@ if(WITH_GTESTS) tests/BLI_path_util_test.cc tests/BLI_polyfill_2d_test.cc tests/BLI_pool_test.cc + tests/BLI_random_access_iterator_mixin_test.cc tests/BLI_ressource_strings.h tests/BLI_serialize_test.cc tests/BLI_session_uid_test.cc diff --git a/source/blender/blenlib/tests/BLI_random_access_iterator_mixin_test.cc b/source/blender/blenlib/tests/BLI_random_access_iterator_mixin_test.cc new file mode 100644 index 00000000000..2de8d82ff2b --- /dev/null +++ b/source/blender/blenlib/tests/BLI_random_access_iterator_mixin_test.cc @@ -0,0 +1,52 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: Apache-2.0 */ + +#include + +#include "testing/testing.h" + +#include "BLI_random_access_iterator_mixin.hh" +#include "BLI_vector.hh" + +namespace blender::iterator::tests { + +template +struct DoublingIterator : public RandomAccessIteratorMixin> { + private: + const T *data_; + + public: + DoublingIterator(const T *data) : data_(data) {} + + T operator*() const + { + return *data_ * 2; + } + + const T *const &iter_prop() const + { + return data_; + } +}; + +TEST(random_access_iterator_mixin, DoublingIterator) +{ + std::array my_array = {3, 6, 1, 2}; + + const DoublingIterator begin = DoublingIterator(&*my_array.begin()); + const DoublingIterator end = begin + my_array.size(); + + Vector values; + for (DoublingIterator it = begin; it != end; ++it) { + values.append(*it); + } + + EXPECT_EQ(values.size(), 4); + EXPECT_EQ(values[0], 6); + EXPECT_EQ(values[1], 12); + EXPECT_EQ(values[2], 2); + EXPECT_EQ(values[3], 4); +} + +} // namespace blender::iterator::tests