Reputation: 51
How to obtain the first n elements of a parameter pack? Or the last n elements, or a slice of elements in [n, n+1, ..., m) in general? For instance:
head<3>(1, 2.0f, "three", '4') => make_tuple(1, 2.0f, "three")
tail<2>(1, 2.0f, "three", '4') => make_tuple("three", '4')
slice<1,3>(1, 2.0f, "three", '4') => make_tuple(2.0, "three")
This is doable with a combination of std::tuple, std::integer_sequence and std::get but I was wondering whether there are simpler ways.
Upvotes: 5
Views: 1077
Reputation: 7881
#include <tuple>
using namespace std;
template <size_t offset, typename Tuple, size_t... N>
auto GetRange(Tuple&& t, std::index_sequence<N...>) {
return std::make_tuple(std::get<offset+N>(std::forward<Tuple>(t))...);
}
template <size_t N, typename... T>
auto Head(T&&... t) {
return GetRange<0>(
std::make_tuple(std::forward<T>(t)...),
std::make_index_sequence<N>{});
}
template <size_t N, typename... T>
auto Tail(T&&... t) {
return GetRange<N>(
std::make_tuple(std::forward<T>(t)...),
std::make_index_sequence<
sizeof...(T)-N
>{});
}
template <int N>
struct PrintTupleElementHelper {
template <typename Tup>
static void Do(const Tup& t) {
PrintTupleElementHelper<N-1>::Do(t);
cout << std::get<N>(t) << ',';
}
};
template <>
struct PrintTupleElementHelper<0> {
template <typename Tup>
static void Do(const Tup& t) {
cout << std::get<0>(t) << ',';
}
};
template <typename Tup>
void PrintTupleElement(const Tup& t) {
PrintTupleElementHelper<std::tuple_size<Tup>{}-1>::Do(t);
cout << endl;
}
int main() {
// your code goes here
PrintTupleElement(Head<3>("Foo", 10, 'x', "Baz"));
PrintTupleElement(Tail<3>("Foo", 10, 'x', "Baz"));
return 0;
}
First is rather trivial, last is a little more tricky given unpacking rules. You can use rhr to make it more efficient.
Upvotes: 1
Reputation: 2075
There is a way by making a new factory alias for std::integer_sequence
that will act as a building block by using the combination of std::tuple
, std::integer_sequence
, std::get
.
Let's say the name of this alias is make_consecutive_integer_sequence
:
namespace detail {
template <typename T, auto Start, auto Step, T... Is>
constexpr auto make_cons_helper_impl_(std::integer_sequence<T, Is...>) {
auto eval_ = [](const T& I) consteval -> T { return Start + Step * I; };
return std::integer_sequence<T, eval_(Is)...>{};
}
template <typename T, auto Start, auto Count, auto Step>
constexpr auto make_cons_impl_() {
return make_cons_helper_impl_<T, Start, Step>(std::make_integer_sequence<T, Count>{});
}
} // namespace detail
template <std::integral T, auto Start, auto Count, auto Step = 1>
using make_consecutive_integer_sequence = decltype(
detail::make_cons_impl_<T, Start, Count, Step>()
);
template <auto Start, auto Count, auto Step = 1>
using make_consecutive_index_sequence = make_consecutive_integer_sequence<std::size_t, Start, Count, Step>;
And then, apply the usage of make_consecutive_integer_sequence
:
template <std::size_t N>
using make_first_n_index_sequence = make_consecutive_index_sequence<0, N>;
template <std::size_t N, std::size_t S>
using make_last_n_index_sequence = make_consecutive_index_sequence<S - N, N>;
template <std::size_t B, std::size_t E>
using make_slice_index_sequence = make_consecutive_index_sequence<B, E - B>;
We do still need to wrap the parameter pack into std::tuple
.
template <typename... Ts, std::size_t... Is>
constexpr auto get_subpack_by_seq(std::index_sequence<Is...>, Ts&&... args) {
return std::make_tuple(std::get<Is>(std::forward_as_tuple(args...))...);
}
template <std::size_t N, typename... Ts>
requires (N <= sizeof...(Ts))
constexpr auto head(Ts&&... args) {
return get_subpack_by_seq(
make_first_n_index_sequence<N>{},
std::forward<Ts>(args)...
);
}
template <std::size_t N, typename... Ts>
requires (N <= sizeof...(Ts))
constexpr auto tail(Ts&&... args) {
return get_subpack_by_seq(
make_last_n_index_sequence<N, sizeof...(Ts)>{},
std::forward<Ts>(args)...
);
}
template <std::size_t B, std::size_t E, typename... Ts>
requires (B < E && B <= sizeof...(Ts) && E <= sizeof...(Ts))
constexpr auto slice(Ts&&... args) {
return get_subpack_by_seq(
make_slice_index_sequence<B, E>{},
std::forward<Ts>(args)...
);
}
The goal here is to generalize the make_consecutive_integer_sequence
(make_consecutive_index_sequence
for std::size_t
) so that we could write a neat implementation for head
, tail
, and slice
in a consistent way.
Compile-time assertions:
static_assert(head<3>(1, 2.0f, "three", '4') == std::make_tuple(1, 2.0f, "three"));
static_assert(tail<2>(1, 2.0f, "three", '4') == std::make_tuple("three", '4'));
static_assert(slice<1, 3>(1, 2.0f, "three", '4') == std::make_tuple(2.0f, "three"));
Upvotes: 0