mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
EnumerateIterator: Add utility for concisely enumerating ranges
This commit is contained in:
parent
7392c2af2b
commit
cb44d72b78
45 changed files with 750 additions and 10 deletions
426
modules/juce_core/containers/juce_Enumerate.h
Normal file
426
modules/juce_core/containers/juce_Enumerate.h
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto canPreDecrement = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto canPreDecrement<T, std::void_t<decltype (--std::declval<T>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canAddAssign = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canAddAssign<T, I, std::void_t<decltype (std::declval<T>() += std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canSubAssign = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canSubAssign<T, I, std::void_t<decltype (std::declval<T>() -= std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canAdd = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canAdd<T, I, std::void_t<decltype (std::declval<T>() + std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canSub = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canSub<T, I, std::void_t<decltype (std::declval<T>() - std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canLessThan = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canLessThan<T, I, std::void_t<decltype (std::declval<T>() < std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canLessThanEqual = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canLessThanEqual<T, I, std::void_t<decltype (std::declval<T>() <= std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canGreaterThan = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canGreaterThan<T, I, std::void_t<decltype (std::declval<T>() > std::declval<I>())>> = true;
|
||||
|
||||
template <typename T, typename I, typename = void>
|
||||
constexpr auto canGreaterThanEqual = false;
|
||||
|
||||
template <typename T, typename I>
|
||||
constexpr auto canGreaterThanEqual<T, I, std::void_t<decltype (std::declval<T>() >= std::declval<I>())>> = true;
|
||||
|
||||
namespace withAdlSize
|
||||
{
|
||||
using std::size;
|
||||
|
||||
template <typename Range>
|
||||
using AdlSize = decltype (size (std::declval<Range>()));
|
||||
|
||||
template <typename Range>
|
||||
using AdlSignedSize = std::common_type_t<std::ptrdiff_t, std::make_signed_t<AdlSize<Range>>>;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
Returned when dereferencing an EnumerateIterator.
|
||||
|
||||
Allows querying the index associated with an element, along with a reference to the element
|
||||
itself.
|
||||
|
||||
You should never need to construct an instance of this type yourself. Instead, use the
|
||||
enumerate() function to construct a range that can be enumerated.
|
||||
|
||||
@see enumerate()
|
||||
@tags{Core}
|
||||
*/
|
||||
template <typename Index, typename Value>
|
||||
struct Enumerated
|
||||
{
|
||||
Index index;
|
||||
Value value;
|
||||
};
|
||||
|
||||
/**
|
||||
An iterator that wraps some other iterator, keeping track of the relative position of that
|
||||
iterator based on calls to arithmetic operators such as
|
||||
operator++(), operator--(), operator+(), and operator-().
|
||||
|
||||
You should never need to construct an instance of this type yourself. Instead, use the
|
||||
enumerate() function to construct a range that can be enumerated.
|
||||
|
||||
@see enumerate()
|
||||
@tags{Core}
|
||||
*/
|
||||
template <typename Iter, typename Index = ptrdiff_t>
|
||||
class EnumerateIterator
|
||||
{
|
||||
public:
|
||||
/** Default constructor. */
|
||||
constexpr EnumerateIterator() = default;
|
||||
|
||||
/** Wraps the provided iterator, and sets the internal count to 0. */
|
||||
constexpr explicit EnumerateIterator (Iter iter)
|
||||
: EnumerateIterator (std::move (iter), Index{}) {}
|
||||
|
||||
/** Wraps the provided iterator, and sets the internal count to the provided value. */
|
||||
constexpr EnumerateIterator (Iter iter, Index ind)
|
||||
: iterator (std::move (iter)), index (ind) {}
|
||||
|
||||
/** Two EnumerateIterators are considered equal if the wrapped iterators are equal. */
|
||||
template <typename OtherIter, typename OtherInd>
|
||||
[[nodiscard]] constexpr bool operator== (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator == other.iterator;
|
||||
}
|
||||
|
||||
/** @see operator==() */
|
||||
template <typename OtherIter, typename OtherInd>
|
||||
[[nodiscard]] constexpr bool operator!= (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
/** Dereferencing the iterator produces an Enumerated instance *by value*. This type holds
|
||||
a copy of the iterator's current index, along with the result of dereferencing the
|
||||
wrapped iterator (normally a reference type).
|
||||
*/
|
||||
[[nodiscard]] constexpr Enumerated<Index, decltype (*std::declval<Iter>())> operator*() const
|
||||
{
|
||||
return { index, *iterator };
|
||||
}
|
||||
|
||||
/** Increments the iterator and the index. */
|
||||
constexpr EnumerateIterator& operator++()
|
||||
{
|
||||
++iterator;
|
||||
++index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Increments the iterator and the index. */
|
||||
constexpr EnumerateIterator operator++ (int)
|
||||
{
|
||||
auto copy = *this;
|
||||
operator++();
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Decrements the iterator and the index.
|
||||
Only participates in overload resolution if the iterator can be pre-decremented.
|
||||
*/
|
||||
template <typename T = Iter, std::enable_if_t<detail::canPreDecrement<T>, int> = 0>
|
||||
constexpr EnumerateIterator& operator--()
|
||||
{
|
||||
--iterator;
|
||||
--index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Decrements the iterator and the index.
|
||||
Only participates in overload resolution if the iterator can be pre-decremented.
|
||||
*/
|
||||
template <typename T = Iter, std::enable_if_t<detail::canPreDecrement<T>, int> = 0>
|
||||
constexpr EnumerateIterator operator-- (int)
|
||||
{
|
||||
auto copy = *this;
|
||||
operator--();
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Adds an integral value to both the iterator and the index.
|
||||
Only participates in overload resolution if the iterator can be add-assigned.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canAddAssign<Iter&, I>, int> = 0>
|
||||
constexpr EnumerateIterator& operator+= (I diff)
|
||||
{
|
||||
iterator += diff;
|
||||
index += static_cast<Index> (diff);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Subtracts an integral value from both the iterator and the index.
|
||||
Only participates in overload resolution if the iterator can be sub-assigned.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canSubAssign<Iter&, I>, int> = 0>
|
||||
constexpr EnumerateIterator& operator-= (I diff)
|
||||
{
|
||||
iterator -= diff;
|
||||
index -= static_cast<Index> (diff);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Subtracts another enumerate iterator from this one, producing the same result as
|
||||
subtracting the two wrapped iterators. For random-access iterators, this will normally
|
||||
return the distance between the two iterators.
|
||||
Only participates in overload resolution if the wrapped iterators can be subtracted.
|
||||
*/
|
||||
template <typename OtherIter, typename OtherInd, std::enable_if_t<detail::canSub<Iter, OtherIter>, int> = 0>
|
||||
[[nodiscard]] constexpr auto operator- (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator - other.iterator;
|
||||
}
|
||||
|
||||
/** Indexes into this iterator, equivalent to adding an integral value to this iterator and
|
||||
then dereferencing the result.
|
||||
Only participates in overload resolution if the wrapped iterator allows addition of
|
||||
integral values.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canAdd<EnumerateIterator, I>, int> = 0>
|
||||
[[nodiscard]] constexpr auto operator[] (I diff) const
|
||||
{
|
||||
return *(*this + diff);
|
||||
}
|
||||
|
||||
/** Returns the result of comparing the two wrapped iterators.
|
||||
Only participates in overload resolution if the wrapped iterators are comparable.
|
||||
*/
|
||||
template <typename OtherIter, typename OtherInd, std::enable_if_t<detail::canLessThan<Iter, OtherIter>, int> = 0>
|
||||
[[nodiscard]] constexpr bool operator< (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator < other.iterator;
|
||||
}
|
||||
|
||||
/** Returns the result of comparing the two wrapped iterators.
|
||||
Only participates in overload resolution if the wrapped iterators are comparable.
|
||||
*/
|
||||
template <typename OtherIter, typename OtherInd, std::enable_if_t<detail::canLessThanEqual<Iter, OtherIter>, int> = 0>
|
||||
[[nodiscard]] constexpr bool operator<= (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator <= other.iterator;
|
||||
}
|
||||
|
||||
/** Returns the result of comparing the two wrapped iterators.
|
||||
Only participates in overload resolution if the wrapped iterators are comparable.
|
||||
*/
|
||||
template <typename OtherIter, typename OtherInd, std::enable_if_t<detail::canGreaterThan<Iter, OtherIter>, int> = 0>
|
||||
[[nodiscard]] constexpr bool operator> (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator > other.iterator;
|
||||
}
|
||||
|
||||
/** Returns the result of comparing the two wrapped iterators.
|
||||
Only participates in overload resolution if the wrapped iterators are comparable.
|
||||
*/
|
||||
template <typename OtherIter, typename OtherInd, std::enable_if_t<detail::canGreaterThanEqual<Iter, OtherIter>, int> = 0>
|
||||
[[nodiscard]] constexpr bool operator>= (const EnumerateIterator<OtherIter, OtherInd>& other) const
|
||||
{
|
||||
return iterator >= other.iterator;
|
||||
}
|
||||
|
||||
/** Returns the result of adding an integral value to this iterator.
|
||||
Only participates in overload resolution if addition is supported by the wrapped iterator.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canAddAssign<EnumerateIterator&, I>, int> = 0>
|
||||
constexpr friend auto operator+ (EnumerateIterator iter, I ind)
|
||||
{
|
||||
return iter += ind;
|
||||
}
|
||||
|
||||
/** Returns the result of adding an integral value to this iterator.
|
||||
Only participates in overload resolution if addition is supported by the wrapped iterator.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canAddAssign<EnumerateIterator&, I>, int> = 0>
|
||||
constexpr friend auto operator+ (I ind, EnumerateIterator iter)
|
||||
{
|
||||
return iter += ind;
|
||||
}
|
||||
|
||||
/** Returns the result of subtracting an integral value from this iterator.
|
||||
Only participates in overload resolution if subtraction is supported by the wrapped iterator.
|
||||
*/
|
||||
template <typename I, std::enable_if_t<detail::canSubAssign<EnumerateIterator&, I>, int> = 0>
|
||||
constexpr friend auto operator- (EnumerateIterator iter, I ind)
|
||||
{
|
||||
return iter -= ind;
|
||||
}
|
||||
|
||||
private:
|
||||
Iter iterator{};
|
||||
Index index = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps a pair of iterators, providing member begin() and end() functions that return
|
||||
those iterators.
|
||||
This is useful in situations where you have an iterator pair, but want to use that
|
||||
pair somewhere that requires an iterable range, such as in a ranged-for loop.
|
||||
|
||||
@see makeRange()
|
||||
*/
|
||||
template <typename Begin, typename End>
|
||||
class IteratorPair
|
||||
{
|
||||
public:
|
||||
/** Constructs a pair from a begin and end iterator.
|
||||
Instead of calling this directly, use makeRange().
|
||||
*/
|
||||
constexpr IteratorPair (Begin bIn, End eIn)
|
||||
: b (std::move (bIn)), e (std::move (eIn)) {}
|
||||
|
||||
/** Returns the begin iterator. */
|
||||
constexpr auto begin() const { return b; }
|
||||
|
||||
/** Returns the end iterator. */
|
||||
constexpr auto end() const { return e; }
|
||||
|
||||
private:
|
||||
Begin b;
|
||||
End e;
|
||||
};
|
||||
|
||||
/**
|
||||
Given two iterators "begin" and "end", returns an IteratorPair with a member
|
||||
begin() and end() function. This pair can be used in contexts that expect an
|
||||
iterable range, the most significant of which is ranged-for loops.
|
||||
This automatically deduces the Begin and End types, so it is more concise to use than
|
||||
directly calling the IteratorPair constructor.
|
||||
*/
|
||||
template <typename Begin, typename End = Begin>
|
||||
[[nodiscard]] constexpr auto makeRange (Begin begin, End end)
|
||||
{
|
||||
return IteratorPair<Begin, End> { std::move (begin), std::move (end) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Given a range and an optional starting offset, returns an IteratorPair that
|
||||
holds EnumerateIterators wrapping the begin() and end() of the range.
|
||||
|
||||
This is useful in situations where you need to iterate some range, but also query
|
||||
the position of each item in the range.
|
||||
|
||||
A simple usage might look like this:
|
||||
|
||||
@code
|
||||
std::list<int> elements { 10, 20, 30, 40, 50 };
|
||||
|
||||
for (const auto pair : enumerate (elements))
|
||||
std::cout << pair.index << ' ' << pair.value << ' ';
|
||||
|
||||
// output: 0 10 1 20 2 30 3 40 4 50
|
||||
@endcode
|
||||
|
||||
You can also use structured bindings to concisely destructure each Enumerated instance:
|
||||
|
||||
@code
|
||||
for (const auto [index, value] : enumerate (elements))
|
||||
std::cout << index << ' ' << value << ' ';
|
||||
@endcode
|
||||
|
||||
Note that the Enumerated instance is returned by value. This is because each Enumerated
|
||||
instance is created on-demand when the iterator is dereferenced. As a result, the following
|
||||
will result in a dangling reference, and will probably trigger a compiler warning:
|
||||
|
||||
@code
|
||||
// BAD IDEA: creating a reference to a temporary Enumerated instance
|
||||
for (auto& [index, value] : enumerate (elements))
|
||||
...
|
||||
@endcode
|
||||
|
||||
The 'value' member of Enumerated automatically assumes the same type as dereferencing the
|
||||
wrapped iterator, which is normally a reference to an element of a container.
|
||||
In the following snippet, the type of '[index, value]' is 'const Enumerated<ptrdiff_t, int&>',
|
||||
the type of 'index' is 'ptrdiff_t', and the type of 'value' is 'int&'.
|
||||
|
||||
@code
|
||||
std::vector<int> elements { 10, 20, 30, 40, 50 };
|
||||
for (const auto [index, value] : enumerate (elements))
|
||||
...
|
||||
@endcode
|
||||
|
||||
By default, the constness of pair.value will match the constness of the range passed to
|
||||
enumerate. If you pass a mutable lvalue reference to enumerate, then each value will also
|
||||
be mutable. If you pass a constant lvalue reference to enumerate, then each value will be
|
||||
const. If you know that you don't need the iterated elements to be mutable, it's good
|
||||
practice to wrap the range with std::as_const before passing it to enumerate:
|
||||
|
||||
@code
|
||||
for (const auto [index, value] : enumerate (std::as_const (elements)))
|
||||
{
|
||||
// value is immutable here
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
template <typename Range, typename Index = detail::withAdlSize::AdlSignedSize<Range>>
|
||||
[[nodiscard]] constexpr auto enumerate (Range&& range, Index startingValue = {})
|
||||
{
|
||||
// This ensures argument-dependent lookup works properly for user-defined non-member begin/end
|
||||
using std::begin, std::end;
|
||||
return makeRange (EnumerateIterator { begin (range), startingValue },
|
||||
EnumerateIterator { end (range), startingValue });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
126
modules/juce_core/containers/juce_Enumerate_test.cpp
Normal file
126
modules/juce_core/containers/juce_Enumerate_test.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class EnumerateUnitTest : public UnitTest
|
||||
{
|
||||
public:
|
||||
EnumerateUnitTest() : UnitTest ("Enumerate", UnitTestCategories::containers) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("enumeration works for bidirectional iterators");
|
||||
{
|
||||
const std::list<int> elements { 10, 20, 30, 40, 50 };
|
||||
std::vector<int> counts;
|
||||
|
||||
for (const auto pair : enumerate (elements))
|
||||
counts.push_back ((int) pair.index);
|
||||
|
||||
expect (counts == std::vector<int> { 0, 1, 2, 3, 4 });
|
||||
}
|
||||
|
||||
beginTest ("enumeration works for random-access iterators");
|
||||
{
|
||||
const std::vector<std::string> strings { "a", "bb", "ccc", "dddd", "eeeee" };
|
||||
|
||||
std::vector<int> counts;
|
||||
|
||||
for (const auto [count, element] : enumerate (strings))
|
||||
counts.push_back ((int) ((size_t) count + element.size()));
|
||||
|
||||
expect (counts == std::vector<int> { 1, 3, 5, 7, 9 });
|
||||
}
|
||||
|
||||
beginTest ("enumeration works for mutable ranges");
|
||||
{
|
||||
std::vector<std::string> strings { "", "", "", "", "" };
|
||||
|
||||
for (const auto [count, element] : enumerate (strings))
|
||||
element = std::to_string (count);
|
||||
|
||||
expect (strings == std::vector<std::string> { "0", "1", "2", "3", "4" });
|
||||
}
|
||||
|
||||
beginTest ("iterator can be incremented by more than one");
|
||||
{
|
||||
std::vector<int> ints (6);
|
||||
|
||||
const auto enumerated = enumerate (ints);
|
||||
|
||||
std::vector<int> counts;
|
||||
|
||||
for (auto b = enumerated.begin(), e = enumerated.end(); b != e; b += 2)
|
||||
counts.push_back ((int) (*b).index);
|
||||
|
||||
expect (counts == std::vector<int> { 0, 2, 4 });
|
||||
}
|
||||
|
||||
beginTest ("iterator can be started at a non-zero value");
|
||||
{
|
||||
const std::vector<int> ints (6);
|
||||
|
||||
std::vector<int> counts;
|
||||
|
||||
for (const auto enumerated : enumerate (ints, 5))
|
||||
counts.push_back ((int) enumerated.index);
|
||||
|
||||
expect (counts == std::vector<int> { 5, 6, 7, 8, 9, 10 });
|
||||
}
|
||||
|
||||
beginTest ("subtracting two EnumerateIterators returns the difference between the base iterators");
|
||||
{
|
||||
const std::vector<int> ints (6);
|
||||
const auto enumerated = enumerate (ints);
|
||||
expect ((int) (enumerated.end() - enumerated.begin()) == (int) ints.size());
|
||||
}
|
||||
|
||||
beginTest ("EnumerateIterator can be decremented");
|
||||
{
|
||||
const std::vector<int> ints (5);
|
||||
std::vector<int> counts;
|
||||
|
||||
const auto enumerated = enumerate (std::as_const (ints));
|
||||
|
||||
for (auto i = enumerated.end(), b = enumerated.begin(); i != b; --i)
|
||||
counts.push_back ((int) (*(i - 1)).index);
|
||||
|
||||
expect (counts == std::vector<int> { -1, -2, -3, -4, -5 });
|
||||
}
|
||||
|
||||
beginTest ("EnumerateIterator can be compared");
|
||||
{
|
||||
const std::vector<int> ints (6);
|
||||
const auto enumerated = enumerate (ints);
|
||||
expect (enumerated.begin() < enumerated.end());
|
||||
expect (enumerated.begin() <= enumerated.end());
|
||||
expect (enumerated.end() > enumerated.begin());
|
||||
expect (enumerated.end() >= enumerated.begin());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static EnumerateUnitTest enumerateUnitTest;
|
||||
|
||||
} // namespace juce
|
||||
Loading…
Add table
Add a link
Reference in a new issue