Skip to content

Commit 45bb83c

Browse files
authored
Merge pull request #344 from elbeno/apply-array
🎨 Make `apply` work with types that implement the tuple protocol
2 parents 279e430 + a8c1ac2 commit 45bb83c

5 files changed

Lines changed: 135 additions & 24 deletions

File tree

include/stdx/tuple.hpp

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <stdx/type_traits.hpp>
34
#include <stdx/udls.hpp>
45
#include <stdx/utility.hpp>
56

@@ -335,18 +336,6 @@ struct tuple_impl<std::index_sequence<Is...>, index_function_list<Fs...>, Ts...>
335336
constexpr static auto size =
336337
std::integral_constant<std::size_t, sizeof...(Ts)>{};
337338

338-
[[nodiscard]] constexpr static auto fill_inner_indices(index_pair *p)
339-
-> index_pair * {
340-
((p++->inner = Is), ...);
341-
return p;
342-
}
343-
[[nodiscard]] constexpr static auto
344-
fill_outer_indices(index_pair *p, [[maybe_unused]] std::size_t n)
345-
-> index_pair * {
346-
((p++->outer = (static_cast<void>(Is), n)), ...);
347-
return p;
348-
}
349-
350339
private:
351340
template <typename Funcs, typename... Us>
352341
requires(... and std::equality_comparable_with<Ts, Us>)
@@ -407,18 +396,56 @@ tuple_impl(Ts...)
407396
template <typename T> constexpr auto tuple_size_v = T::size();
408397
template <typename T, std::size_t N>
409398
constexpr auto tuple_size_v<std::array<T, N>> = N;
399+
template <typename T, typename U>
400+
constexpr auto tuple_size_v<std::pair<T, U>> = std::size_t{2};
410401
template <typename T, T N>
411402
constexpr auto tuple_size_v<make_integer_sequence<T, N>> = std::size_t{N};
412403

413-
template <std::size_t I, typename T>
414-
using tuple_element_t = typename T::template element_t<I>;
415-
416404
template <typename T>
417405
concept tuple_comparable = requires { typename T::common_tuple_comparable; };
418406

419407
template <typename T>
420408
concept tuplelike = requires { typename remove_cvref_t<T>::is_tuple; };
421409

410+
template <std::size_t I, typename T> struct tuple_element;
411+
412+
template <std::size_t I, tuplelike T> struct tuple_element<I, T> {
413+
using type = typename T::template element_t<I>;
414+
};
415+
416+
template <std::size_t I, typename T, std::size_t N>
417+
struct tuple_element<I, std::array<T, N>> {
418+
using type = T;
419+
};
420+
421+
template <std::size_t I, typename T, typename U>
422+
struct tuple_element<I, std::pair<T, U>> {
423+
using type = nth_t<I, T, U>;
424+
};
425+
426+
template <std::size_t I, typename T>
427+
using tuple_element_t = typename tuple_element<I, T>::type;
428+
429+
namespace detail {
430+
template <typename T>
431+
concept has_vacuous_tuple_protocol = requires {
432+
{
433+
tuple_size_v<std::remove_cvref_t<T>>
434+
} -> std::same_as<std::size_t const &>;
435+
};
436+
template <typename T>
437+
concept is_vacuous_tuple = tuple_size_v<std::remove_cvref_t<T>> == 0;
438+
} // namespace detail
439+
440+
template <typename T>
441+
concept has_tuple_protocol =
442+
detail::has_vacuous_tuple_protocol<T> and
443+
(detail::is_vacuous_tuple<T> or requires(T &t) {
444+
{
445+
get<0>(t)
446+
} -> std::same_as<tuple_element_t<0, std::remove_cvref_t<T>> &>;
447+
});
448+
422449
template <typename... Ts>
423450
class tuple : public detail::tuple_impl<std::index_sequence_for<Ts...>,
424451
detail::index_function_list<>, Ts...> {
@@ -524,6 +551,5 @@ class one_of : public detail::tuple_impl<std::index_sequence_for<Ts...>,
524551
}
525552
};
526553
template <typename... Ts> one_of(Ts...) -> one_of<Ts...>;
527-
528554
} // namespace v1
529555
} // namespace stdx

