Skip to content

Commit 7a12219

Browse files
committed
Add zip and enumerate iteration helpers.
1 parent f50d7fa commit 7a12219

File tree

5 files changed

+233
-45
lines changed

5 files changed

+233
-45
lines changed

core/templates/command_queue_mt.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class CommandQueueMT {
7171

7272
// This method exists so we can call it in the parameter pack expansion in call_impl.
7373
template <size_t I>
74-
_FORCE_INLINE_ auto &get() { return ::tuple_get<I>(args); }
74+
_FORCE_INLINE_ auto &get() { return args.template get<I>(); }
7575
};
7676

7777
// Separate class from Command so we can save the space of the ret pointer for commands that don't return.
@@ -98,7 +98,7 @@ class CommandQueueMT {
9898

9999
// This method exists so we can call it in the parameter pack expansion in call_impl.
100100
template <size_t I>
101-
_FORCE_INLINE_ auto &get() { return ::tuple_get<I>(args); }
101+
_FORCE_INLINE_ auto &get() { return args.template get<I>(); }
102102
};
103103

104104
/***** BASE *******/

core/templates/iterable.h

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,53 @@
3030

3131
#pragma once
3232

33+
#include "core/templates/tuple.h"
34+
#include "core/typedefs.h"
35+
36+
// Like std::begin, but without an expensive include.
37+
template <class T, size_t SIZE>
38+
T *std_begin(T (&array)[SIZE]) {
39+
return array;
40+
}
41+
42+
template <class T, size_t SIZE>
43+
T *std_end(T (&array)[SIZE]) {
44+
return array + SIZE;
45+
}
46+
47+
template <class T, size_t SIZE>
48+
const T *std_begin(const T (&array)[SIZE]) {
49+
return array;
50+
}
51+
52+
template <class T, size_t SIZE>
53+
const T *std_end(const T (&array)[SIZE]) {
54+
return array + SIZE;
55+
}
56+
57+
template <class T>
58+
auto std_begin(T &t) -> decltype(t.begin()) {
59+
return t.begin();
60+
}
61+
62+
template <class T>
63+
auto std_end(T &t) -> decltype(t.end()) {
64+
return t.end();
65+
}
66+
67+
template <class T>
68+
auto std_begin(const T &t) -> decltype(t.begin()) {
69+
return t.begin();
70+
}
71+
72+
template <class T>
73+
auto std_end(const T &t) -> decltype(t.end()) {
74+
return t.end();
75+
}
76+
77+
/// Can be returned from a function and directly iterated.
78+
/// Example usage:
79+
/// for (int i : object.get_iterable()) { ... }
3380
template <typename I>
3481
class Iterable {
3582
I _begin;
@@ -44,3 +91,92 @@ class Iterable {
4491
Iterable(const I &begin, const I &end) :
4592
_begin(begin), _end(end) {}
4693
};
94+
95+
template <typename T>
96+
struct IteratorType {
97+
using type = decltype(std_begin(std::declval<T>()));
98+
};
99+
100+
template <typename T>
101+
using IteratorTypeT = typename IteratorType<T>::type;
102+
103+
template <typename T, typename... Rest>
104+
bool _tuple_any_elements_equal(const Tuple<T, Rest...> &p_lhs, const Tuple<T, Rest...> &p_rhs) {
105+
if constexpr (sizeof...(Rest) == 0) {
106+
return p_lhs.value == p_rhs.value;
107+
} else {
108+
return p_lhs.value == p_rhs.value || _tuple_any_elements_equal(static_cast<const Tuple<Rest...> &>(p_lhs), static_cast<const Tuple<Rest...> &>(p_rhs));
109+
}
110+
}
111+
112+
template <typename T, typename... Rest>
113+
void _tuple_increment(Tuple<T, Rest...> &p_tuple) {
114+
p_tuple.value++;
115+
if constexpr (sizeof...(Rest) > 0) {
116+
_tuple_increment<Rest...>(p_tuple);
117+
}
118+
}
119+
120+
template <typename... T, typename... T1, size_t... Is>
121+
Tuple<T...> _tuple_dereference_impl(Tuple<T1...> &p_tuple, IndexSequence<Is...>) {
122+
return Tuple<T...>{ *p_tuple.template get<Is>()... };
123+
}
124+
125+
template <typename TUPLE, typename... T>
126+
struct ZipShortestIterator {
127+
TUPLE iterators;
128+
129+
Tuple<T...> operator*() {
130+
return _tuple_dereference_impl<T...>(iterators, BuildIndexSequence<sizeof...(T)>{});
131+
}
132+
ZipShortestIterator &operator++() {
133+
_tuple_increment(iterators);
134+
return *this;
135+
}
136+
bool operator!=(const ZipShortestIterator &iter) const {
137+
return !_tuple_any_elements_equal(iterators, iter.iterators);
138+
}
139+
};
140+
141+
/// Can be used to iterate multiple iterables together.
142+
/// The iteration stops with the shortest iterator.
143+
/// Example usage:
144+
/// for (auto [ai, bi, ci] : zip_shortest<A, B, C>(a, b, c)) { ... }
145+
template <typename... T, typename... ITER>
146+
Iterable<ZipShortestIterator<Tuple<IteratorTypeT<ITER>...>, T...>> zip_shortest(ITER &&...t) {
147+
static_assert(sizeof...(T) == sizeof...(ITER));
148+
using TUPLE = Tuple<IteratorTypeT<ITER>...>;
149+
return Iterable<ZipShortestIterator<TUPLE, T...>>{
150+
ZipShortestIterator<TUPLE, T...>{ TUPLE{ std_begin(t)... } },
151+
ZipShortestIterator<TUPLE, T...>{ TUPLE{ std_end(t)... } }
152+
};
153+
}
154+
155+
template <typename IDX, typename VALUE, typename T>
156+
struct EnumerateIterator {
157+
size_t index;
158+
T iterator;
159+
160+
Tuple<IDX, VALUE> operator*() {
161+
return { index, *iterator };
162+
}
163+
EnumerateIterator &operator++() {
164+
index++;
165+
iterator++;
166+
return *this;
167+
}
168+
bool operator!=(const EnumerateIterator &iter) {
169+
return iterator != iter.iterator;
170+
}
171+
};
172+
173+
/// Can be used to count an index with an iterable.
174+
/// Example usage:
175+
/// for (auto [index, ai] : enumerate<size_t, A>(a)) { ... }
176+
template <typename IDX, typename VALUE, typename ITER>
177+
Iterable<EnumerateIterator<IDX, VALUE, IteratorTypeT<ITER>>> enumerate(ITER &&t) {
178+
return Iterable{
179+
EnumerateIterator<IDX, VALUE, IteratorTypeT<ITER>>{ 0, std_begin(t) },
180+
EnumerateIterator<IDX, VALUE, IteratorTypeT<ITER>>{ 0, std_end(t) }
181+
};
182+
}

core/templates/tuple.h

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,6 @@
4949
// Tuple<int, float>
5050
// step 1: Tuple T = int, Rest = float. Results in a Tuple<int> : Tuple<float>
5151
// step 2: Tuple T = float, no Rest. Results in a Tuple<float>
52-
//
53-
// tuple_get<I> works through a similar recursion, using the inheritance chain to walk to the right node.
54-
// In order to tuple_get<1>(my_tuple), from the example tuple above:
55-
//
56-
// 1. We want tuple_get<1> to return the float, which is one level "up" from Tuple<int> : Tuple<float>,
57-
// (the real type of the Tuple "root").
58-
// 2. Since index 1 > 0, it casts the tuple to its parent type (Tuple<float>). This works because
59-
// we cast to Tuple<Rest...> which in this case is just float.
60-
// 3. Now we're looking for index 0 in Tuple<float>, which directly returns its value field. Note
61-
// how get<0> is a template specialization.
62-
//
63-
// At compile time, this gets fully resolved. The compiler sees get<1>(my_tuple) and:
64-
// 1. Creates TupleGet<1, Tuple<int, float>>::tuple_get which contains the cast to Tuple<float>.
65-
// 2. Creates TupleGet<0, Tuple<float>>::tuple_get which directly returns the value.
66-
// 3. The compiler will then simply optimize all of this nonsense away and return the float directly.
6752

6853
#include "core/typedefs.h"
6954

@@ -83,40 +68,40 @@ struct Tuple<T, Rest...> : Tuple<Rest...> {
8368
_FORCE_INLINE_ Tuple(F &&f, R &&...rest) :
8469
Tuple<Rest...>(std::forward<R>(rest)...),
8570
value(std::forward<F>(f)) {}
86-
};
87-
88-
// Tuple is zero-constructible if and only if all constrained types are zero-constructible.
89-
template <typename... Types>
90-
struct is_zero_constructible<Tuple<Types...>> : std::conjunction<is_zero_constructible<Types>...> {};
9171

92-
template <size_t I, typename Tuple>
93-
struct TupleGet;
72+
template <std::size_t Index>
73+
std::tuple_element_t<Index, Tuple> &get() {
74+
if constexpr (Index == 0) {
75+
return value;
76+
} else {
77+
return Tuple<Rest...>::template get<Index - 1>();
78+
}
79+
}
9480

95-
template <typename First, typename... Rest>
96-
struct TupleGet<0, Tuple<First, Rest...>> {
97-
_FORCE_INLINE_ static First &tuple_get(Tuple<First, Rest...> &t) {
98-
return t.value;
81+
template <std::size_t Index>
82+
const std::tuple_element_t<Index, Tuple> &get() const {
83+
if constexpr (Index == 0) {
84+
return value;
85+
} else {
86+
return Tuple<Rest...>::template get<Index - 1>();
87+
}
9988
}
10089
};
10190

102-
// Rationale for using auto here is that the alternative is writing a
103-
// helper struct to create an otherwise useless type. we would have to write
104-
// a second recursive template chain like: TupleGetType<I, Tuple<First, Rest...>>::type
105-
// just to recover the type in the most baroque way possible.
91+
namespace std {
92+
template <typename... Args>
93+
struct tuple_size<Tuple<Args...>> : std::integral_constant<std::size_t, sizeof...(Args)> {};
10694

107-
template <size_t I, typename First, typename... Rest>
108-
struct TupleGet<I, Tuple<First, Rest...>> {
109-
_FORCE_INLINE_ static auto &tuple_get(Tuple<First, Rest...> &t) {
110-
return TupleGet<I - 1, Tuple<Rest...>>::tuple_get(static_cast<Tuple<Rest...> &>(t));
111-
}
95+
template <typename T, typename... Rest>
96+
struct tuple_element<0, Tuple<T, Rest...>> {
97+
using type = T;
11298
};
11399

114-
template <size_t I, typename... Types>
115-
_FORCE_INLINE_ auto &tuple_get(Tuple<Types...> &t) {
116-
return TupleGet<I, Tuple<Types...>>::tuple_get(t);
117-
}
100+
template <std::size_t Index, typename T, typename... Rest>
101+
struct tuple_element<Index, Tuple<T, Rest...>>
102+
: tuple_element<Index - 1, Tuple<Rest...>> {};
103+
} //namespace std
118104

119-
template <size_t I, typename... Types>
120-
_FORCE_INLINE_ const auto &tuple_get(const Tuple<Types...> &t) {
121-
return TupleGet<I, Tuple<Types...>>::tuple_get(t);
122-
}
105+
// Tuple is zero-constructible if and only if all constrained types are zero-constructible.
106+
template <typename... Types>
107+
struct is_zero_constructible<Tuple<Types...>> : std::conjunction<is_zero_constructible<Types>...> {};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**************************************************************************/
2+
/* test_iterable.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "core/templates/iterable.h"
34+
35+
#include "tests/test_macros.h"
36+
37+
namespace TestZip {
38+
TEST_CASE("[zip] Basic tests") {
39+
constexpr uint16_t a[5] = { 1, 2, 3, 4, 5 };
40+
constexpr uint32_t b[5] = { 2, 3, 4, 5, 6 };
41+
42+
size_t i = 0;
43+
for (auto [ai, bi] : zip_shortest<uint16_t, uint32_t>(a, b)) {
44+
CHECK_EQ(ai, i + 1);
45+
CHECK_EQ(bi, i + 2);
46+
i++;
47+
}
48+
CHECK_EQ(i, 5);
49+
}
50+
} //namespace TestZip
51+
52+
namespace TestEnumerate {
53+
54+
TEST_CASE("[zip] Enumerate tests") {
55+
constexpr uint32_t a[5] = { 1, 2, 3, 4, 5 };
56+
57+
size_t i = 0;
58+
for (auto [idx, ai] : enumerate<size_t, uint32_t>(a)) {
59+
CHECK_EQ(idx, i);
60+
CHECK_EQ(ai, i + 1);
61+
i++;
62+
}
63+
CHECK_EQ(i, 5);
64+
}
65+
66+
} //namespace TestEnumerate

tests/test_main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
#include "tests/core/templates/test_fixed_vector.h"
102102
#include "tests/core/templates/test_hash_map.h"
103103
#include "tests/core/templates/test_hash_set.h"
104+
#include "tests/core/templates/test_iterable.h"
104105
#include "tests/core/templates/test_list.h"
105106
#include "tests/core/templates/test_local_vector.h"
106107
#include "tests/core/templates/test_lru.h"

0 commit comments

Comments
 (0)