1

Possible duplicate: Is it possible to "store" a template parameter pack without expanding it?

Similar to the question above, I'd like to explore this more and store a variadic array.

template<size_t N, typename... Args>
void foo(Args(&...args)[N]) {
  Args[N]... args2; // compilation error
}

Is this possible to be accomplished?

The end goal is to be able to call foo(), mutate a copy of its variadic array inputs, and execute some function on the mutations. So, something like:

template<typename F, size_t N, typename... Args>
void applyAsDoubled(F f, Args(&...args)[N]) {
  Args[N]... args2;
  doublerMutation(args2...); // doubles each argument; external function, assume it cannot avoid having a side-effect on its parameters

  for (int i = 0; i < N; i++)
    f(args2[i]...);
}

will be call and suffer no side-effects:

int A[N] = { 1, 2, 3, 4, 5 };
int B[N] = { 2, 2, 2, 2, 2 };

applyAsDoubled(printAdded, A, B);

will print 6, 8, 10, 12, 14 where A and B are not mutated. Just to clarify, the function doublerMutation() is a dummy function to represent a function that will cause mutations for arguments and cannot be rewritten.

max66
  • 65,235
  • 10
  • 71
  • 111
Michael Choi
  • 610
  • 5
  • 22

1 Answers1

1

I propose you a C++14 solution that should be adapted for C++11 with substitutes for std::index_sequence and std::make_index_sequence.

I suggest, for applyAsDoubled(), to simply call an helper function passing also a std::index_sequence over the number of arrays

template <typename F, std::size_t N, typename ... As>
void applyAsDoubled (F f, As(&...as)[N])
 { applyAsDoubledH(f, std::make_index_sequence<sizeof...(As)>{}, as...); }

The helper function is heavily based over std::tuple, to pack the copies of arrays, and new std::array, for the C-style arrays copies

void applyAsDoubledH (F f, std::index_sequence<Is...> const & is,
                      Args(&...args)[N])
 {
   auto isn { std::make_index_sequence<N>{} };

   std::tuple<std::array<Args, N>...> tpl { getStdArray(args, isn)... };

   doublerMutation(tpl, is);

   for (auto ui { 0u } ; ui < N ; ++ui )
      f(std::get<Is>(tpl)[ui]...);
 }

Observe the calls to getStdArray()

template <typename T, std::size_t N, std::size_t ... Is>
std::array<T, N> getStdArray (T(&a)[N], std::index_sequence<Is...> const &)
 { return { { a[Is]... } }; }

to get the singles std::array form singles C-style array.

The doublerMutation() also uses a helper function

template <std::size_t I, std::size_t N, typename ... Args>
void doublerMutationH (std::tuple<std::array<Args, N>...> & tpl)
 {
   for ( auto ui { 0u } ; ui < N ; ++ui )
      std::get<I>(tpl)[ui] *= 2;
 }

template <std::size_t N, typename ... Args, std::size_t ... Is>
void doublerMutation (std::tuple<std::array<Args, N>...> & tpl,
                      std::index_sequence<Is...> const &)
 {
   using unused = int[];

   (void) unused { 0, (doublerMutationH<Is>(tpl), 0)... };
 }

The following is a full working example

#include <tuple>
#include <array>
#include <iostream>
#include <type_traits>

template <std::size_t I, std::size_t N, typename ... Args>
void doublerMutationH (std::tuple<std::array<Args, N>...> & tpl)
 {
   for ( auto ui { 0u } ; ui < N ; ++ui )
      std::get<I>(tpl)[ui] *= 2;
 }

template <std::size_t N, typename ... Args, std::size_t ... Is>
void doublerMutation (std::tuple<std::array<Args, N>...> & tpl,
                      std::index_sequence<Is...> const &)
 {
   using unused = int[];

   (void) unused { 0, (doublerMutationH<Is>(tpl), 0)... };
 }

template <typename T, std::size_t N, std::size_t ... Is>
std::array<T, N> getStdArray (T(&a)[N], std::index_sequence<Is...> const &)
 { return { { a[Is]... } }; }

template <typename F, std::size_t ... Is, std::size_t N, typename ... Args>
void applyAsDoubledH (F f, std::index_sequence<Is...> const & is,
                      Args(&...args)[N])
 {
   auto isn { std::make_index_sequence<N>{} };

   std::tuple<std::array<Args, N>...> tpl { getStdArray(args, isn)... };

   doublerMutation(tpl, is);

   for (auto ui { 0u } ; ui < N ; ++ui )
      f(std::get<Is>(tpl)[ui]...);
 }

template <typename F, std::size_t N, typename ... As>
void applyAsDoubled (F f, As(&...as)[N])
 { applyAsDoubledH(f, std::make_index_sequence<sizeof...(As)>{}, as...); }

int main ()
 {
   int  A[] = { 1, 2, 3, 4, 5 };
   long B[] = { 2, 2, 2, 2, 2 };

   auto printSum = [](auto const & ... as)
    {   
      using unused = int[];

      typename std::common_type<decltype(as)...>::type  sum {};

      (void)unused { 0, (sum += as, 0)... };

      std::cout << "the sum is " << sum << std::endl;
    };

   applyAsDoubled(printSum, A, B);
 }

If you can also use C++17, using template folding and the power of comma operator, you can avoid the use of unuseds array and doublerMutation() can be simplified as follows

template <std::size_t N, typename ... Args, std::size_t ... Is>
void doublerMutation (std::tuple<std::array<Args, N>...> & tpl,
                      std::index_sequence<Is...> const &)
 { ( doublerMutationH<Is>(tpl), ... ); }

and the printSum() lambda test function as follows

auto printSum = [](auto const & ... as)
 { std::cout << "the sum is " << (as + ...) << std::endl; };
max66
  • 65,235
  • 10
  • 71
  • 111
  • I know this is against SO, but I really appreciate the amount of work you've put it. I am really stunned. – Michael Choi Sep 01 '18 at 01:52
  • 1
    @MichaelChoi - I like template meta-programming. Added a couple of C++17 simplifications. – max66 Sep 01 '18 at 01:56
  • Just a quick question, in your C++17 example how does the second parameter of doubleMutation not have a name? How do you reference this? – Michael Choi Sep 01 '18 at 01:57
  • 1
    @MichaelChoi - not only in C++17 example, also in C++14 version; that parameter is used only to permit the compiler to deduce the `Is...` variadic list; is't not used itself. In C++ you can not give a name to an unused parameter and this avoid annoying "parameter not used" type warnings. – max66 Sep 01 '18 at 02:01