Skip to content

Commit 4f3d1cb

Browse files
authored
Merge pull request #154 from elbeno/variadic-apply
✨ Add variadic `apply` for `tuple`
2 parents 4c2c92e + b5dd5da commit 4f3d1cb

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

docs/tuple_algorithms.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ auto t = stdx::tuple{1, 2, 3};
4848
auto sum = stdx::apply([] (auto... args) { return (args + ... + 0); }, t); // 6
4949
----
5050

51+
`stdx::apply` can also be called with a variadic pack of tuples, which are
52+
unpacked and passed to the function:
53+
[source,cpp]
54+
----
55+
auto t1 = stdx::tuple{1, 2, 3};
56+
auto t2 = stdx::tuple{4, 5, 6};
57+
auto sum = stdx::apply([] (auto... args) { return (args + ... + 0); }, t1, t2); // 21
58+
----
59+
5160
=== `cartesian_product`
5261

5362
`cartesian_product` takes any number of tuples and returns the tuple-of-tuples

include/stdx/tuple_algorithms.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@
1616

1717
namespace stdx {
1818
inline namespace v1 {
19+
template <typename F, tuplelike... Ts> constexpr auto apply(F &&f, Ts &&...ts) {
20+
constexpr auto total_num_elements =
21+
(std::size_t{} + ... + stdx::tuple_size_v<std::remove_cvref_t<Ts>>);
22+
23+
[[maybe_unused]] constexpr auto element_indices = [&] {
24+
std::array<detail::index_pair, total_num_elements> indices{};
25+
[[maybe_unused]] auto p = indices.data();
26+
((p = std::remove_cvref_t<Ts>::fill_inner_indices(p)), ...);
27+
[[maybe_unused]] auto q = indices.data();
28+
[[maybe_unused]] std::size_t n{};
29+
((q = std::remove_cvref_t<Ts>::fill_outer_indices(q, n++)), ...);
30+
return indices;
31+
}();
32+
33+
[[maybe_unused]] auto outer_tuple =
34+
stdx::tuple<Ts &&...>{std::forward<Ts>(ts)...};
35+
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
36+
return std::forward<F>(f)(
37+
std::move(outer_tuple)[index<element_indices[Is].outer>]
38+
[index<element_indices[Is].inner>]...);
39+
}(std::make_index_sequence<total_num_elements>{});
40+
}
41+
1942
template <tuplelike... Ts> [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) {
2043
if constexpr (sizeof...(Ts) == 0) {
2144
return stdx::tuple<>{};

test/tuple_algorithms.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <catch2/catch_template_test_macros.hpp>
66
#include <catch2/catch_test_macros.hpp>
77

8+
#include <algorithm>
89
#include <array>
910
#include <functional>
1011
#include <type_traits>
@@ -77,19 +78,82 @@ TEST_CASE("apply", "[tuple_algorithms]") {
7778
stdx::tuple{}) == 0);
7879
static_assert(stdx::apply([](auto... xs) { return (0 + ... + xs); },
7980
stdx::tuple{1, 2, 3}) == 6);
81+
}
8082

83+
TEST_CASE("apply handles a stateful function properly", "[tuple_algorithms]") {
8184
auto stateful = [calls = 0](auto...) mutable { return ++calls; };
8285
CHECK(stdx::apply(stateful, stdx::tuple{1, 2, 3}) == 1);
8386
CHECK(stdx::apply(stateful, stdx::tuple{1, 2, 3}) == 2);
87+
}
8488

89+
TEST_CASE("apply handles move-only types", "[tuple_algorithms]") {
8590
static_assert(stdx::apply([](auto x) { return x.value; },
8691
stdx::tuple{move_only{42}}) == 42);
92+
}
8793

94+
TEST_CASE("apply handles tuples of references", "[tuple_algorithms]") {
8895
auto t = stdx::tuple{1, 2, 3};
8996
stdx::apply([](auto &...xs) { (++xs, ...); }, t);
9097
CHECK(t == stdx::tuple{2, 3, 4});
9198
}
9299

100+
TEST_CASE("apply preserves argument order", "[tuple_algorithms]") {
101+
int called{};
102+
stdx::apply(
103+
[&](auto... xs) {
104+
++called;
105+
auto const a = std::array{xs...};
106+
CHECK(std::is_sorted(std::begin(a), std::end(a)));
107+
},
108+
stdx::tuple{1, 2, 3});
109+
CHECK(called == 1);
110+
}
111+
112+
TEST_CASE("variadic apply", "[tuple_algorithms]") {
113+
static_assert(stdx::apply([](auto... xs) { return (0 + ... + xs); }) == 0);
114+
static_assert(stdx::apply([](auto... xs) { return (0 + ... + xs); },
115+
stdx::tuple{1, 2, 3},
116+
stdx::tuple{4, 5, 6}) == 21);
117+
}
118+
119+
TEST_CASE("variadic apply handles a stateful function properly",
120+
"[tuple_algorithms]") {
121+
auto stateful = [calls = 0](auto...) mutable { return ++calls; };
122+
CHECK(stdx::apply(stateful) == 1);
123+
CHECK(stdx::apply(stateful) == 2);
124+
}
125+
126+
TEST_CASE("variadic apply handles move-only types", "[tuple_algorithms]") {
127+
static_assert(stdx::apply([](auto x, auto y) { return x.value + y.value; },
128+
stdx::tuple{move_only{42}},
129+
stdx::tuple{move_only{17}}) == 59);
130+
}
131+
132+
TEST_CASE("variadic apply handles tuples of references", "[tuple_algorithms]") {
133+
auto t1 = stdx::tuple{1};
134+
auto t2 = stdx::tuple{2};
135+
stdx::apply(
136+
[](auto &x1, auto &x2) {
137+
x1 += x2;
138+
x2 *= 2;
139+
},
140+
t1, t2);
141+
CHECK(t1 == stdx::tuple{3});
142+
CHECK(t2 == stdx::tuple{4});
143+
}
144+
145+
TEST_CASE("variadic apply preserves argument order", "[tuple_algorithms]") {
146+
int called{};
147+
stdx::apply(
148+
[&](auto... xs) {
149+
++called;
150+
auto const a = std::array{xs...};
151+
CHECK(std::is_sorted(std::begin(a), std::end(a)));
152+
},
153+
stdx::tuple{1, 2, 3}, stdx::tuple{4, 5, 6});
154+
CHECK(called == 1);
155+
}
156+
93157
TEST_CASE("join", "[tuple_algorithms]") {
94158
constexpr auto t = stdx::tuple{1, 2, 3};
95159
static_assert(t.join(std::plus{}) == 6);

0 commit comments

Comments
 (0)