From what I have understood, declarations/initializations in C++ are statements with 'base type' followed by a comma separated list of declarators.
Consider the following declarations:
int i = 0, *const p = &i; // Legal, the so-called base type is 'int'. // i is an int while p is a const pointer to an int. int j = 0, const c = 2; // Error: C++ requires a type specifier for all declarations. // Intention was to declare j as an int and c an as const int. int *const p1 = nullptr, i1 = 0; // p1 is a const pointer to an int while i1 is just an int. int const j1 = 0, c1 = 2; // Both j1 and c1 are const int.
Is const int
a base type or a compound type?
From the error in the second declaration above, it seems to be a base type. If it is so, then what about the first declaration?
In other words, if the first statement is legal, why isn't the second one? Also, why does the behaviour differ among the third and fourth statements?
Declaration of a function provides the compiler with the name of the function, the number and type of arguments it takes, and its return type. For example, consider the following code, int add(int, int); Here, a function named add is declared with 2 arguments of type int and return type int.
In computer programming, a declaration is a language construct specifying identifier properties: it declares a word's (identifier's) meaning. Declarations are most commonly used for functions, variables, constants, and classes, but can also be used for other entities such as enumerations and type definitions.
Word forms: plural declarations. 1. countable noun. A declaration is an official announcement or statement. They will sign the declaration tomorrow.
A declaration establishes the names and characteristics of data objects used in a program. A definition allocates storage for data objects, and associates an identifier with that object. When you declare or define a type, no storage is allocated.
Good question, with a complicated answer. To really grasp this, you need to understand the internal structure of C++ declarations quite thoroughly.
(Note that in this answer, I will totally omit the existence of attributes to prevent overcomplication).
A declaration has two components: a sequence of specifiers, followed by a comma-separated list of init-declarators.
Specifiers are things like:
static
, extern
)virtual
, inline
)friend
, typedef
, constexpr
int
, short
)const
, volatile
)decltype
)The second part of a declaration are the comma-separated init-declarators. Each init-declarator consists of a sequence of declarators, optionally followed by an initialiser.
What declarators are:
i
in int i;
)*
, &
, &&
, pointer-to-member syntax)(int, char)
)[2][3]
)Notice that the declaration's structure is strict: first specifiers, then init-declarators (each being declarators optionally followed by an initialiser).
The rule is: specifiers apply to the entire declaration, while declarators apply only to the one init-declarator (to the one element of the comma-separated list).
Also notice above that a cv-qualifier can be used as both a specifier and a declarator. As a declarator, the grammar restricts them to only be used in the presence of pointers.
So, to handle the four declarations you have posted:
int i = 0, *const p = &i;
The specifier part contains just one specifier: int
. That is the part that all declarators will apply to.
There are two init-declarators: i = 0
and * const p = &i
.
The first one has one declarator, i
, and an initialiser = 0
. Since there is no type-modifying declarator, the type of i
is given by the specifiers, int
in this case.
The second init-declarator has three declarators: *
, const
, and p
. And an initialiser, = &i
.
The declarators *
and const
modify the base type to mean "constant pointer to the base type." The base type, given by specifiers, is int
, to the type of p
will be "constant pointer to int
."
int j = 0, const c = 2;
Again, one specifier: int
, and two init-declarators: j = 0
and const c = 2
.
For the second init-declarator, the declarators are const
and c
. As I mentioned, the grammar only allows cv-qualifiers as declarators if there is a pointer involved. That is not the case here, hence the error.
int *const p1 = nullptr, i1 = 0;
One specifier: int
, two init-declarators: * const p1 = nullptr
and i1 = 0
.
For the first init-declarator, the declarators are: *
, const
, and p1
. We already dealt with such an init-declarator (the second one in case 1). It adds the "constant pointer to base type" to the specifier-defined base type (which is still int
).
For the second init-declarator i1 = 0
, it's obvious. No type modifications, use the specifier(s) as-is. So i1
becomes an int
.
int const j1 = 0, c1 = 2;
Here, we have a fundamentally different situation from the preceding three. We have two specifiers: int
and const
. And then two init-declarators, j1 = 0
and c1 = 2
.
None of these init-declarators have any type-modifying declarators in them, so they both use the type from the specifiers, which is const int
.
This is specified in [dcl.dcl] and [dcl.decl] as part of the simple-declaration
* and boils down to differences between the branches in ptr-declarator
:
declaration-seq: declaration declaration: block-declaration block-declaration: simple-declaration simple-declaration: decl-specifier-seqopt init-declarator-listopt ; ---- decl-specifier-seq: decl-specifier decl-specifier-seq decl-specifier: type-specifier ← mentioned in your error type-specifier: trailing-type-specifier trailing-type-specifier: simple-type-specifier cv-qualifier ---- init-declarator-list: init-declarator init-declarator-list , init-declarator init-declarator: declarator initializeropt declarator: ptr-declarator ptr-declarator: ← here is the "switch" noptr-declarator ptr-operator ptr-declarator ptr-operator: ← allows const * cv-qualifier-seq opt cv-qualifier: const volatile noptr-declarator: ← does not allow const declarator-id declarator-id: id-expression
The important fork in the rules is in ptr-declarator
:
ptr-declarator: noptr-declarator ptr-operator ptr-declarator
Essentially, noptr-declarator
in your context is an id-expression
only. It may not contain any cv-qualifier
, but qualified or unqualified ids. However, a ptr-operator
may contain a cv-qualifier
.
This indicates that your first statement is perfectly valid, since your second init-declarator
*const p = &i;
is a ptr-declarator
of form ptr-operator ptr-declarator
with ptr-operator
being * const
in this case and ptr-declarator
being a unqualified identifier.
Your second statement isn't legal because it is not a valid ptr-operator
:
const c = 2
A ptr-operator
must start with *
, &
, &&
or a nested name specifier followed by *
. Since const c
does not start with either of those tokens, we consider const c
as noptr-declarator
, which does not allow const
here.
Also, why the behaviour differs among 3rd and 4th statements?
Because int
is the type-specifier
, and the *
is part of the init-declarator
,
*const p1
declares a constant pointer.
However, in int const
, we have a decl-specifier-seq
of two decl-specifier
, int
(a simple-type-specifier
) and const
(a cv-qualifier
), see trailing-type-specifier
. Therefore both form one declaration specifier.
* Note: I've omitted all alternatives which cannot be applied here and simplified some rules. Refer to section 7 "Declarations" and section 8 "Declarators" of C++11 (n3337) for more information.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With