The template is defined in the standard in [temp.variadic] / 4 : (via @tc )
A package extension consists of a template and an ellipsis, which creates zero or more instances of the template in the list (described below). The shape of the template depends on the context in which the extension occurs. Package extensions can occur in the following contexts:
The above quotation from a standard project indicates how much of the grammar described in the links is an extension of the “template”. To understand this, you need to know how the C ++ grammar is described, and the exceptions to how it is used in the most standard text; however, if you have basic BNF knowledge and a little patience, you can fix it. Names are often helpful.
However, you can use ... and basically understand it without going almost so deep.
The general rule is simple: you have some bit of C ++ grammar (called a template) that is parsed in the usual way, where a type package is processed as one type, and a package of literals is treated as one literal, Then, at the end, you have ... expander. He then takes all the unexpanded packages into a bit of C ++ grammar immediately before (the template) and extends it.
How it works in every context is different; it is not just a macro extension. The contexts in which it acts ... are listed above; extension effects are listed in the standard at every point where it really is.
In the most common cases ... pattern is an expression, and the expression expands as if each copy were separated (not an operator, , but another "normal"), usually in the context where a list of things is expected (function call, list of initializers etc.).
There are contexts for declaring function parameters (where ... and extends the types of function parameters, and introduces a new package of parameter names), in the parameter lists of the template (where it usually introduces the package), etc.
sizeof... little strange: for sizeof... it counts how many elements are in the packet passed in () . This works differently because ... does not apply to the "structure on the left."
For alignas(X...) we get alignas( X@0 ), alignas( X@1 ), ... (where @0 is my pseudo-code for the first element of the package), because alignas( X@0 , X@1 , ...) invalid C ++ (again @TC in the comments below).
For inheritance, it creates a set of base classes. For mem-initializer-lists, it allows you to pass ctor arguments to a package of base classes. For lambda, this gives you limited packet capture (not full expression capture with the extension of the last one I checked).
The template is expanding. It is important to note that the extended template is not expanded by another template expander: therefore std::array< Ts, sizeof...(Ts) >... is an array of arrays of various types, each of which has several elements determined by how large package yourself. sizeof...(Ts) "counts the extension" Ts in its () , although ... not "correct" because the language defines Ts in () as a template that extends to ...
A template cannot generally be called an expression, because types are not expressions, and some templates are expressions (or at least expand into lists of expressions). And in some cases ... extends the type template into an extended type package (for example, in a throw expression).
The general rule that you treat ... as an expanding object on the left, appropriately in the local context, works for almost everything except sizeof... (which is a magic operator that tells you how many elements are in the parameter package). It will only be in corner cases when it does not create a decent model. And in my experience, in the worst case, this will lead to code that doesn't compile when you think it should; you can explore workarounds in this case. For example, remembering that the bare operator "has no local context", so you cannot do a = std::get<Is>(tup))...; , and in a roundabout way (void)(int[]){0,(a = std::get<Is>(tup)),0)...}; (it may be necessary to introduce tpedef, which is int[] ), where we provide the context (creating an array) for extending the package to work.
C ++ 1z fold expressions are another bizarre spot where ... not applied on the left; here they wanted to have an extension of the binary operator, so the "left" does not have the same meaning as the usual "unary" extension.