/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * A `blender::IndexRange` wraps an interval of non-negative integers. It can be used to reference * consecutive elements in an array. Furthermore, it can make for loops more convenient and less * error prone, especially when using nested loops. * * I'd argue that the second loop is more readable and less error prone than the first one. That is * not necessarily always the case, but often it is. * * for (int64_t i = 0; i < 10; i++) { * for (int64_t j = 0; j < 20; j++) { * for (int64_t k = 0; k < 30; k++) { * * for (int64_t i : IndexRange(10)) { * for (int64_t j : IndexRange(20)) { * for (int64_t k : IndexRange(30)) { * * Some containers like blender::Vector have an index_range() method. This will return the * IndexRange that contains all indices that can be used to access the container. This is * particularly useful when you want to iterate over the indices and the elements (much like * Python's enumerate(), just worse). Again, I think the second example here is better: * * for (int64_t i = 0; i < my_vector_with_a_long_name.size(); i++) { * do_something(i, my_vector_with_a_long_name[i]); * * for (int64_t i : my_vector_with_a_long_name.index_range()) { * do_something(i, my_vector_with_a_long_name[i]); * * Ideally this could be could be even closer to Python's enumerate(). We might get that in the * future with newer C++ versions. * * One other important feature is the as_span method. This method returns a Span * that contains the interval as individual numbers. */ #include #include #include #include #include "BLI_utildefines.h" namespace blender { template class Span; class IndexRange { private: int64_t start_ = 0; int64_t size_ = 0; public: constexpr IndexRange() = default; constexpr explicit IndexRange(int64_t size) : start_(0), size_(size) { BLI_assert(size >= 0); } constexpr IndexRange(int64_t start, int64_t size) : start_(start), size_(size) { BLI_assert(start >= 0); BLI_assert(size >= 0); } class 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; private: int64_t current_; 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 int64_t operator*() const { return current_; } }; constexpr Iterator begin() const { return Iterator(start_); } constexpr Iterator end() const { return Iterator(start_ + size_); } /** * Access an element in the range. */ constexpr int64_t operator[](int64_t index) const { BLI_assert(index >= 0); BLI_assert(index < this->size()); return start_ + index; } /** * Two ranges compare equal when they contain the same numbers. */ constexpr friend bool operator==(IndexRange a, IndexRange b) { return (a.size_ == b.size_) && (a.start_ == b.start_ || a.size_ == 0); } /** * Get the amount of numbers in the range. */ constexpr int64_t size() const { return size_; } /** * Returns true if the size is zero. */ constexpr bool is_empty() const { return size_ == 0; } /** * Create a new range starting at the end of the current one. */ constexpr IndexRange after(int64_t n) const { BLI_assert(n >= 0); return IndexRange(start_ + size_, n); } /** * Create a new range that ends at the start of the current one. */ constexpr IndexRange before(int64_t n) const { BLI_assert(n >= 0); return IndexRange(start_ - n, n); } /** * Get the first element in the range. * Asserts when the range is empty. */ constexpr int64_t first() const { BLI_assert(this->size() > 0); return start_; } /** * Get the nth last element in the range. * Asserts when the range is empty or when n is negative. */ constexpr int64_t last(const int64_t n = 0) const { BLI_assert(n >= 0); BLI_assert(n < size_); BLI_assert(this->size() > 0); return start_ + size_ - 1 - n; } /** * Get the element one before the beginning. The returned value is undefined when the range is * empty, and the range must start after zero already. */ constexpr int64_t one_before_start() const { BLI_assert(start_ > 0); return start_ - 1; } /** * Get the element one after the end. The returned value is undefined when the range is empty. */ constexpr int64_t one_after_last() const { return start_ + size_; } /** * Get the first element in the range. The returned value is undefined when the range is empty. */ constexpr int64_t start() const { return start_; } /** * Returns true when the range contains a certain number, otherwise false. */ constexpr bool contains(int64_t value) const { return value >= start_ && value < start_ + size_; } /** * Returns a new range, that contains a sub-interval of the current one. */ constexpr IndexRange slice(int64_t start, int64_t size) const { BLI_assert(start >= 0); BLI_assert(size >= 0); int64_t new_start = start_ + start; BLI_assert(new_start + size <= start_ + size_ || size == 0); return IndexRange(new_start, size); } constexpr IndexRange slice(IndexRange range) const { return this->slice(range.start(), range.size()); } /** * Returns a new IndexRange with n elements removed from the beginning of the range. * This invokes undefined behavior when n is negative. */ constexpr IndexRange drop_front(int64_t n) const { BLI_assert(n >= 0); const int64_t new_size = std::max(0, size_ - n); return IndexRange(start_ + n, new_size); } /** * Returns a new IndexRange with n elements removed from the end of the range. * This invokes undefined behavior when n is negative. */ constexpr IndexRange drop_back(int64_t n) const { BLI_assert(n >= 0); const int64_t new_size = std::max(0, size_ - n); return IndexRange(start_, new_size); } /** * Returns a new IndexRange that only contains the first n elements. This invokes undefined * behavior when n is negative. */ constexpr IndexRange take_front(int64_t n) const { BLI_assert(n >= 0); const int64_t new_size = std::min(size_, n); return IndexRange(start_, new_size); } /** * Returns a new IndexRange that only contains the last n elements. This invokes undefined * behavior when n is negative. */ constexpr IndexRange take_back(int64_t n) const { BLI_assert(n >= 0); const int64_t new_size = std::min(size_, n); return IndexRange(start_ + size_ - new_size, new_size); } /** * Move the range forward or backward within the larger array. The amount may be negative, * but its absolute value cannot be greater than the existing start of the range. */ constexpr IndexRange shift(int64_t n) const { return IndexRange(start_ + n, size_); } /** * Get read-only access to a memory buffer that contains the range as actual numbers. */ Span as_span() const; friend std::ostream &operator<<(std::ostream &stream, IndexRange range) { stream << "[" << range.start() << ", " << range.one_after_last() << ")"; return stream; } private: static std::atomic s_current_array_size; static std::atomic s_current_array; Span as_span_internal() const; }; struct AlignedIndexRanges { IndexRange prefix; IndexRange aligned; IndexRange suffix; }; /** * Split a range into three parts so that the boundaries of the middle part are aligned to some * power of two. * * This can be used when an algorithm can be optimized on aligned indices/memory. The algorithm * then needs a slow path for the beginning and end, and a fast path for the aligned elements. */ AlignedIndexRanges split_index_range_by_alignment(const IndexRange range, const int64_t alignment); } // namespace blender