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?
Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.
Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.
Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With