2

lets say I had a function like the following, how could I get the first n elements of a tuple?

template<std::size_t N, class... Ts>
void foo(Ts... ts){
   std::tuple<Ts...> all_elements(ts...);
   auto first_elements = //the first N elements of the tuple all_elements
}

What would be the best way to define the variable first_elements with the first N elements of the all_elements tuple?

Update

This is just going off of Sam Varshavchik's answer for a version compatible with lower versions of C++ such as C++17 as well:

template<typename T, T... ints, class...DTs>
auto reduce_tuple(std::integer_sequence<T, ints...> int_seq, std::tuple<DTs&&...>&& t){
    return std::forward_as_tuple((std::move(std::get<ints>(t)))...);
}

template<std::size_t N, class... Ts>
auto foo(Ts... ts){
   std::tuple<Ts&&...> all_elements(std::forward<Ts>(ts)...);
   return reduce_tuple(std::make_index_sequence<N>{}, std::move(all_elements));  
}

//usage
int main(){
    auto t=foo<2>(3, "a", 0.1);

    static_assert(std::is_same_v<decltype(t), std::tuple<int, const char*>>);

    std::cout << std::get<0>(t) << " " << std::get<1>(t) << "\n";
}

Sam Moldenha
  • 463
  • 2
  • 11
  • Here comes the cavalry consisting of C++20, a template closure that's specialized for a `std::index_sequence`, and invoked with a `std::make_index_sequence`... – Sam Varshavchik Sep 01 '23 at 21:31
  • See https://stackoverflow.com/questions/40112646/get-first-n-elements-of-parameter-pack – Vivick Sep 01 '23 at 21:33
  • @SamVarshavchik do you actually need to have a `std::tuple` or do you just need to call another function with the first `N` arguments of `foo`? – Jan Schultke Sep 01 '23 at 21:45
  • The question seems to be: here's one tuple, make me a shorter one, @JanSchultke. – Sam Varshavchik Sep 01 '23 at 21:46
  • @SamVarshavchik I'm not really sure. It kinda depend on how `first_elements` is used. Chances are it doesn't really need to be a tuple, and the tuple is immediately fed into another function like `std::apply`. – Jan Schultke Sep 01 '23 at 21:47
  • Sure, but that would be a minor variation, once you have the scaffolding for plucking out the sub-tuple, the sky's limit for what comes next. – Sam Varshavchik Sep 01 '23 at 21:49
  • @SamVarshavchik there is a simpler answer which just perfectly forwards. `std::tuple` is a big hammer, and maybe it can be avoided. – Jan Schultke Sep 01 '23 at 21:52
  • Well, if efficiency is a concern then `foo()` should use universal references in the first place, and if the tuple comes from `std::forward_as_tuple`, then one can hope that the compiler will optimize everything away. – Sam Varshavchik Sep 01 '23 at 21:55

1 Answers1

3

Here's a classical C++20 solution: use a template closure, in combination with std::integer_sequence.

Let's improve things a little bit by using universal references and a forwarding tuple in order to minimize the number of copies.

Rather than defining a tuple the following example forwards the first N parameters to another function.

And by sheer coincidence, the function in question is std::make_tuple that ...produces a tuple. But, any function will do.

#include <tuple>
#include <utility>
#include <iostream>
#include <type_traits>

template<std::size_t N, typename ...Ts>
auto foo(Ts && ...ts)
{
    auto all_elements=std::forward_as_tuple(
        std::forward<Ts>(ts)...
    );

    return [&]<std::size_t ...I>(std::index_sequence<I...>)
        {
            return std::make_tuple(std::get<I>(all_elements)...);
        }(
            std::make_index_sequence<N>{}
        );
}

int main()
{
    auto t=foo<2>(3, "a", 0.1);

    static_assert(std::is_same_v<decltype(t), std::tuple<int, const char*>>);

    std::cout << std::get<0>(t) << " " << std::get<1>(t) << "\n";
}

(live demo)

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148