/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #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 an Span * that contains the interval as individual numbers. */ #include #include #include #include "BLI_utildefines.h" /* Forward declare tbb::blocked_range for conversion operations. */ namespace tbb { template class blocked_range; } namespace blender { template class Span; class IndexRange { private: int64_t start_ = 0; int64_t size_ = 0; public: IndexRange() = default; explicit IndexRange(int64_t size) : start_(0), size_(size) { BLI_assert(size >= 0); } IndexRange(int64_t start, int64_t size) : start_(start), size_(size) { BLI_assert(start >= 0); BLI_assert(size >= 0); } template IndexRange(const tbb::blocked_range &range) : start_(range.begin()), size_(range.size()) { } class Iterator { private: int64_t current_; public: Iterator(int64_t current) : current_(current) { } Iterator &operator++() { current_++; return *this; } bool operator!=(const Iterator &iterator) const { return current_ != iterator.current_; } int64_t operator*() const { return current_; } }; Iterator begin() const { return Iterator(start_); } Iterator end() const { return Iterator(start_ + size_); } /** * Access an element in the range. */ 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. */ 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. */ int64_t size() const { return size_; } /** * Create a new range starting at the end of the current one. */ 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. */ 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. */ int64_t first() const { BLI_assert(this->size() > 0); return start_; } /** * Get the last element in the range. * Asserts when the range is empty. */ int64_t last() const { BLI_assert(this->size() > 0); return start_ + size_ - 1; } /** * Get the element one after the end. The returned value is undefined when the range is empty. */ 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. */ int64_t start() const { return start_; } /** * Returns true when the range contains a certain number, otherwise false. */ 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. */ 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); } IndexRange slice(IndexRange range) const { return this->slice(range.start(), range.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; } }; } // namespace blender