include/stdx/tuple_algorithms.hpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,49 @@
1414

1515
namespace stdx {
1616
inline namespace v1 {
17-
template <typename F, tuplelike... Ts> constexpr auto apply(F &&f, Ts &&...ts) {
17+
namespace detail {
18+
template <has_tuple_protocol T>
19+
[[nodiscard]] constexpr auto fill_inner_indices([[maybe_unused]] index_pair *p)
20+
-> index_pair * {
21+
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
22+
((p++->inner = Is), ...);
23+
return p;
24+
}(std::make_index_sequence<tuple_size_v<T>>{});
25+
}
26+
27+
template <has_tuple_protocol T>
28+
[[nodiscard]] constexpr auto fill_outer_indices([[maybe_unused]] index_pair *p,
29+
[[maybe_unused]] std::size_t n)
30+
-> index_pair * {
31+
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
32+
((p++->outer = (static_cast<void>(Is), n)), ...);
33+
return p;
34+
}(std::make_index_sequence<tuple_size_v<T>>{});
35+
}
36+
} // namespace detail
37+
38+
template <typename F, has_tuple_protocol... Ts>
39+
constexpr auto apply(F &&f, Ts &&...ts) {
1840
constexpr auto total_num_elements =
1941
(std::size_t{} + ... + stdx::tuple_size_v<std::remove_cvref_t<Ts>>);
2042

2143
[[maybe_unused]] constexpr auto element_indices =
2244
[&]() -> std::array<detail::index_pair, total_num_elements> {
2345
std::array<detail::index_pair, total_num_elements> indices{};
2446
[[maybe_unused]] auto p = indices.data();
25-
((p = std::remove_cvref_t<Ts>::fill_inner_indices(p)), ...);
47+
((p = detail::fill_inner_indices<std::remove_cvref_t<Ts>>(p)), ...);
2648
[[maybe_unused]] auto q = indices.data();
2749
[[maybe_unused]] std::size_t n{};
28-
((q = std::remove_cvref_t<Ts>::fill_outer_indices(q, n++)), ...);
50+
((q = detail::fill_outer_indices<std::remove_cvref_t<Ts>>(q, n++)),
51+
...);
2952
return indices;
3053
}();
3154

3255
[[maybe_unused]] auto outer_tuple =
3356
stdx::tuple<Ts &&...>{std::forward<Ts>(ts)...};
3457
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
35-
return std::forward<F>(f)(
36-
std::move(outer_tuple)[index<element_indices[Is].outer>]
37-
[index<element_indices[Is].inner>]...);
58+
return std::forward<F>(f)(get<element_indices[Is].inner>(
59+
get<element_indices[Is].outer>(std::move(outer_tuple)))...);
3860
}(std::make_index_sequence<total_num_elements>{});
3961
}
4062

