C++: Get head and tail of a parameter pack

920 views Asked by At

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.

2

There are 2 answers

2
IdeaHat On
#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.

0
Desmond Gold On

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"));