BLI: add utility to simplify creating proper random access iterator

The difficulty of implementing this iterator is that it requires lots of operator
overloads which are usually very simple to implement, but result in a lot of code.
The goal of this patch is to abstract the common parts so that it becomes easier
to implement random accessor iterators. Many algorithms can work more
efficiently with random access iterators than with other iterator types.

Also see https://en.cppreference.com/w/cpp/iterator/random_access_iterator

Pull Request: https://projects.blender.org/blender/blender/pulls/118113
This commit is contained in:
Jacques Lucke 2024-02-17 20:59:45 +01:00
parent 4acbda91f8
commit 1e20f06c21
5 changed files with 204 additions and 42 deletions

@ -41,6 +41,7 @@
#include <iosfwd>
#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<Iterator> {
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

@ -4,6 +4,7 @@
#pragma once
#include "BLI_random_access_iterator_mixin.hh"
#include "BLI_span.hh"
namespace blender {
@ -70,7 +71,12 @@ template<typename T, typename BaseT> class OffsetSpan {
return {offset_, data_.slice(start, size)};
}
class Iterator {
class Iterator : public iterator::RandomAccessIteratorMixin<Iterator> {
public:
using value_type = T;
using pointer = const T *;
using reference = T;
private:
T offset_;
const BaseT *data_;
@ -78,21 +84,14 @@ template<typename T, typename BaseT> 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_;
}
};

@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <iterator>
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<typename Derived> 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<Derived *>(this) + i);
}
constexpr decltype(auto) operator[](const std::ptrdiff_t i) const
{
return *(*static_cast<const Derived *>(this) + i);
}
auto &iter_prop_mutable()
{
const auto &const_iter_prop = static_cast<const Derived *>(this)->iter_prop();
return const_cast<std::remove_const_t<std::remove_reference_t<decltype(const_iter_prop)>> &>(
const_iter_prop);
}
};
} // namespace blender::iterator

@ -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

@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include <array>
#include "testing/testing.h"
#include "BLI_random_access_iterator_mixin.hh"
#include "BLI_vector.hh"
namespace blender::iterator::tests {
template<typename T>
struct DoublingIterator : public RandomAccessIteratorMixin<DoublingIterator<T>> {
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<int, 4> my_array = {3, 6, 1, 2};
const DoublingIterator<int> begin = DoublingIterator<int>(&*my_array.begin());
const DoublingIterator<int> end = begin + my_array.size();
Vector<int> values;
for (DoublingIterator<int> 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