Skip to content

Commit d1c3828

Browse files
committed
⚡ Add gather_by for tuples
This replaces `sort | chunk_by` as used by CIB. This improves the CIB compilation benchmark: `gather_by` is roughly 1.4x faster than `sort | chunk_by`.
1 parent a5fd19f commit d1c3828

File tree

2 files changed

+120
-20
lines changed

2 files changed

+120
-20
lines changed

include/stdx/tuple_algorithms.hpp

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ constexpr auto contains_type(
173173
stdx::detail::tuple_impl<IndexSeq, index_function_list<Fs...>, Us...> const
174174
&) -> std::bool_constant<(is_index_for<T, Fs, Us...> or ...) or
175175
(std::is_same_v<T, Us> or ...)>;
176+
177+
template <tuplelike T, template <typename> typename Proj = std::type_identity_t>
178+
[[nodiscard]] constexpr auto sorted_indices() {
179+
return []<std::size_t... Is>(std::index_sequence<Is...>)
180+
-> std::array<std::size_t, sizeof...(Is)> {
181+
using P = std::pair<std::string_view, std::size_t>;
182+
auto a = std::array<P, sizeof...(Is)>{
183+
P{stdx::type_as_string<Proj<tuple_element_t<Is, T>>>(), Is}...};
184+
std::sort(a.begin(), a.end(), [](auto const &p1, auto const &p2) {
185+
return p1.first < p2.first;
186+
});
187+
return {a[Is].second...};
188+
}
189+
(std::make_index_sequence<T::size()>{});
190+
}
176191
} // namespace detail
177192

178193
template <tuplelike Tuple, typename T>
@@ -183,36 +198,29 @@ template <template <typename> typename Proj = std::type_identity_t,
183198
tuplelike Tuple>
184199
[[nodiscard]] constexpr auto sort(Tuple &&t) {
185200
using T = stdx::remove_cvref_t<Tuple>;
186-
using P = std::pair<std::string_view, std::size_t>;
187-
constexpr auto indices = []<std::size_t... Is>(std::index_sequence<Is...>) {
188-
auto a = std::array<P, sizeof...(Is)>{
189-
P{stdx::type_as_string<Proj<tuple_element_t<Is, T>>>(), Is}...};
190-
std::sort(a.begin(), a.end(), [](auto const &p1, auto const &p2) {
191-
return p1.first < p2.first;
192-
});
193-
return a;
194-
}(std::make_index_sequence<T::size()>{});
195-
201+
constexpr auto indices = detail::sorted_indices<T, Proj>();
196202
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
197-
return stdx::tuple<tuple_element_t<indices[Is].second, T>...>{
198-
std::forward<Tuple>(t)[index<indices[Is].second>]...};
203+
return stdx::tuple<tuple_element_t<indices[Is], T>...>{
204+
std::forward<Tuple>(t)[index<indices[Is]>]...};
199205
}(std::make_index_sequence<T::size()>{});
200206
}
201207

