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?
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.
Related
Having following functions f0, f1, f2 in C++14 code, which accepts arbitrary number of fixed-length arrays:
#include <functional>
template<typename... TS, size_t N> void f0( TS(&& ... args)[N] ) {}
template<typename T, size_t... NS> void f1( T(&& ... args)[NS] ) {}
template<typename... TS, size_t... NS> void f2( TS(&& ... args)[NS] ) {}
int main(){
f0({1,2}, {3.0,4.0}, {true, false});
f1({1,2,3}, {4,5}, {6});
f2({1,2,3}, {4.0,5.0}, {true});
return 0;
}
Function f0 accepts arrays with different types and fixed array length. Function f1 accepts arrays with fixed type and different array lengths. It's clear how this works: C++ compiler deduces variable-length parameter pack in immediate context of template function instantiation, which is expanded in (&& ... args) expression.
Function f2 accepts arrays with different types and different array lengths, which produces two variable-length parameter packs, however there is only one ellipsis operator in pack expansion (&& ... args), but code compiles and works well.
So question is: what is general rule for expanding multiple parameter packs within single ellipsis operator? Obviously, at a minimum, they must be the same length, but what are the other requirements? Is there a precise definition that the n-th element of the first parameter packing should expand along with the n-th element of the second parameter packing?
Also, following code with explicit template argument provision does not compile: f2<int,float,bool,3,2,1>({1,2,3},{4.0f,5.0f},{true});. It would be interesting to know the reasons for this behaviour.
This is specified in C++ Standard section [temp.variadic]. Basically, it's what you described: when a pack expansion expands more than one pack, all those packs must have the same number of elements. And the expansion in most cases forms a list where the nth element in the resulting list uses the nth element of each expanded pack.
More exactly, paragraph 5 defines
A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:
In a function parameter pack; the pattern is the parameter-declaration without the ellipsis.
...
In your example, each function template declares a function parameter pack named args. The patterns are TS(&& args)[N], T(&& args)[NS], and TS(&& args)[NS].
Paragraph 7 (after clarifying which packs are expanded by which pack expansions that when one pack expansion appears inside another) has the requirement
All of the packs expanded by a pack expansion shall have the same number of arguments specified.
And paragraph 8:
The instantiation of a pack expansion that is neither a sizeof... expression nor a fold-expression produces a list of elements E1, E2, ..., EN, where N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element.
So yes, for the instantiation of f3 where TS is deduced as int, double, bool and NS is deduced as 3, 2, 1, the pack expansion becomes a function parameter list with types int(&&)[3], double(&&)[2], bool(&&)[1].
All packs appearing as part of one pack expansion (...) must have exactly the same length. Otherwise substitution fails (which depending on context is a hard error or SFINAE). (see [temp.variadic]/7)
All packs are expanded so that the i-th expanded element of the pack expansion uses the i-th element of each pack. For the detailed expansion rule see [temp.variadic]/8.
(Links are to the post-C++20 draft of the standard, but the same applies to C++14.)
A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.
A template with at least one parameter pack is called a variadic template.
Visit[1]: https://en.cppreference.com/w/cpp/language/parameter_pack
Visit[2]: Multiple expansions of multiple parameter packs in the same expression
Visit[3]: https://www.ibm.com/docs/en/zos/2.1.0?topic=only-variadic-templates-c11
I hope this help you.
template <typename... Args>
void bark( int = 0, Args&&... args ) {}
int main() {
bark();
bark(1);
bark(1, 2);
}
Is this code well-formed according to the C++ Standard?
The proposed duplicate does not contain the same calls of the function.
Due to CWG 777, the declaration is valid:
In a given function declaration, all each parameters subsequent to a
parameter with a default argument shall have a default arguments
supplied in this or a previous declarations or shall be a function
parameter pack.
Deduction should succeed in all three cases, since the default argument makes no difference to the nature of deduction: If no argument to the pack parameter args is provided, it's deduced to the empty pack via [temp.arg.explicit]/3, otherwise the usual rule in [temp.deduct.call]/1 applies (as the pack is clearly not in a non-deduced context).
This question already has answers here:
variadic template parameter pack expanding for function calls
(2 answers)
Closed 8 years ago.
In the code below I expand a parameter pack inside an initializer list, while calling a function DoSomethingReturnInt on each element. Below that I attempt to do something seemingly similar to try and call DoSomething on each element, but get a compiler error. Is this simply not possible or do I simply have to modify it slightly to accomplish this? It seems to me that something like this should be possible.
template <class T>
int DoSomethingReturnInt(T&& t)
{}
template <class T>
void DoSomething(T&& t)
{}
template <class... T>
void variadic(T&&... args)
{
int arr[] = { DoSomethingReturnInt(args)... }; //Compiles OK
DoSomething(args)...; //error: parameter packs not expanded with '...'
}
int main()
{
variadic("Testing", "one", 2.0, 3);
}
This is not a valid location for parameter pack expansion. The valid contexts for pack expansion is covered in the draft C++ standard section 14.5.3 Variadic templates which says:
A pack expansion consists of a pattern and an ellipsis, the
instantiation of which produces zero or more instantiations of the
pattern in a list (described below). The form of the pattern depends
on the context in which the expansion occurs. 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.
This is also covered on cppreference section for Parameter pack.
You can use the comma operator to your advantage here.
int dummy[] = { (DoSomething(args), 0)... };
EDIT: If you don't like the comma operator "abuse", maybe a lambda?
int dummy[] = { []() { DoSomething(args); return 0; }()... };
Note that gcc4.9 doesn't seem to be able to handle this, but clang will do just fine.
There is a technique I sometimes use when overriding template functions that goes like this:
#include <utility>
template<int> struct unique_enum { enum class type {}; };
template<int index> using UniqueEnum = typename unique_enum<index>::type;
template<bool b, int index=1>
using EnableFuncIf = typename std::enable_if< b, UniqueEnum<index> >::type;
template<bool b, int index=1>
using DisableFuncIf = EnableFuncIf<!b, -index>;
// boring traits class:
template<typename T>
struct is_int : std::false_type {};
template<>
struct is_int<int> : std::true_type {};
#include <iostream>
// use empty variardic packs to give these two SFINAE functions different signatures:
template<typename C, EnableFuncIf< is_int<C>::value >...>
void do_stuff() {
std::cout << "int!\n";
}
template<typename C, DisableFuncIf< is_int<C>::value >...>
void do_stuff() {
std::cout << "not int!\n";
}
int main() {
do_stuff<int>();
do_stuff<double>();
}
This distinguishes do_stuff from do_stuff, because one takes 0 or more UniqueEnum<1>s, and the other takes 0 or more UniqueEnum<-1>s. gcc 4.8 considers these different empty packs to be distinct.
However, in the latest version of clang I tried, this fails: it treats the function with 0 UniqueEnum<1>s as being the same as the function with 0 UniqueEnum<-1>s.
There are easy workarounds that work in clang, but I'm wondering if my above technique is legal -- do two function templates, which differ only by empty variardic parameter packs, actually different?
I think GCC is right, and your technique is correct. Basically, since the type argument for C is specified explicitly, the question is whether:
a. substitution of C everywhere else in the function template signature happens first, and then type deduction is performed (which should result in a substitution failure); or
b. type deduction is performed first, and then substitution is performed (which would not result in a substitution failure, because the corresponding argument pack would be empty, and so there would be no substitution to perform).
It seems GCC assumes (1), while Clang assumes (2). Paragraph 14.8.2/2 of the C++11 Standard specifies:
When an explicit template argument list is specified, the template arguments must be compatible with the
template parameter list and must result in a valid function type as described below; otherwise type deduction
fails. Specifically, the following steps are performed when evaluating an explicitly specified template
argument list with respect to a given function template:
— The specified template arguments must match the template parameters in kind (i.e., type, non-type,
template). There must not be more arguments than there are parameters unless at least one parameter
is a template parameter pack, and there shall be an argument for each non-pack parameter. Otherwise,
type deduction fails.
— Non-type arguments must match the types of the corresponding non-type template parameters, or must
be convertible to the types of the corresponding non-type parameters as specified in 14.3.2, otherwise
type deduction fails.
— The specified template argument values are substituted for the corresponding template parameters as
specified below.
The following paragraph then says:
After this substitution is performed, the function parameter type adjustments described in 8.3.5 are performed. [...]
Moreover, paragraph 14.8.2/5 specifies:
The resulting substituted and adjusted function type is used as the type of the function template for template
argument deduction. [...]
Finally, paragraph 14.8.2/6 goes as follows:
At certain points in the template argument deduction process it is necessary to take a function type that
makes use of template parameters and replace those template parameters with the corresponding template
arguments. This is done at the beginning of template argument deduction when any explicitly specified template
arguments are substituted into the function type, and again at the end of template argument deduction
when any template arguments that were deduced or obtained from default arguments are substituted.
This all seems to imply that first substitution is performed, then template argument deduction. Hence, a substitution failure should occur in either case and one of the two templates should be discarded from the overload set.
Unfortunately, there does not seem to be a clear specification as to what the behavior should be when templates arguments are deduced rather than being explicitly specified.
Does anyone know if the following implicit capture of 'ts' is well-formed:
template<class ... Ts> void bar(Ts ... ts) { }
template<class ... Ts> int foo(Ts ... ts) {
auto L = [=] () {
bar(ts...);
};
L();
return 0;
}
int g = foo(1, 2, 3);
Does the standard clearly state anywhere that this should not be well formed?
14.5.3/6:
The instantiation of a pack expansion that is not a sizeof... expression produces a list E1, E2, ..., EN , where
N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. All of the Ei become elements in the enclosing list.
Regardless of whether you're allowed to explicitly capture a pack (you can, using [ts ...]), the general rule of expansion will result in capture of each element of the list.
I guess it's well formed, I've not found a straight statement (the wording sometimes lacks in clarity / illustration for certain situations) but I guess it may be inferred:
§5.1.2/23:
A capture followed by an ellipsis is a pack expansion (14.5.3). [ Example:
template<class... Args>
void f(Args... args) {
auto lm = [&, args...] { return g(args...); };
lm();
}
— end example ]
A capture followed by an ellipsis, implies args, in the lambda-capture, is an example of a capture (in this case, explicit), and the notable fact is that args is a parameter pack identifier. This short paragraph has the sole job of describing how lambda-captures hold pack expansions, which demonstrates parameter packs can be captured even though its purpose is not about allowing them to be captured.
§5.1.2/12:
An entity is captured if it is captured explicitly or implicitly.[...]
§3/3:
An entity is a value, object, reference, function, enumerator, type, class member, template, template specialization, namespace, parameter pack, or this.
From this I assume parameter packs are entities that can be captured explicitly or implicitly, and so, the same capturing rules as for ordinary variables shall apply, except that parameter packs shall be expanded accordingly.
I guess your question (and the same argumentation) could be applied equally well for reference variables for example (It is unspecified whether or not a reference requires storage. §8.3.2/4). It seems you're interested when you're allowed or not to refer to a parameter pack identifier inside a lambda.
You can think the same about reference variables in the outer scope since you may have access to them but couldn't even be allowed to access the identifier of the original variable.
They're as ethereal as parameter packs.