Reputation: 3989
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
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
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