Files
test/source/blender/blenlib/BLI_index_range.hh
Aras Pranckevicius acbd952abf Cleanup: fewer iostreams related includes from BLI/BKE headers
Including <iostream> or similar headers is quite expensive, since it
also pulls in things like <locale> and so on. In many BLI headers,
iostreams are only used to implement some sort of "debug print",
or an operator<< for ostream.

Change some of the commonly used places to instead include <iosfwd>,
which is the standard way of forward-declaring iostreams related
classes, and move the actual debug-print / operator<< implementations
into .cc files.

This is not done for templated classes though (it would be possible
to provide explicit operator<< instantiations somewhere in the
source file, but that would lead to hard-to-figure-out linker error
whenever someone would add a different template type). There, where
possible, I changed from full <iostream> include to only the needed
<ostream> part.

For Span<T>, I just removed print_as_lines since it's not used by
anything. It could be moved into a .cc file using a similar approach
as above if needed.

Doing full blender build changes include counts this way:
- <iostream> 1986 -> 978
- <sstream> 2880 -> 925

It does not affect the total build time much though, mostly because
towards the end of it there's just several CPU cores finishing
compiling OpenVDB related source files.

Pull Request: https://projects.blender.org/blender/blender/pulls/111046
2023-08-16 09:51:37 +02:00

345 lines
8.9 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* 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.
*/
#include <algorithm>
#include <cmath>
#include <iosfwd>
#include "BLI_utildefines.h"
namespace blender {
template<typename T> 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 friend int64_t 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);
}
constexpr friend bool operator!=(IndexRange a, IndexRange b)
{
return !(a == b);
}
/**
* Get the amount of numbers in the range.
*/
constexpr int64_t size() const
{
return size_;
}
constexpr IndexRange index_range() const
{
return IndexRange(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 that contains the intersection of the current one with the given
* range. Returns empty range if there are no overlapping indices. The returned range is always
* a valid slice of this range.
*/
constexpr IndexRange intersect(IndexRange other) const
{
const int64_t old_end = start_ + size_;
const int64_t new_start = std::min(old_end, std::max(start_, other.start_));
const int64_t new_end = std::max(new_start, std::min(old_end, other.start_ + other.size_));
return IndexRange(new_start, new_end - new_start);
}
/**
* 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<int64_t>(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<int64_t>(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<int64_t>(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<int64_t>(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_);
}
friend std::ostream &operator<<(std::ostream &stream, IndexRange range);
};
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