Skip to content

Commit 114c96d

Browse files
committed
🎨 Allow tuple transform to work on different length tuples
1 parent 4f3d1cb commit 114c96d

File tree

3 files changed

+59
-39
lines changed

3 files changed

+59
-39
lines changed

docs/tuple_algorithms.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ auto t2 = stdx::tuple{2, 3, 4};
274274
auto u = stdx::transform(std::multiplies{}, t1, t2); // {2, 6, 12}
275275
----
276276

277+
NOTE: It's OK to zip together different length tuples: `transform` will produce
278+
a tuple that is the length of the shortest input.
279+
277280
`transform` can also apply xref:tuple.adoc#_indexed_tuples[indexing functions]
278281
while it transforms:
279282
[source,cpp]

include/stdx/tuple_algorithms.hpp

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -140,39 +140,40 @@ template <std::size_t I, typename... Ts>
140140
constexpr auto invoke_at(auto &&op, Ts &&...ts) -> decltype(auto) {
141141
return op(std::forward<Ts>(ts)[index<I>]...);
142142
}
143+
144+
template <typename... Ts>
145+
constexpr std::size_t zip_length_for =
146+
sizeof...(Ts) == 0
147+
? 0
148+
: std::min({stdx::tuple_size_v<std::remove_cvref_t<Ts>>...});
143149
} // namespace detail
144150

145-
template <template <typename> typename... Fs, typename Op, tuplelike T,
146-
tuplelike... Ts>
147-
constexpr auto transform(Op &&op, T &&t, Ts &&...ts) {
151+
template <template <typename> typename... Fs, typename Op, tuplelike... Ts>
152+
constexpr auto transform(Op &&op, Ts &&...ts) {
148153
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
149154
if constexpr (sizeof...(Fs) == 0) {
150155
return stdx::tuple<decltype(detail::invoke_at<Is>(
151-
std::forward<Op>(op), std::forward<T>(t),
152-
std::forward<Ts>(ts)...))...>{
153-
detail::invoke_at<Is>(std::forward<Op>(op), std::forward<T>(t),
156+
std::forward<Op>(op), std::forward<Ts>(ts)...))...>{
157+
detail::invoke_at<Is>(std::forward<Op>(op),
154158
std::forward<Ts>(ts)...)...};
155159
} else {
156-
return stdx::make_indexed_tuple<Fs...>(
157-
detail::invoke_at<Is>(std::forward<Op>(op), std::forward<T>(t),
158-
std::forward<Ts>(ts)...)...);
160+
return stdx::make_indexed_tuple<Fs...>(detail::invoke_at<Is>(
161+
std::forward<Op>(op), std::forward<Ts>(ts)...)...);
159162
}
160-
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
163+
}(std::make_index_sequence<detail::zip_length_for<Ts...>>{});
161164
}
162165

163-
template <typename Op, typename T, typename... Ts>
164-
constexpr auto unrolled_for_each(Op &&op, T &&t, Ts &&...ts) -> Op {
166+
template <typename Op, typename... Ts>
167+
constexpr auto unrolled_for_each(Op &&op, Ts &&...ts) -> Op {
165168
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
166-
(detail::invoke_at<Is>(op, std::forward<T>(t), std::forward<Ts>(ts)...),
167-
...);
168-
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
169+
(detail::invoke_at<Is>(op, std::forward<Ts>(ts)...), ...);
170+
}(std::make_index_sequence<detail::zip_length_for<Ts...>>{});
169171
return op;
170172
}
171173

172-
template <typename Op, tuplelike T, tuplelike... Ts>
173-
constexpr auto for_each(Op &&op, T &&t, Ts &&...ts) -> Op {
174-
return unrolled_for_each(std::forward<Op>(op), std::forward<T>(t),
175-
std::forward<Ts>(ts)...);
174+
template <typename Op, tuplelike... Ts>
175+
constexpr auto for_each(Op &&op, Ts &&...ts) -> Op {
176+
return unrolled_for_each(std::forward<Op>(op), std::forward<Ts>(ts)...);
176177
}
177178

178179
namespace detail {
@@ -182,36 +183,31 @@ constexpr auto invoke_with_idx_at(auto &&op, Ts &&...ts) -> decltype(auto) {
182183
}
183184
} // namespace detail
184185

185-
template <typename Op, typename T, typename... Ts>
186-
constexpr auto unrolled_enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
186+
template <typename Op, typename... Ts>
187+
constexpr auto unrolled_enumerate(Op &&op, Ts &&...ts) -> Op {
187188
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
188-
(detail::invoke_with_idx_at<Is>(op, std::forward<T>(t),
189-
std::forward<Ts>(ts)...),
190-
...);
191-
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
189+
(detail::invoke_with_idx_at<Is>(op, std::forward<Ts>(ts)...), ...);
190+
}(std::make_index_sequence<detail::zip_length_for<Ts...>>{});
192191
return op;
193192
}
194193

