Definition of "pattern" for parameter pack expansion, especially within a function call - c++

I understand that when an ellipsis (...) occurs to the right of a pattern containing a parameter pack, the pattern is expanded once for each parameter in the pack. However, though I have been able to find isolated examples of patterns with their expansion, I have been unable to find a definition of what constitutes a pattern. From what I can see, whitespace plays no role in the definition of the pattern, but parentheses do. For instance, in this example:
template<typename ... Ts>
void func(Ts)
{
do_something(validate(Ts)...);
}
the do_something line would be expanded to:
do_something(validate(var1), validate(var2), validate(var3))
if Ts happened to represent three variables. By contrast:
do_something(validate(Ts...));
would be expanded to:
do_something(validate(var1, var2, var3));
So clearly parentheses have something to do with determining where the pattern begins and ends. I can also see that whitespace does not. But that only gets me so far. I'd like to know exactly what constitutes a pattern, and how it will be expanded. I tried searching through the C++ Standard, but found too many instances of "parameter pack" to make that effective. Could someone please give me a definition of "pattern", or a link to a definition, or both?
UPDATE: To limit the scope of my question, I'd like to focus on the case where a pattern occurs within a function call. I've edited the title accordingly. Sorry I didn't make that clear from the beginning.

A pattern is defined in the standard under [temp.variadic]/4: (via #t.c.)
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 ([dcl.fct]); the pattern is the parameter-declaration without the ellipsis.
In a template parameter pack that is a pack expansion ([temp.param]):
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 ([dcl.init]); the pattern is an initializer-clause.
In a base-specifier-list (Clause [class.derived]; the pattern is a base-specifier.
In a mem-initializer-list ([class.base.init]) for a mem-initializer whose mem-initializer-id denotes a base class; the pattern is the mem-initializer.
In a template-argument-list ([temp.arg]); the pattern is a template-argument.
In a dynamic-exception-specification ([except.spec]); the pattern is a type-id.
In an attribute-list ([dcl.attr.grammar]); the pattern is an attribute.
In an alignment-specifier ([dcl.align]); the pattern is the alignment-specifier without the ellipsis.
In a capture-list ([expr.prim.lambda]); the pattern is a capture.
In a sizeof... expression ([expr.sizeof]); the pattern is an identifier.
In a fold-expression ([expr.prim.fold]); the pattern is the cast-expression that contains an unexpanded parameter pack.
The above quote from the standard draft talks about what part of the grammar described in the links is the "pattern" that is being expanded. To understand it, you need to know how the C++ grammar is described and the exceptions about how it is used in the standard text itself; however, if you have basic BNF knowledge and a bit of patience, you can work it out. The names are often useful as well.
However, you can use ... and mostly understand it without going nearly that deep.
The general rule is simple: you have some bit of C++ grammar (called the pattern) parsed in the usual way, where a pack of types is treated like a single type, and a pack of literals is treated like a single literal. Then, at the end of it, you have a ... expander. It then takes all of the unexpanded packs in the bit of C++ grammar immediately before (the pattern) and expands it.
How this works in each context is different; it isn't just a macro expansion. The contexts in which ... is valid are enumerated above; the effects of the expansion are listed in the standard at each point where it is valid.
In the most mundane use cases of ..., the pattern is an expression, and the expression gets expanded as-if it was each copy separated by , (not operator,, but the other "normal" one), usually in a context where a list of things is expected (function call, initializer list, etc).
There are function parameter declaration contexts (where ... both expands the types of the function parameters, and introduces a new pack of the paramter names), in template parameter lists (where it introduces a pack usually), etc.
sizeof... is a bit strange: for sizeof... it counts how many elements are in the pack passed in the (). This works differently, as the ... does not apply to the "structure on the left".
For alignas(X...), we end up with alignas(X#0), alignas(X#1), ... (where #0 is my pseudocode for the first element of the pack), as alignas(X#0, X#1, ...) is not valid C++ (again #T.C. in comments below).
For inheritance, it creates a set of base classes. For mem-initializer-lists, it lets you pass ctor arguments to said pack of base classes. For lambdas, it gives you limited capture of packs (not full on expression capture with expansion last I checked).
The pattern is the thing expanded. Importantly, an expanded pattern is not expanded by another pattern expander: so std::array< Ts, sizeof...(Ts) >... is a pack of arrays of various types, each one with a number of elements determined by how big the pack itself is. sizeof...(Ts) "counts as expanding" the Ts in its (), even though ... isn't to "its right", because the language defines the Ts in the () as the pattern that is expanded by the ....
The pattern in the general case cannot be called an expression, because types are not expressions, and some patterns are expressions (or at least expand into lists of expressions). And in some cases, ... expands a type-pattern into an expanded package of types (like in the throw expression).
The general rule, that you treat ... as expanding the thing on the left in an appropriate way in the local context, works for almost everything except sizeof... (which is a magical operator that tells you how many elements are in a parameter pack). It is only going to be in corner cases where this doesn't produce a decent model. And in my experience, at worse it will result in code that doesn't compile when you think it should; you can learn workarounds in that case. Like, remembering that a bare statement "has no local context", so you cannot do a = std::get<Is>(tup))...;, but rather the workaround (void)(int[]){0,(a = std::get<Is>(tup)),0)...}; (might have to typedef that int[]) where we provide a context (creating an array) for the pack expansion to work in.
C++1z's fold expressions are another quirky spot where ... does not apply on the left; here, they wanted to have an expansion of a binary operator, so "on the left" doesn't make as much sense as the usual "unary" expansion.

The pattern is defined by its association with the ... expansion syntax. So validate(ts) alone is just a fragment of meaningless text. But validate(ts)... makes the validate(ts) part the pattern of the expansion. So it's a matter of how ... affects the associated code.
Usually, unpack syntax designates that the stuff to the left of the ... is the pattern. With C++17's fold expressions, this becomes a bit more complex. But generally speaking, if you want to know what the pattern is, find the ..., and look at the expression immediately to the left of it.
The specific limitations on what can be in the pattern depend entirely on where the expansion is taking place and what kind it of pack is involved (type vs. non-type, etc). The pattern needs to be whatever would be legal code for whatever context it is expanded within.

Related

The "(optional)" marker in cppreference.com documentation

Last week, I had a discussion with a colleague in understanding the documentation of C++ features on cppreference.com. We had a look at the documentation of the parameter packs, in particular the meaning of the (optional) marker:
(Another example can be found here.)
I thought it means that this part of the syntax is optional. Meaning I can omit this part in the syntax, but it is always required to be supported by the compiler to comply with the C++ standard. But he stated that it means that it is optional in the standard and that a compiler does not need to support this feature to comply to the standard. Which is it? Both of these explanations make sense to me.
I couldn't find any kind of explanation on the cppreference web site. I also tried to google it but always landed at std::optional...
The opt / (optional) suffix means the symbol is optional [for the C++ programmer to use; not the compiler to support]
As this question has been tagged language-lawyer, and as general when we look for a definite reference, let's move away from CppReference and into the standard.
Where CppReference uses the (optional) subscript, the standard uses opt; e.g. as in [temp.param]/1:
The syntax for template-parameters is:
template-parameter:
type-parameter
parameter-declaration
type-parameter:
type-parameter-key ...opt identifieropt
[... and so on]
[syntax]/1 describe the syntax notation [emphasis mine]:
In the syntax notation used in this document, syntactic categories are
indicated by italic type, and literal words and characters in constant
width type. Alternatives are listed on separate lines except in a few
cases where a long set of alternatives is marked by the phrase “one
of”. If the text of an alternative is too long to fit on a line, the
text is continued on subsequent lines indented from the first one.
An optional terminal or non-terminal symbol is indicated by the subscript “opt", so
{ expressionopt }
indicates an optional expression enclosed in braces.
Thus, you are correct, and your colleague is wrong. Particularly for your example of template parameter packs (which we introduce by the optional ... after typename) the identifier that follows after typename..., which names the pack (or the template parameter, if ... is omitted), is optional.
But he stated that it means that it is optional in the standard and that a compiler does not need to support this feature to comply to the standard.
The ridiculousness of this claim becomes even more clear if we annotate the "optional permutations" of a class template with a single type template parameter:
template<typename>
// ^^^^^^^^ type-parameter
// (omitting optional '...' and 'identifier')
struct S;
template<typename T>
// ^^^^^^^^^^ type-parameter
// (omitting optional '...')
struct S;
template<typename...>
// ^^^^^^^^^^^ type-parameter
// (omitting optional 'identifier')
struct S;
template<typename... Ts>
// ^^^^^^^^^^^^^^ type-parameter
struct S;
If the claim above was true, only the first of these four would need to be supported by a compliant implementation (based solely on grammar, in this contrived example), which would mean a compiler vendor could offer a compliant implementation where we could never name neither template (type) parameters nor function parameters.
It means that particular token is optional. For instance both these declarations work:
template <class... Args>
void foo();
template <class...>
void bar();
While I found a page that lists all of the marks, I was unable to find a page that specifies what the marks are intended to mean. Still, I might ask your colleague to take a look at some other pages, with the goal of the colleague abandoning the idea that "optional" means "optional to support". (This is not a definitive argument, but many would find it persuasive.) I found two good examples at Function declaration.
Function declaration:
noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional)
Focus on cv (short for "const-volatile"), which is marked "optional" and which is "only allowed in non-static member function declarations". Your colleague's interpretation of this marker would mean that compilers do not have to support const member functions, as the const keyword is "optional".
Function definition, the first option for function-body:
ctor-initializer(optional) compound-statement
The "optional" part here is the member initializer list (only allowed in constructors). Is your colleague ready to claim that a compiler need not support member initializer lists?
Sometimes one should look at the familiar to understand annotations.