202208
namespace detail {
203-
template <tuplelike T, template <typename> typename Proj, std::size_t I>
204-
[[nodiscard]] constexpr auto test_adjacent() -> bool {
205-
return std::is_same_v<Proj<stdx::tuple_element_t<I, T>>,
206-
Proj<stdx::tuple_element_t<I + 1, T>>>;
207-
}
209+
210+
template <tuplelike T, template <typename> typename Proj> struct test_pair_t {
211+
template <std::size_t I, std::size_t J>
212+
constexpr static auto value =
213+
std::is_same_v<Proj<stdx::tuple_element_t<I, T>>,
214+
Proj<stdx::tuple_element_t<J, T>>>;
215+
};
208216

209217
template <tuplelike T, template <typename> typename Proj = std::type_identity_t>
210218
requires(tuple_size_v<T> > 1)
211219
[[nodiscard]] constexpr auto count_chunks() {
212220
auto count = std::size_t{1};
213221
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
214-
((count +=
215-
static_cast<std::size_t>(not detail::test_adjacent<T, Proj, Is>())),
222+
((count += static_cast<std::size_t>(
223+
not test_pair_t<T, Proj>::template value<Is, Is + 1>)),
216224
...);
217225
}(std::make_index_sequence<stdx::tuple_size_v<T> - 1>{});
218226
return count;
@@ -232,7 +240,7 @@ template <tuplelike T, template <typename> typename Proj = std::type_identity_t>
232240
std::array<chunk, count_chunks<T, Proj>()> chunks{};
233241
++chunks[index].size;
234242
auto check_next_chunk = [&]<std::size_t I>() {
235-
if (not detail::test_adjacent<T, Proj, I>()) {
243+
if (not test_pair_t<T, Proj>::template value<I, I + 1>) {
236244
chunks[++index].offset = I + 1;
237245
}
238246
++chunks[index].size;
@@ -333,6 +341,55 @@ template <tuplelike Tuple> constexpr auto to_unsorted_set(Tuple &&t) {
333341
std::forward<Tuple>(t))...};
334342
}(std::make_index_sequence<U::size()>{});
335343
}
344+
345+
template <template <typename> typename Proj = std::type_identity_t,
346+
tuplelike Tuple>
347+
[[nodiscard]] constexpr auto gather_by(Tuple &&t) {
348+
using tuple_t = std::remove_cvref_t<Tuple>;
349+
if constexpr (tuple_size_v<tuple_t> == 0) {
350+
return stdx::tuple{};
351+
} else if constexpr (tuple_size_v<tuple_t> == 1) {
352+
return stdx::make_tuple(std::forward<Tuple>(t));
353+
} else {
354+
constexpr auto sorted_idxs = detail::sorted_indices<tuple_t, Proj>();
355+
constexpr auto tests =
356+
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
357+
return std::array<bool, stdx::tuple_size_v<tuple_t> - 1>{
358+
detail::test_pair_t<tuple_t, Proj>::template value<
359+
sorted_idxs[Is], sorted_idxs[Is + 1]>...};
360+
}(std::make_index_sequence<stdx::tuple_size_v<tuple_t> - 1>{});
361+
362+
constexpr auto chunks = [&] {
363+
constexpr auto chunk_count =
364+
std::count(std::begin(tests), std::end(tests), false) + 1;
365+
std::array<detail::chunk, chunk_count> cs{};
366+
367+
auto index = std::size_t{};
368+
++cs[index].size;
369+
for (auto i = std::size_t{}; i < std::size(tests); ++i) {
370+
if (not tests[i]) {
371+
cs[++index].offset = i + 1;
372+
}
373+
++cs[index].size;
374+
}
375+
return cs;
376+
}();
377+
378+
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
379+
return stdx::make_tuple([&]<std::size_t... Js>(
380+
std::index_sequence<Js...>) {
381+
constexpr auto offset = chunks[Is].offset;
382+
return stdx::tuple<
383+
tuple_element_t<sorted_idxs[offset + Js], tuple_t>...>{
384+
std::forward<Tuple>(t)[index<sorted_idxs[offset + Js]>]...};
385+
}(std::make_index_sequence<chunks[Is].size>{})...);
386+
}(std::make_index_sequence<chunks.size()>{});
387+
}
388+
}
389+
390+
template <tuplelike Tuple> [[nodiscard]] constexpr auto gather(Tuple &&t) {
391+
return gather_by(std::forward<Tuple>(t));
392+
}
336393
} // namespace v1
337394
} // namespace stdx
338395

test/tuple_algorithms.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,3 +649,46 @@ TEST_CASE("unrolled enumerate on arrays", "[tuple_algorithms]") {
649649
a, a);
650650
CHECK(sum == (0 + 1 + 2) + (2 + 4 + 6));
651651
}
652+
653+
TEST_CASE("gather (empty tuple)", "[tuple_algorithms]") {
654+
constexpr auto t = stdx::tuple{};
655+
[[maybe_unused]] constexpr auto gathered = stdx::gather(t);
656+
static_assert(std::is_same_v<decltype(gathered), stdx::tuple<> const>);
657+
}
658+
659+
TEST_CASE("gather (1-element tuple)", "[tuple_algorithms]") {
660+
constexpr auto t = stdx::tuple{1};
661+
constexpr auto gathered = stdx::gather(t);
662+
static_assert(std::is_same_v<decltype(gathered),
663+
stdx::tuple<stdx::tuple<int>> const>);
664+
CHECK(gathered == stdx::make_tuple(stdx::tuple{1}));
665+
}
666+
667+
TEST_CASE("gather (general case)", "[tuple_algorithms]") {
668+
constexpr auto t = stdx::tuple{1, 1.0, 2, 2.0, 3, true};
669+
constexpr auto gathered = stdx::gather(t);
670+
static_assert(std::is_same_v<
671+
decltype(gathered),
672+
stdx::tuple<stdx::tuple<bool>, stdx::tuple<double, double>,
673+
stdx::tuple<int, int, int>> const>);
674+
CHECK(gathered == stdx::tuple{stdx::tuple{true}, stdx::tuple{1.0, 2.0},
675+
stdx::tuple{1, 2, 3}});
676+
}
677+
678+
TEST_CASE("gather preserves references", "[tuple_algorithms]") {
679+
int x{1};
680+
int y{2};
681+
auto t = stdx::tuple<int &, int &>{x, y};
682+
auto gathered = stdx::gather(t);
683+
static_assert(std::is_same_v<decltype(gathered),
684+
stdx::tuple<stdx::tuple<int &, int &>>>);
685+
CHECK(get<0>(gathered) == stdx::tuple{1, 2});
686+
}
687+
688+
TEST_CASE("gather with move only types", "[tuple_algorithms]") {
689+
auto t = stdx::tuple{move_only{1}};
690+
auto gathered = stdx::gather(std::move(t));
691+
static_assert(std::is_same_v<decltype(gathered),
692+
stdx::tuple<stdx::tuple<move_only>>>);
693+
CHECK(get<0>(gathered) == stdx::tuple{move_only{1}});
694+
}

0 commit comments

Comments
 (0)