unpacking values of an array as parameters to a variadic function
Asked Answered
I

2

5

I am trying (at compile time) to unpack integers as arguments to a variadic function. The idea would be to have those values packed in an array or in a std::index_sequence (c++14) at compile time. I have tried to use some of the answers from older posts, but I find the example code unreadable for my level.

Here is a simple example with the functionality that I need to implement in a code that I am writing, in this case attempting to use std::make_index_sequence. I do not necessarily need to use the latter. The problem is that the values of the sequence are not unpacked as arguments to the variadic function:

#include <cstdio>
#include <iostream>
#include <utility>

using namespace std;


void print(const int &val){
  cout << val << endl;
}

template<typename ...S> void print(const int &val, const S&... others)
{
  print(val);
  print(others...);
}

template<size_t n> void printNumbers(){
  std::make_index_sequence<n> a;
  print(a);
}


int main(){
  printNumbers<6>();
}

The output from GCC8:

    tet.cc: In instantiation of ‘void printNumbers() [with long unsigned int n = 6]’:
tet.cc:25:19:   required from here
tet.cc:20:8: error: no matching function for call to ‘print(std::make_index_sequence<6>&)’
   print(a);
   ~~~~~^~~
tet.cc:8:6: note: candidate: ‘void print(const int&)’
    void print(const int &val){
     ^~~~~
tet.cc:8:6: note:   no known conversion for argument 1 from ‘std::make_index_sequence<6>’ {aka ‘std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4, 5>’} to ‘const int&’
tet.cc:12:30: note: candidate: ‘template<class ... S> void print(const int&, const S& ...)’
template<typename ...S> void print(const int &val, const S&... others)
                                 ^~~~~
tet.cc:12:30: note:   template argument deduction/substitution failed:
tet.cc:20:9: note:   cannot convert ‘a’ (type ‘std::make_index_sequence<6>’ {aka ‘std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4, 5>’}) to type ‘const int&’
Intinction answered 24/11, 2018 at 9:3 Comment(0)
S
6

std::make_index_sequence<6> is an alias for:

std::integer_sequence<std::size_t, 0, 1, 2, 3, 4, 5>

and this is the type of the expression a that is the argument of the function call print(a). Your print function expects individual values, not an std::integer_sequence.

To make your implementation work, you should first deduce the indices, and only then use them as arguments for print:

template <std::size_t... Is>
void printNumbers(std::index_sequence<Is...>)
{
    print(Is...);
}

template <std::size_t N>
void printNumbers()
{
    printNumbers(std::make_index_sequence<N>{});
}

In you could remove the intermediate print function and just say:

template <std::size_t... Is>
void printNumbers(std::index_sequence<Is...>)
{
    (print(Is), ...);
}

In you can both create an index sequence and deduce its indices within a single function:

template <std::size_t N>
void printNumbers()
{
    [] <std::size_t... Is> (std::index_sequence<Is...>)
    { (print(Is), ...); }(std::make_index_sequence<N>{});
}

DEMO

Socialist answered 24/11, 2018 at 9:12 Comment(0)
C
1

As an addendum to the Piotr Skotnicki's answer, I propose a C++14 way to avoid the recursive print().

Not so elegant as the C++17 solution, based on template folding, but equally permit to avoid the use of recursion (and consider that template recursion is usually strict limited by compilers, so the recursive solution works but not when N exceed the recursion limit).

You have to write the printNumber() function as usual, passing a std::make_index_sequence<N> (that inherit from std::index_sequence<0, 1, ...., N-1> aka std::integer_sequence<std::size_t, 0, 1, ..., N-1>) to another function

template <std::size_t N>
void printNumbers ()
 { printNumbers2(std::make_index_sequence<N>{}); }

but in the printNumbers2() you can avoid to call the recursive print() and you can call the print() that effectively call std::cout inside the initialization of an unused array

template <std::size_t ... Is>
void printNumbers2 (std::index_sequence<Is...>)
 {
   using unused = int[];

   (void)unused { 0, (print(Is), 0)... };
 } 

You can also avoid both print() functions printing directly in printNumbers2()

void printNumbers2 (std::index_sequence<Is...>)
 {
   using unused = int[];

   (void)unused { 0, (std::cout << val << std::endl, 0)... };
 } 

You can do the same in the C++17/C++20 template folding solutions.

In C++11 this solution doesn't works but only because std::make_integer_sequence and std::index_sequence are introduced from C++11.

If you write a C++11 surrogate for std::make_integer_sequence and std::index_sequence, you can adapt this solution also to C++11.

Crossing answered 24/11, 2018 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.