marton78
marton78

Reputation: 3989

Expression contains unexpanded parameter packs

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?

Upvotes: 18

Views: 26301

Answers (2)

ecatmur
ecatmur

Reputation: 157344

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.

Upvotes: 28

Xeo
Xeo

Reputation: 131789

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.

Upvotes: 5

Related Questions