17

Somehow I don't get how variadic template parameter packs are expanded. What's wrong with thie following code?

#include <iostream>

template <typename T>
struct print_one
{
    static void run(const T& t)
    {
        std::cout << t << ' ';
    }
};

template<typename... Args>
void print_all(Args&&... args)
{
    // the next line doesn't compile:
    print_one<Args>::run(std::forward<Args>(args))...;
}

int main()
{
    print_all(1.23, "foo");
}

Clang says, Expression contains unexpanded parameter packs 'Args' and 'args'. Why?

Xeo
  • 129,499
  • 52
  • 291
  • 397
marton78
  • 3,899
  • 2
  • 27
  • 38
  • 3
    Please don't edit the solution into your question, just let it stand in the answer that provided it. Only do this if the answers gave you hints on how to solve this and didn't specify an exact solution. :) – Xeo Sep 20 '12 at 15:33
  • Oh, ok. The only difference was `int dummy[] = {` ... `};` since my compiler doesn't support initializer lists. – marton78 Sep 20 '12 at 15:36
  • Oh, sorry, didn't see that. Maybe edit it into ecatmur's answer? :) (aka change his first example from `int dummy[]{...}` to `int dummy[] = {...}`). – Xeo Sep 20 '12 at 15:38
  • That edit is too short. I guess he has to do it himself. – marton78 Sep 20 '12 at 15:41
  • Edited his answer. Btw, if you're using Clang, I recommend updating to the newest version or maybe even the trunk, it supports initializer lists. – Xeo Sep 20 '12 at 15:43
  • Tnx. I use the Clang in the latest stable Xcode, because the editor of the current beta (developer preview 4) crashes all the time. – marton78 Sep 20 '12 at 15:48
  • I see. The Clang delivered with Xcode is always pretty old, most of the time a full .x revision behind. – Xeo Sep 20 '12 at 15:49

2 Answers2

28

The ... has to go inside the function call parentheses:

print_one<Args>::run(std::forward<Args>(args)...);

Obviously, that won't work for your function that takes only a single argument, so you need to find a way to expand the calls into a function call or other allowed construct:

// constructing a dummy array via uniform initialization
// the extra 0 at the start is to make it work when the pack is empty
int dummy[]{0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, if your compiler doesn't support uniform initialization
int dummy[] = {0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, calling a dummy function
template<typename... Args> void dummy(Args...) {}
dummy((print_one<Args>::run(std::forward<Args>(args)), 0)...);

// or, constructing a temporary dummy object
struct dummy { dummy(std::initializer_list<int>) {} };
dummy{(print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, constructing a temporary initializer list
std::initializer_list<int>{(print_one<Args>::run(std::forward<Args>(args)), 0)...};

Note the use of the comma operator to turn the void return of print_one into a value suitable to place in an argument list or initializer expression.

The initializer-list forms are preferred to the function call forms, as they are (supposed to be) ordered LTR which function call arguments are not.

The forms where a parameter pack expansion can occur are covered by 14.5.3 [temp.variadic]:

4 - [...] Pack expansions can occur in the following contexts:

  • [...]

Your original code is illegal because although textually it might appear that it should produce a statement consisting of a number of comma-operator expressions, that is not a context allowed by 14.5.3:4.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Can you elaborate? You write about an "allowed construct". Why is my code not allowed? – marton78 Sep 20 '12 at 15:18
  • Nice. Especially the trick with `,0`. But **why** doesn't it work the way I did it? – marton78 Sep 20 '12 at 15:26
  • @marton78 standard reference added above. – ecatmur Sep 20 '12 at 15:28
  • 2
    Another good way might be `auto dummy = [](...){};`, which would be usable local to a function, as opposed to a full-blown function template. Or even just a local struct if you don't have access to lambdas (which compiler doesn't...): `struct { void operator()(...){} } dummy;`. – Xeo Sep 20 '12 at 21:04
  • 3
    It should be mentioned that the array tricks fail when the pack expansion expand to nothing, since zero-size arrays are not valid. – Johannes Schaub - litb Sep 20 '12 at 21:25
  • @JohannesSchaub-litb good point; prepending a 0 to the array initializer pack fixes it (above). – ecatmur Sep 21 '12 at 09:06
  • Nice catch, **litb**. Is there any advantage to using function calls over arrays? Undefined order of evaluation is a no-go, at least in my application. Also, a full solution should include casting `dummy` to `void` to silence the unused variable warning. – marton78 Sep 21 '12 at 13:29
  • could also avoid named array by using cast `(char []) {0, (func(args), 0)...};` – dan Apr 01 '16 at 17:51
  • Your suggestion to put eclipse(...) inside parenthese doesn't compile at all. The easiest fix is using "comma operator": (print_one::run(std::forward(args)),...); – Nick Huang Sep 19 '21 at 08:57
5

The standard dictates where pack expansion is allowed:

§14.5.3 [temp.variadic] p4

[...] Pack expansions can occur in the following contexts:

  • In a function parameter pack (8.3.5); the pattern is the parameter-declaration without the ellipsis.
  • In a template parameter pack that is a pack expansion (14.1):
    • if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration without the ellipsis;
    • if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is the corresponding type-parameter without the ellipsis.
  • In an initializer-list (8.5); the pattern is an initializer-clause.
  • In a base-specifier-list (Clause 10); the pattern is a base-specifier.
  • In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
  • In a template-argument-list (14.3); the pattern is a template-argument.
  • In a dynamic-exception-specification (15.4); the pattern is a type-id.
  • In an attribute-list (7.6.1); the pattern is an attribute.
  • In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
  • In a capture-list (5.1.2); the pattern is a capture.
  • In a sizeof... expression (5.3.3); the pattern is an identifier.

So basically, as a top-level statement, expansion is not allowed. The rationale behind this? No idea. Most likely they only picked contexts where a seperating comma (,) is part of the grammar; anywhere else you might pick overloaded operator, if user-defined types are involved and get in trouble.

Xeo
  • 129,499
  • 52
  • 291
  • 397