@@ -51,10 +73,11 @@ template <tuplelike... Ts> [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) {
5173
[&]() -> std::array<detail::index_pair, total_num_elements> {
5274
std::array<detail::index_pair, total_num_elements> indices{};
5375
auto p = indices.data();
54-
((p = std::remove_cvref_t<Ts>::fill_inner_indices(p)), ...);
76+
((p = detail::fill_inner_indices<std::remove_cvref_t<Ts>>(p)), ...);
5577
auto q = indices.data();
5678
std::size_t n{};
57-
((q = std::remove_cvref_t<Ts>::fill_outer_indices(q, n++)), ...);
79+
((q = detail::fill_outer_indices<std::remove_cvref_t<Ts>>(q, n++)),
80+
...);
5881
return indices;
5982
}();
6083

test/indexed_tuple.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ template <typename Key, typename Value> struct map_entry {
1717
template <typename T> using key_for = typename T::key_t;
1818
} // namespace
1919

20+
TEST_CASE("indexed_tuple is tuplelike", "[tuple]") {
21+
auto t = stdx::indexed_tuple{1, 2, 3};
22+
STATIC_CHECK(stdx::tuplelike<decltype(t)>);
23+
}
24+
25+
TEST_CASE("indexed_tuple has tuple protocol", "[tuple]") {
26+
auto t = stdx::indexed_tuple{1, 2, 3};
27+
STATIC_CHECK(stdx::has_tuple_protocol<decltype(t)>);
28+
}
29+
2030
TEST_CASE("make_indexed_tuple", "[indexed_tuple]") {
2131
STATIC_REQUIRE(stdx::make_indexed_tuple<>() == stdx::indexed_tuple{});
2232
STATIC_REQUIRE(stdx::make_indexed_tuple<>(1, 2, 3) ==

test/tuple.cpp

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

10+
#include <array>
1011
#include <compare>
1112
#include <type_traits>
1213
#include <utility>
@@ -44,6 +45,29 @@ TEST_CASE("multi element tuple", "[tuple]") {
4445
STATIC_CHECK(T::size() == 2);
4546
}
4647

48+
TEST_CASE("tuple is tuplelike", "[tuple]") {
49+
auto t = stdx::tuple{1, 2, 3};
50+
STATIC_CHECK(stdx::tuplelike<decltype(t)>);
51+
STATIC_CHECK(stdx::tuplelike<stdx::tuple<>>);
52+
}
53+
54+
TEST_CASE("tuple has tuple protocol", "[tuple]") {
55+
auto t = stdx::tuple{1, 2, 3};
56+
STATIC_CHECK(stdx::has_tuple_protocol<decltype(t)>);
57+
STATIC_CHECK(stdx::has_tuple_protocol<stdx::tuple<>>);
58+
}
59+
60+
TEST_CASE("std::array has tuple protocol", "[tuple_algorithms]") {
61+
auto t = std::array{1, 2, 3};
62+
STATIC_CHECK(stdx::has_tuple_protocol<decltype(t)>);
63+
STATIC_CHECK(stdx::has_tuple_protocol<std::array<int, 0>>);
64+
}
65+
66+
TEST_CASE("std::pair has tuple protocol", "[tuple_algorithms]") {
67+
auto t = std::pair{1, 2};
68+
STATIC_CHECK(stdx::has_tuple_protocol<decltype(t)>);
69+
}
70+
4771
namespace {
4872
template <auto N> struct empty {};
4973
} // namespace

test/tuple_algorithms.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ TEST_CASE("apply preserves argument order", "[tuple_algorithms]") {
116116
CHECK(called == 1);
117117
}
118118

119+
TEST_CASE("apply works on std::array", "[tuple_algorithms]") {
120+
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
121+
std::array{1, 2, 3, 4}) == 10);
122+
}
123+
124+
TEST_CASE("apply works on std::pair", "[tuple_algorithms]") {
125+
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
126+
std::pair{1, 2}) == 3);
127+
}
128+
119129
TEST_CASE("variadic apply", "[tuple_algorithms]") {
120130
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); }) == 0);
121131
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
@@ -161,6 +171,24 @@ TEST_CASE("variadic apply preserves argument order", "[tuple_algorithms]") {
161171
CHECK(called == 1);
162172
}
163173

174+
TEST_CASE("variadic apply works on std::array", "[tuple_algorithms]") {
175+
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
176+
std::array{1, 2, 3, 4},
177+
std::array{1, 2, 3, 4, 5}) == 25);
178+
}
179+
180+
TEST_CASE("variadic apply works on std::pair", "[tuple_algorithms]") {
181+
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
182+
std::pair{1, 2}, std::pair{3, 4}) == 10);
183+
}
184+
185+
TEST_CASE("variadic apply works on heterogeneous arguments",
186+
"[tuple_algorithms]") {
187+
STATIC_REQUIRE(stdx::apply([](auto... xs) { return (0 + ... + xs); },
188+
std::array{1, 2}, std::pair{3, 4},
189+
stdx::tuple{5, 6}) == 21);
190+
}
191+
164192
TEST_CASE("join", "[tuple_algorithms]") {
165193
constexpr auto t = stdx::tuple{1, 2, 3};
166194
STATIC_REQUIRE(t.join(std::plus{}) == 6);

0 commit comments

Comments
 (0)