In OCaml Menhir, how to write a parser for C++/Rust/Java-style generics

In C++, a famous parsing ambiguity happens with code like
x<T> a;
Is it if T is a type, it is what it looks like (a declaration of a variable a of type x<T>, otherwise it is (x < T) > a (<> are comparison operators, not angle brackets).
In fact, we could make a change to make this become unambiguous: we can make < and > nonassociative. So x < T > a, without brackets, would not be a valid sentence anyway even if x, T and a were all variable names.
How could one resolve this conflict in Menhir? At first glance it seems we just can't. Even with the aforementioned modification, we need to lookahead an indeterminate number of tokens before we see another closing >, and conclude that it was a template instantiation, or otherwise, to conclude that it was an expression. Is there any way in Menhir to implement such an arbitrary lookahead?
Different languages (including the ones listed in your title) actually have very different rules for templates/generics (like what type of arguments there can be, where templates/generics can appear, when they are allowed to have an explicit argument list and what the syntax for template/type arguments on generic methods is), which strongly affect the options you have for parsing. In no language that I know is it true that the meaning of x<T> a; depends on whether T is a type.
So let's go through the languages C++, Java, Rust and C#:
In all four of those languages both types and functions/methods can be templates/generic. So we'll not only have to worry about an ambiguity with variable declarations, but also function/method calls: is f<T>(x) a function/method call with an explicit template/type argument or is it two relational operators with the last operand parenthesized? In all four languages template/generic functions/methods can be called without template/type when those can be inferred, but that inference isn't always possible, so just disallowing explicit template/type arguments for function/method calls is not an option.
Even if a language does not allow relational operators to be chained, we could get an ambiguity in expressions like this: f(a<b, c, d>(e)). Is this calling f with the three arguments a<b, c and d>e or with the single argument a<b, c, d>(e) calling a function/method named a with the type/template arguments b,c,d?
Now beyond this common foundation, most everything else is different between these languages:
Rust
In Rust the syntax for a variable declaration is let variableName: type = expr;, so x<T> a; couldn't possibly be a variable declaration because that doesn't match the syntax at all. In addition it's also not a valid expression statement (anymore) because comparison operators can't be chained (anymore).
So there's no ambiguity here or even a parsing difficulty. But what about function calls? For function calls, Rust avoided the ambiguity by simply choosing a different syntax to provide type arguments: instead of f<T>(x) the syntax is f::<T>(x). Since type arguments for function calls are optional when they can be inferred, this ugliness is thankfully not necessary very often.
So in summary: let a: x<T> = ...; is a variable declaration, f(a<b, c, d>(e)); calls f with three arguments and f(a::<b, c, d>(e)); calls a with three type arguments. Parsing is easy because all of these are sufficiently different to be distinguished with just one token of lookahead.
Java
In Java x<T> a; is in fact a valid variable declaration, but it is not a valid expression statement. The reason for that is that Java's grammar has a dedicated non-terminal for expressions that can appear as an expression statement and applications of relational operators (or any other non-assignment operators) are not matched by that non-terminal. Assignments are, but the left side of assignment expressions is similarly restricted. In fact, an identifier can only be the start of an expression statement if the next token is either a =, ., [ or (. So an identifier followed by a < can only be the start of a variable declaration, meaning we only need one token of lookahead to parse this.
Note that when accessing static members of a generic class, you can and must refer to the class without type arguments (i.e. FooClass.bar(); instead of FooClass<T>.bar()), so even in that case the class name would be followed by a ., not a <.
But what about generic method calls? Something like y = f<T>(x); could still run into the ambiguity because relational operators are of course allowed on the right side of =. Here Java chooses a similar solution as Rust by simply changing the syntax for generic method calls. Instead of object.f<T>(x) the syntax is object.<T>f(x) where the object. part is non-optional even if the object is this. So to call a generic method with an explicit type argument on the current object, you'd have to write this.<T>f(x);, but like in Rust the type argument can often be inferred, allowing you to just write f(x);.
So in summary x<T> a; is a variable declaration and there can't be expression statements that start with relational operations; in general expressions this.<T>f(x) is a generic method call and f<T>(x); is a comparison (well, a type error, actually). Again, parsing is easy.
C#
C# has the same restrictions on expression statements as Java does, so variable declarations aren't a problem, but unlike the previous two languages, it does allow f<T>(x) as the syntax for function calls. In order to avoid ambiguities, relational operators need to be parenthesized when used in a way that could also be valid call of a generic function. So the expression f<T>(x) is a method call and you'd need to add parentheses f<(T>(x)) or (f<T)>(x) to make it a comparison (though actually those would be type errors because you can't compare booleans with < or >, but the parser doesn't care about that) and similarly f(a<b, c, d>(e)) calls a generic method named a with the type arguments b,c,d whereas f((a<b), c, (d<e)) would involve two comparisons (and you can in fact leave out one of the two pairs of parentheses).
This leads to a nicer syntax for method calls with explicit type arguments than in the previous two languages, but parsing becomes kind of tricky. Considering that in the above example f(a<b, c, d>(e)) we can actually place an arbitrary number of arguments before d>(e) and a<b is a perfectly valid comparison if not followed by d>(e), we actually need an arbitrary amount of lookahead, backtracking or non-determinism to parse this.
So in summary x<T> a; is a variable declaration, there is no expression statement that starts with a comparison, f<T>(x) is a method call expression and (f<T)>(x) or f<(T>(x)) would be (ill-typed) comparisons. It is impossible to parse C# with menhir.
C++
In C++ a < b; is a valid (albeit useless) expression statement, the syntax for template function calls with explicit template arguments is f<T>(x) and a<b>c can be a perfectly valid (even well-typed) comparison. So statements like a<b>c; and expressions like a<b>(c) are actually ambiguous without additional information. Further, template arguments in C++ don't have to be types. That is, Foo<42> x; or even Foo<c> x; where c is defined as const int x = 42;, for example, could be perfectly valid instantiations of the Foo template if Foo is defined to take an integer as a template argument. So that's a bummer.
To resolve this ambiguity, the C++ grammar refers to the rule template-name instead of identifier in places where the name of a template is expected. So if we treated these as distinct entities, there'd be no ambiguity here. But of course template-name is defined simply as template-name: identifier in the grammar, so that seems pretty useless, ... except that the standard also says that template-name should only be matched when the given identifier names a template in the current scope. Similarly it says that identifiers should only be interpreted as variable names when they don't refer to a template (or type name).
Note that, unlike the previous three languages, C++ requires all types and templates to be declared before they can be used. So when we see the statement a<b>c;, we know that it can only be a template instantiation if we've previously parsed a declaration for a template named a and it is currently in scope.
So, if we keep track of scopes while parsing, we can simply use if-statements to check whether the name a refers to a previously parsed template or not in a hand-written parser. In parser generators that allow semantic predicates, we can do the same thing. Doing this does not even require any lookahead or backtracking.
But what about parser generators like yacc or menhir that don't support semantic predicates? For these we can use something known as the lexer hack, meaning we make the lexer generate different tokens for type names, template names and ordinary identifiers. Then we have a nicely unambiguous grammar that we can feed our parser generator. Of course the trick is getting the lexer to actually do that. In order to accomplish that, we need to keep track of which templates and types are currently in scope using a symbol table and then access that symbol table from the lexer. We'll also need to tell the lexer when we're reading the name of a definition, like the x in int x;, because then we want to generate a regular identifier even if a template named x is currently in scope (the definition int x; would shadow the template until the variable goes out of scope).
This same approach is used to resolve the casting ambiguity (is (T)(x) a cast of x to type T or a function call of a function named T?) in C and C++.
So in summary, foo<T> a; and foo<T>(x) are template instantiations if and only if foo is a template. Parsing's a bitch, but possible without arbitrary lookahead or backtracking and even using menhir when applying the lexer hack.
AFAIK C++'s template syntax is a well-known example of real-world non-LR grammar. Strictly speaking, it is not LR(k) for any finite k... So C++ parsers are usually hand-written with hacks (like clang) or generated by a GLR grammar (LR with branching). So in theory it is impossible to implement a complete C++ parser in Menhir, which is LR.
However even the same syntax for generics can be different. If generic types and expressions involving comparison operators never appear under the same context, the grammar may still be LR compatible. For example, consider the rust syntax for variable declaration (for this part only):
let x : Vec<T> = ...
The : token indicates that a type, rather than an expression follows, so in this case the grammar can be LR, or even LL (not verified).
So the final answer is, it depends. But for the C++ case it should be impossible to implement the syntax in Menhir.

typed vs untyped vs expr vs stmt in templates and macros

I've been lately using templates and macros, but i have to say i have barely found information about these important types. This is my superficial understanding:
typed/expr is something that must exists previously, but you can use .immediate. to overcome them.
untyped/stmt is something that doesn't to be defined previously/one or more statements.
This is a very vague notion of the types. I'd like to have a better explanation of them, including which types should be used as return.
The goal of these different parameter types is to give you several increasing levels of precision in specifying what the compiler should accept as a parameter to the macro.
Let's imagine a hypothetical macro that can solve mathematical equations. It will be used like this:
solve(x + 10 = 25) # figures out that the correct value for x is 15
Here, the macro just cares about the structure of the supplied AST tree. It doesn't require that the same tree is a valid expression in the current scope (i.e. that x is defined and so on). The macro just takes advantage of the Nim parser that already can decode most of the mathematical equations to turn them into easier to handle AST trees. That's what untyped parameters are for. They don't get semantically checked and you get the raw AST.
On the next step in the precision ladder are the typed parameters. They allow us to write a generic kind of macro that will accept any expression, as long as it has a proper meaning in the current scope (i.e. its type can be determined). Besides catching errors earlier, this also has the advantage that we can now work with the type of the expression within the macro body (using the macros.getType proc).
We can get even more precise by requiring an expression of a specific type (either a concrete type or a type class/concept). The macro will now be able to participate in overload resolution like a regular proc. It's important to understand that the macro will still receive an AST tree, as it will accept both expressions that can be evaluated at compile-time and expressions that can only be evaluated at run-time.
Finally, we can require that the macro receives a value of specific type that is supplied at compile-time. The macro can work with this value to parametrise the code generation. This is realm of the static parameters. Within the body of the macro, they are no longer AST trees, but rather ordinary well typed values.
So far, we've only talked about expressions, but Nim's macros also accept and produce blocks and this is the second axis, which we can control. expr generally means a single expression, while stmt denotes a list of expressions (historically, its name comes from StatementList, which existed as a separate concept before expressions and statements were unified in Nim).
The distinction is most easily illustrated with the return types of templates. Consider the newException template from the system module:
template newException*(exceptn: typedesc, message: string): expr =
## creates an exception object of type ``exceptn`` and sets its ``msg`` field
## to `message`. Returns the new exception object.
var
e: ref exceptn
new(e)
e.msg = message
e
Even thought it takes several steps to construct an exception, by specifying expr as the return type of the template, we tell the compiler that only that last expression will be considered as the return value of the template. The rest of the statements will be inlined, but cleverly hidden from the calling code.
As another example, let's define a special assignment operator that can emulate the semantics of C/C++, allowing assignments within if statements:
template `:=` (a: untyped, b: typed): bool =
var a = b
a != nil
if f := open("foo"):
...
Specifying a concrete type has the same semantics as using expr. If we had used the default stmt return type instead, the compiler wouldn't have allowed us to pass a "list of expressions", because the if statement obviously expects a single expression.
.immediate. is a legacy from a long-gone past, when templates and macros didn't participate in overload resolution. When we first made them aware of the type system, plenty of code needed the current untyped parameters, but it was too hard to refactor the compiler to introduce them from the start and instead we added the .immediate. pragma as a way to force the backward-compatible behaviour for the whole macro/template.
With typed/untyped, you have a more granular control over the individual parameters of the macro and the .immediate. pragma will be gradually phased out and deprecated.

Is there any guarantee on the order of substitution in a function template after type deduction?

Consider this function template:
template<typename T>
typename soft_error<T>::type foo(T, typename hard_error<T>::type)
{ }
After deducing type T from the type of the first argument in the call to foo(), the compiler will proceed to substitute T and instantiate the function signature.
If substitution for the return type gets executed first, causing a simple substitution failure, the compiler will discard this function template when computing the overload set and search for other viable overloads (SFINAE).
On the other hand, if substitution for the second function parameter occurs first, causing a hard error (e.g. because of a substitution failure in a non-immediate context), the entire compilation would fail.
QUESTION: Is there any guarantee on the order in which substitution will be performed for the function parameters and return types?
NOTE: This example seems to show that on all major compilers (VC11 was tested separately and gave identical results) substitution for the return type occurs before substitution for the parameter types.
[NOTE: This was not originally meant to be a self-answered question, but I happened to find out the solution while crafting the question]
Is there any guarantee on the order in which substitution will be performed for the function parameters and return types?
Not in the current standard.
However, this Defect Report (courtesy of Xeo) shows that this is indeed intended to be the case. Here is the proposed new wording for Paragraph 14.8.2/7 of the C++11 Standard (which has become part of the n3485 draft):
The substitution occurs in all types and expressions that are used in the function type and in template
parameter declarations. The expressions include not only constant expressions such as those that appear in
array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions)
inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds
in lexical order and stops when a condition that causes deduction to fail is encountered. [...]
As correctly pointed out by Nicol Bolas in the comments to the question, lexical order means that a trailing return type would be substituted after the parameter types, as shown in this live example.

