Home > other >  How to forward a trimmed version of parameter pack?
How to forward a trimmed version of parameter pack?

Time:09-08

Suppose my constructors (A5) takes a parameter pack. I want it to forward the first N elements to one member constructor (B) and the rest to another (C). Both member constructors taking parameter packs as well. Is there a way to do it?

template<int N> struct B;

template<int N> struct C;

template<int N, typename ...Args>
auto get_first(Args&&... args);

template<int N, typename ...Args>
auto get_last(Args&&... args);

template<int N>
struct A5
{
  B<N> b;
  C<5-N> c;

  template<typename ...Args>
  A5(Args&&... args)
    : b(get_first<N>(args)...) // how this might look like?
    , c(get_last<5-N>(args)...)
  {}
};

Problem in my case is, that the number of arguments needed for the constructors of B and C depends on the non-type parameter.

It would be nice to know how to implement something like get_first and get_last. If it's not possible, I'd be interested in why.

Also, suggestions for other possible solutions or workarounds would be appreciated.

This was probably asked before, but I cannot find any related questions.

Thanks in advance!

CodePudding user response:

With reflection we might get a simple way to slice a parameter pack in the future. Until then the implementations I know of are not pretty. A common technique is to use tuples and std::index_sequence:

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

template<int N> struct B { B(int) {} };
template<int N> struct C { C() {} C(int) {} };


template<class ...Args>
int get_first(Args&&... args)
{
    std::cout << "first: ";
    ((std::cout << args << ' '), ...) << std::endl;
    return 0;
}

template<typename ...Args>
int get_last(Args&&... args)
{
    std::cout << "second: ";
    ((std::cout << args << ' '), ...) << std::endl;
    return 0;
}

template<std::size_t... Idx, class ...Args>
int get_first_impl(std::index_sequence<Idx...>, std::tuple<Args...> args)
{
    return get_first(std::get<Idx>(args)...);
}

template<std::size_t Offset, std::size_t... Idx, class ...Args>
int get_last_impl(std::integral_constant<std::size_t, Offset>, std::index_sequence<Idx...>, std::tuple<Args...> args)
{
    return get_last(std::get<Idx   Offset>(args)...);
}

template<int N>
struct A5
{
  B<N> b;
  C<5-N> c;

  template<class ...Args>
  A5(Args&&... args)
    : b{get_first_impl(std::make_index_sequence<N>{},
                       std::tuple{args...})} ,
      c{get_last_impl(std::integral_constant<std::size_t, N>{},
                      std::make_index_sequence<5 - N>{},
                      std::tuple{args...})}
  {}
};

int main()
{
    A5<3> a{1, 2, 3, 4, 5};
}

This will call get_first(1, 2, 3) and get_last(4, 5) respectively:

first: 1 2 3 
second: 4 5 

See it in action on godbolt.

The ideea is: we pack the tuple with the full args and send it along with the indexes we need for each function and then we get for the final args just the elements at the indexes we are interested in.

Here is a step by step expansion of what is happening:

get_first_impl(std::index_sequence<0, 1, 2>{}, std::tuple{1, 2, 3, 4, 5})
\/
get_first(std::get<0>(tp{1, 2, 3, 4, 4}),
          std::get<1>(tp{1, 2, 3, 4, 4}),
          std::get<2>(tp{1, 2, 3, 4, 4}),
\/
get_first(1, 2, 3)
get_last_impl(std::integral_constant<3>{},
              std::index_sequence<0, 1>{}.
              std::tuple{1, 2, 3, 4, 5})
\/
get_second(std::get<0   3>(tp{1, 2, 3, 4, 5},
           std::get<1   3>(tp{1, 2, 3, 4, 5})
\/
get_second(std::get<3>(tp{1, 2, 3, 4, 5},
           std::get<4>(tp{1, 2, 3, 4, 5})
\/
get_second(4, 5)

I think Boost.Hana has a pretty and simple way to do this, but I am not familiar with the library.

CodePudding user response:

You have to have a pack of your wanted sizes (N and 5-N) somewhere.

std::make_index_sequence with a delegating constructor is a good place to create a pack:

template<int N>
struct A5
{
  B<N> b;
  C<5-N> c;

  template<typename ...Args>
  A5(Args&&... args)
    : A5(std::make_index_sequence<N>{}, std::make_index_sequence<5-N>{},
         std::forward_as_tuple(std::forward<Args>(args)...)) {}

private:
  // Deduce the pack `Head` and `Tail` of the sizes specified in the
  // other constructor. Use `std::get` to select the arguments.
  template<typename... Args, std::size_t... Head, std::size_t... Tail, std::size_t Offset = sizeof...(Args)-sizeof...(Tail)>
  A5(std::index_sequence<Head...>, std::index_sequence<Tail...>, std::tuple<Args&&...> args)
    : b(std::get<Head>(args)...)
    , c(std::get<Tail Offset>(args)...) {}
};

Example: https://godbolt.org/z/rcqvf8xo6

You can also consider taking the tuple of arguments (std::forward_as_tuple(std::forward<Args>(args)...)), trimming it (with a similar make_index_sequence technique, like shown here: get part of std::tuple) and then using something like:

: b(std::apply([](auto&&... args) {
    return B<N>(std::forward<decltype(args)>(args)...);
}, tuple_first<N>(args_in_tuple)))
, c(std::apply([](auto&&... args) {
    return C<5-N>(std::forward<decltype(args)>(args)...);
}, tuple_last<5-N>(args_in_tuple)))

Though without guaranteed copy elision in C 17, this might create a copy of the return value.

  • Related