template<typename... T>
void foo(T... args);
In the above example, T is expanded according to §14.5.3 - (4.1) of the standard.
§14.5.3 - (4.1) — In a function parameter pack (8.3.5); the pattern is the parameter-declaration without the ellipsis.
What exactly is happening here? Let's say I call the function with 3 integers.
foo(1, 2, 3);
Is the parameter pack being expanded like
foo(int arg1, int arg2, int arg3);
where arg1, arg2, and arg3 are just arbitrary names given to by the compiler?
The standard says how "the pattern is the parameter-declaration without the ellipsis"
The other way I interpret that is args gets a single parameter-declartion.Is args getting its own type? I've tried doing
std::cout << typeid(args).name;
but that doesn't work, and throws compiler errors. So I could assume it's not getting its own type. Could someone "dumb down" what really is happening here, and the behavior of the function parameter pack?
Alright, I think I've figured it out. Correct me if I'm wrong.
A parameter expansion happens when the ellipsis is on the right on the pattern. Our pattern here is simply just T:
foo(T... args);
The standard states that the result of this will be a parameter-declaration. This decays into:
attribute-specifier-seqopt decl-specifier-seq declarator
attribute-specifier-seq can be ignored.
decl-specifier-seq is the type, T;
declarator is ... args
Semantically, this is a function parameter pack declaration. Nothing special.
Once again, my curiosity causes me to go insane for a little... heh.
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.
In the C++03 standard, [dcl.fct] p.2 states that:
The parameter-declaration-clause determines the arguments that can be specified, and their processing, when the func- tion is called. [ Note: the parameter-declaration-clause is used to convert the arguments specified on the function call; see5.2.2. —endnote]Iftheparameter-declaration-clauseisempty,thefunctiontakesnoarguments.Theparameter list (void) is equivalent to the empty parameter list. Except for this special case, void shall not be a parameter type (though types derived from void, such as void*, can). If the parameter-declaration-clause terminates with an ellipsis, the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument. Where syntactically correct, “, ...” is synonymous with “...”.
The grammar for a parameter-declaration-clause allows it to end in either ... or , .... I found this question and the answers said that initially the grammar allowed only ..., and the comma variant (, ...) was introduced for compatibility with C.
My question is why does the quoted paragraph say "where syntactically correct"? Considering that function parameter packs or pack expansions were not present in C++03, are there any cases where it would be "syntactically incorrect" to consider ... synonymous with , ...?
Thank you.
for my understanding, it just mean that when you use ,... with a correct syntax, then it can be replaced by ...
for instance :
int printf(const char*, ...); is a correct syntax and can be replaced by int printf(const char*...);
int printf(,...); is not syntactically correct and is not equivalent to int printf(...);
You can easily try in a C code by just adding the following prototypes:
void printf(...); --> works
void printf(,...); --> expected primary-expression before ‘,’ token
are there any cases where it would be "syntactically incorrect" to consider ... synonymous with , ...?
Exactly as Guillaume says, it would be "syntactically incorrect" to rewrite
foo(...)
as
foo(, ...)
so that clause just handles the corner case where there is no prior parameter from which the ellipsis can be separated by a comma.
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.
I answered this question How can I get my va_list arguments to repeat itself? and noticed the uncommon function declaration:
void ordered(int num1, double list ...);
First i thought the compiler would complain but clang 3.2 didn't and neither did g++ 4.7.2.
What does this declaration expand to? As what does it get interpreted?
Edit: I know about the ellipsis. But the normal form is <return type> <function-name>(<argument1-type> <arg-name>, ...); in the example the comma is missing.
It's the same as:
void ordered(int num1, double list, ...);
This is a snippet of the grammar in the C++ standard:
parameter-declaration-clause:
parameter-declaration-list[opt] ...[opt]
parameter-declaration-list , ...
Basically the ellipsis can be preceded by , if the are other parameter declarations, but it need not be. The function declaration:
void f(int,double...);
really means:
void f(int,double,...);
void ordered(int num1, double list ...);
is same as:
void ordered(int num1, double list, ...);
Reference:
Standard C++11 8.3.5.3/4:
parameter-declaration-clause:
parameter-declaration-listopt ...opt
parameter-declaration-list , ...
If the parameter-declaration-clause terminates with an ellipsis or a function parameter pack (14.5.3), the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument and are not function parameter packs. Where syntactically correct and where “...” is not part of an abstract-declarator, “, ...” is synonymous with “...”.
The three dots (...) are called "ellipses" and denote a variable argument list. So you can pass as many arguments as you like (there is an OS-specified limitation, though). In this way, printf works, for example.
See here for further explanation.
I guess you mean the "..." right?
For some functions it is not possible to specify the number and type of all arguments expected in a call. Such a function is declared by terminating the list of argument declarations with the ellipsis (...).
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.