20

I have this code:

#include <iostream>
using namespace std;

int print(int i)
{
    cout << endl << i;
}

template<typename ...Args>
inline void pass(Args&&...args)
{

}

template<typename ...args>
inline void expand(args&&... a)
{
    print(a) ...; //this doesn't expand
    //pass( print(a)... ); this works
}

int main() {
    expand(1,2,3,4);
    return 0;
}

It throws an error:

 In function 'void expand(args&& ...)':
error: expected ';' before '...' token
  print(a) ...;
           ^
parameter packs not expanded with '...':
  print(a) ...;
              ^

Why is the use of the pass() function necessary?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
q126y
  • 1,589
  • 4
  • 18
  • 50
  • 3
    Parameter pack expansion is allowed basically where a comma-separated list is allowed. There is a fixed number of contexts: function parameter lists, template parameter lists, initializers etc. An expression is not a valid context. A comma operator doesn't make a comma-separated list. – n. m. could be an AI Nov 23 '15 at 10:27

5 Answers5

26

Essentially, expanding a parameter pack E... produces a list E1, E2, [...], EN, one E for each element in the pack. This syntactic construct is only valid in places where lists are grammatically correct, such as in function calls, initializer lists etc. An expression containing multiple comma operators does not count.

I believe that with fold expressions (N4295: Folding expressions (Andrew Sutton, Richard Smith)) you'll be able to simply write:

(print(a), ...);

In this expression,

  • print(a) is an expression with an unexpanded parameter pack,
  • , is the operator and
  • ... designates the right fold expansion.

The result of the entire expression is that (print(a), ...) will be transformed into

print(a1) , (print(a2), (print(a3), print(a4))) // (assuming four elements). 
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
7

Pack expansions can only happen in pack expansion contexts. These essentially are :

  • braced initialization
  • initializer lists
  • aggregate initializations
  • function calls
  • array initializations

Of these the easier to use in your case would be the last :

#include <iostream>
using namespace std;
int print(int i)
{
    cout<<endl<<i;
    return 0;
}

template<typename ...args>
inline void expand(args&&... a)
{
    using expander = int[]; 
    (void)expander{0, ((void)print(a), 0)...}; 
}

int main() 
{
    expand(1,2,3,4);

    return 0;
}

Demo

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
4

This one works too:

#include <iostream>

void print() {}

template<typename T, typename ... Types>
void print (T firstArg, Types ... args) {
    std::cout << firstArg << "\n";
    print(args...);
}

int main() {
    print("Hello",1337,42.44,"World");
}

Demo

Janek_Kozicki
  • 736
  • 7
  • 9
2

You may want to consider the following C++ 11 code:

#include <iostream>

template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
    std::cout << value << std::endl;
    (void) std::initializer_list<T>{([&args] {
        std::cout << args << std::endl;
    }(), value)...};
}
Michael Haephrati
  • 3,660
  • 1
  • 33
  • 56
alonegame
  • 49
  • 4
1

with C++17 and fold expressions, the above code could be simplified as shown below:

#include <iostream>

template<typename ...args>
inline void print(args&&... a) {
    ((std::cout << a << std::endl), ...);
}

int main() {
    print(1,2,3,4);
    return 0;
}
Mital Vora
  • 2,199
  • 16
  • 19