In the following simplified program, I try to create a std::vector
object sized according to an enumerator value:
#include <vector>
enum class E { Count };
int main() {
std::vector<int*> vec( size_t( E::Count ) );
}
It works fine in GCC, but other compilers complain.
error: parameter declarator cannot be qualified
6 | std::vector<int*> vec( size_t( E::Count ) );
error C2751: 'E::Count': the name of a function parameter cannot be qualified
Online demo: https://gcc.godbolt.org/z/sP3MfPGb9
Which implementation is correct here?
::
in parameter names is a semantic error)This answer references the C++20 Standard (N4868), unless noted otherwise.
The "most vexing parse" is defined by the following sections in the standard:
The important section in this case is: (emphasis mine)
9.3.3 Declarators - Ambiguity resolution [dcl.ambig.res]
(1) The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in [stmt.ambig] can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in [stmt.ambig], the resolution is to consider any construct that could possibly be a declaration a declaration.
Sidenote:
To be fair this paragraph is (imho) rather cryptic to read and hard to understand.
CWG2620 (which was accepted as a defect report into C++23) reworded the relevant section to make it clearer how the disambiguation should be done.
The important part is that if a given declaration could be either a function declaration or an object declaration, then it will always be a function declaration.
That is due to the statement "any construct that could possibly be a declaration [is] a declaration" — that means that any (sub-)part of the statement that could potentially be a declaration must be considered as a declaration.
In this case the relevant bit is what comes after vec
— size_t(E::Count)
— because that part could either be interpreted as one of the following:
vec
with one parameter of type size_t
named E::Count
, returning std::vector<int*>
.std::vector<int>* vec(size_t E::Count);
std::vector<int*> vec ( size_t ( E::Count ) ) ;
// \- decl-specifier-seq -/ | \- simple-type-specifier -/ | \- id-expression -/ | | |
// | | \- decl-specifier-seq --/ \----- declarator ---/ | |
// | | \------------ parameter-declaration ------------/ | |
// | | \------- parameter-declaration-clause --------/ | |
// | \----------------------- declarator ----------------------/ |
// \--------------------------------- simple-declaration ----------------------------------/
(Yes, E::Count
is semantically not a valid name for a function parameter, we'll get to that later)vec
of type std::vector<int*>
that gets initialized using the initializer size_t (E::Count)
.std::vector<int*> vec((size_t)E::Count);
std::vector<int*> vec ( size_t (E::Count) ) ;
// \- decl-specifier-seq -/ | | \- initializer-list -/ | |
// | | \----- initializer -----/ |
// | \------- declarator -------/ |
// \----------------- simple-declaration ------------------/
Sidenote:
I skipped over several steps in the grammar in the examples above to keep them short.
See [gram] for the full grammar.
So size_t(E::Count)
could either be interpreted as a parameter-declaration of a declarator or as the initializer of a declarator - it's ambiguous.
=> Any construct (size_t(E::Count)
in this case) that could be a declaration must be a declaration (parameter-declaration is considered a "declaration").
=> The compiler must treat size_t(E::Count)
as a parameter-declaration so vec
will be a function declaration.
This is always a problem when the initializer of a declarator could also be interpreted as one (or more) parameter-declarations.
Declarations (like a parameter-declaration) must always be preferred - so function declarations will always win over object declarations when there is ambiguity.
CWG2620 (merged into C++23) clarifies this section a bit and explicitly mentions that parameter declarations are considered declarations: (emphasis mine)
[C++23] 9.3.3 Declarators - Ambiguity resolution [dcl.ambig.res]
(1) The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in [stmt.ambig] can also occur in the context of a declaration. In that context, the choice is between an object declaration with a function-style cast as the initializer and a declaration involving a function declarator with a redundant set of parentheses around a parameter name. Just as for the ambiguities mentioned in [stmt.ambig], the resolution is to consider any construct, such as the potential parameter declaration, that could possibly be a declaration to be a declaration.
size_t(E::Count)
a valid parameter-declaration?The C++ Standard consists out of 2 different types of rules: Syntactic and Semantic (see Difference between Syntax and Semantics).
The disambiguation for declarations and statements happens very early on during compilation, so only syntactic rules are considered, NOT semantic ones:
8.9 Statements - Ambiguity resolution [stmt.ambig]
(3) The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation. Class templates are instantiated as necessary to determine if a qualified name is a type-name. Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration. If, during parsing, a name in a template parameter is bound differently than it would be bound during a trial parse, the program is ill-formed. No diagnostic is required.
So function declarations like these
std::vector<int*> vec(size_t (E::Count));
std::vector<int*> vec(size_t E::Count);
are syntactically valid C++ according to the grammar (which is all what matters when disambiguation happens)
Only after parsing are semantic rules considered (like checking if function parameter names are actually identifiers)
=> After parsing std::vector<int*> vec(size_t E::Count);
will be ill-formed (because it is a function declaration with a parameter-declaration that has a qualified-id as declarator-id)
Sidenote:
Full grammatical breakdown of std::vector<int*> vec(size_t(E::Count));
: (refer to [gram])
E::Count
is a ptr-declarator -> noptr-declarator -> declarator-id -> id-expression -> qualified-id -> nested-name-specifier [ E::
] unqualified-id [ Count
]
E::
is a nested-name-specifier -> type-name :: -> enum-name -> identifierCount
is an unqualified-id -> identifier(E::Count)
is a declarator -> ptr-declarator -> noptr-declarator
[ using the ( ptr-declarator )
form, ptr-declarator is E::Count
]
size_t(E::Count)
is a parameter-declaration -> decl-specifier-seq [size_t
] declarator [ E::Count
]
size_t
is a decl-specifier-seq -> decl-specifier -> defining-type-specifier -> type-specifier -> simple-type-specifier -> type-name -> typedef-name -> identifier(size_t(E::Count))
is a parameters-and-qualifiers -> ( parameter-declaration-clause ) -> parameter-declaration-list -> parameter-declaration [ size_t(E::Count)
]
vec(size_t(E::Count))
is a init-declarator -> declarator -> ptr-declarator -> noptr-declarator -> noptr-declarator [ vec
] parameters-and-qualifiers [ (size_t(E::Count))
]
vec
is a noptr-declarator -> declarator-id -> id-expression -> unqualified-id -> identifierstd::vector<int*> vec(size_t(E::Count));
is a statement -> declaration-statement -> block-declaration -> simple-declaration -> decl-specifier-seq [ std::vector<int*>
] init-declarator-list [ vec(size_t(E::Count))
]
std::vector<int*>
is a decl-specifier-seq -> decl-specifier -> defining-type-specifier -> simple-type-specifier -> nested-name-specifier [ std::
] type-name [ vector<int*>
]
std::
is a nested-name-specifier -> namespace-name :: -> identifiervector<int*>
is a type-name -> class-name -> simple-template-id -> template-name [ identifier vector
] < template-argument-list [ int*
] >
-> int*
is a template-argument-list -> template-argument -> type-id -> type-specifier-seq [ int
] abstract-declarator [ *
]
int
is a type-specifier-seq -> type-specifier -> simple-type-specifier -> int*
is an abstract-declarator -> ptr-abstract-declarator -> ptr-operator -> *