195-
template <typename Op, tuplelike T, tuplelike... Ts>
196-
constexpr auto enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
197-
return unrolled_enumerate(std::forward<Op>(op), std::forward<T>(t),
198-
std::forward<Ts>(ts)...);
194+
template <typename Op, tuplelike... Ts>
195+
constexpr auto enumerate(Op &&op, Ts &&...ts) -> Op {
196+
return unrolled_enumerate(std::forward<Op>(op), std::forward<Ts>(ts)...);
199197
}
200198

201-
template <typename F, tuplelike T, tuplelike... Ts>
202-
constexpr auto all_of(F &&f, T &&t, Ts &&...ts) -> bool {
199+
template <typename F, tuplelike... Ts>
200+
constexpr auto all_of(F &&f, Ts &&...ts) -> bool {
203201
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
204-
return (... and detail::invoke_at<Is>(f, std::forward<T>(t),
205-
std::forward<Ts>(ts)...));
206-
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
202+
return (... and detail::invoke_at<Is>(f, std::forward<Ts>(ts)...));
203+
}(std::make_index_sequence<detail::zip_length_for<Ts...>>{});
207204
}
208205

209-
template <typename F, tuplelike T, tuplelike... Ts>
210-
constexpr auto any_of(F &&f, T &&t, Ts &&...ts) -> bool {
206+
template <typename F, tuplelike... Ts>
207+
constexpr auto any_of(F &&f, Ts &&...ts) -> bool {
211208
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
212-
return (... or detail::invoke_at<Is>(f, std::forward<T>(t),
213-
std::forward<Ts>(ts)...));
214-
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
209+
return (... or detail::invoke_at<Is>(f, std::forward<Ts>(ts)...));
210+
}(std::make_index_sequence<detail::zip_length_for<Ts...>>{});
215211
}
216212

217213
template <typename... Ts> constexpr auto none_of(Ts &&...ts) -> bool {

test/tuple_algorithms.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ TEST_CASE("transform preserves references", "[tuple_algorithms]") {
5656
CHECK(value == 2);
5757
}
5858

59+
TEST_CASE("transform stops at smallest tuple length", "[tuple_algorithms]") {
60+
static_assert(stdx::transform(std::plus{}, stdx::tuple{1, 2, 3},
61+
stdx::tuple{1, 2}) == stdx::tuple{2, 4});
62+
}
63+
5964
namespace {
6065
template <typename Key, typename Value> struct map_entry {
6166
using key_t = Key;
@@ -207,6 +212,13 @@ TEST_CASE("for_each", "[tuple_algorithms]") {
207212
}
208213
}
209214

215+
TEST_CASE("for_each stops at smallest tuple length", "[tuple_algorithms]") {
216+
auto sum = 0;
217+
stdx::for_each([&](auto x, auto y) { sum += x + y; }, stdx::tuple{1, 2, 3},
218+
stdx::tuple{1, 2});
219+
CHECK(sum == 6);
220+
}
221+
210222
TEST_CASE("unrolled_for_each on arrays", "[tuple_algorithms]") {
211223
auto a = std::array{1, 2, 3};
212224
auto sum = 0;
@@ -419,20 +431,29 @@ TEST_CASE("all_of", "[tuple_algorithms]") {
419431
static_assert(stdx::all_of([](auto n) { return n > 0; }, t));
420432
static_assert(
421433
stdx::all_of([](auto x, auto y) { return (x + y) % 2 == 0; }, t, t));
434+
435+
static_assert(stdx::all_of([](auto x, auto y) { return (x + y) % 2 == 0; },
436+
stdx::tuple{1, 3, 5}, stdx::tuple{1, 3}));
422437
}
423438

424439
TEST_CASE("any_of", "[tuple_algorithms]") {
425440
constexpr auto t = stdx::tuple{1, 2, 3};
426441
static_assert(stdx::any_of([](auto n) { return n % 2 == 0; }, t));
427442
static_assert(
428443
stdx::any_of([](auto x, auto y) { return (x + y) % 2 == 0; }, t, t));
444+
445+
static_assert(stdx::any_of([](auto x, auto y) { return (x + y) % 2 == 0; },
446+
stdx::tuple{1, 3, 5}, stdx::tuple{1, 3}));
429447
}
430448

431449
TEST_CASE("none_of", "[tuple_algorithms]") {
432450
constexpr auto t = stdx::tuple{1, 3, 5};
433451
static_assert(stdx::none_of([](auto n) { return n % 2 == 0; }, t));
434452
static_assert(
435453
stdx::none_of([](auto x, auto y) { return (x + y) % 2 != 0; }, t, t));
454+
455+
static_assert(stdx::none_of([](auto x, auto y) { return (x + y) % 2 != 0; },
456+
stdx::tuple{1, 3, 5}, stdx::tuple{1, 3}));
436457
}
437458

438459
TEST_CASE("contains_type", "[tuple_algorithms]") {

0 commit comments

Comments
 (0)