What's the correct term for the '...' token?

Consider printf:
int printf ( const char * format, ... );
What are the terms used to describe the ... and the functions that use it? I've been calling it an ellipsis, but that's like calling & the "ampersand operator."
Variable length parameter list
Edit:
Or, if describing the function itself: Variadic function
Ellipsis notation (, ...) p202 "K+R The C Programming Language"
"Ellipsis" is in fact often the best term here. Sometimes we refer to "arguments passed using the ellipsis" (C++03 8.3.5p2). In the context of figuring out the best overloaded function, an argument can be said to "match the ellipsis" (C++03 13.3.2p2).
printf and other functions like it are often called "variadic functions".
Note: The coming C++0x Standard offers two different ways of declaring and implementing variadic functions (the va_arg way and the template way). But both involve the ellipsis token.
Ellipsis operator is the only term I have heard - it's rare enough (thankfully) that you don't need anything else!
This C++ draft specification refers to it simply as 'ellipsis' and sometimes with a definite or indefinite article, as 'an ellipsis' or 'the ellipsis'.
5.2.2 "Function call" section 6 contains:
A function can be declared to accept fewer arguments (by declaring
default arguments (8.3.6)) or more arguments (by using the ellipsis, ... 8.3.5)
than the number of parameters in the function definition (8.4).
8.3.5 "Functions" section 2 contains:
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.
8.3.6 section 4 contains sample code:
void g(int = 0, ...); // OK, ellipsis is not a parameter so it can follow
// a parameter with a default argument
Extra pedantry: section 13.3.3.1.3 ("Ellipsis conversion sequences") refers to "the ellipsis parameter specification". However, as stated in the sample code above, the ellipsis is not, strictly speaking, a parameter. 8.3.5 section 1 explains that, while the ellipsis appears in the parameter-declaration-clause, it follows the parameter-declaration-list.
In addition to "ellipsis" and "variadic function", one also sees the terms "vararg" and "varargs" thrown around. This appears to be an abbreviation for "variable argument list", judging by the language surrounding the (LEGACY) header <varargs.h> in POSIX.
Also, the principle reason that the term "ampersand operator" is not used is that the ampersand can represent either of two different operators, depending on the context, which would make the term ambiguous. This does not occur with the ellipsis; there is no other meaning assigned to it, so using the term "ellipsis" for the token "..." is not like using the term "ampersand operator" for the token "&".
Variadic
Martin and Demian are both right:
The three "." together form a ellipsis (On the Macintosh this is a single special character "...", but not usable for C++)
In C++ an ellipsis is used to define a Variable length parameter list