Good question, with a complicated answer. To understand this, you need to fully understand the internal structure of C ++ declarations.
(Note that in this answer, I completely omit the existence of attributes to prevent overuse).
A declaration has two components: a sequence of qualifiers, followed by a comma-separated list of declarator initiators.
Qualifiers:
- storage class specifiers (e.g.
static , extern ) - function specifiers (e.g.
virtual , inline ) friend , typedef , constexpr- type specifiers that include:
- simple type specifiers (e.g.
int , short ) - cv-qualifiers (
const , volatile ) - other things (e.g.
decltype )
The second part of the declaration is comma-separated init-declarators. Each init-declarator consists of a sequence of declarators, optionally followed by an initializer.
Which declarators:
- (e.g.
i in int i; ) - operators like pointers (
* , & , && , element pointer syntax) - function parameter syntax (e.g.
(int, char) ) - array syntax (for example,
[2][3] ) - cv-qualifiers if they follow a pointer pointer.
Please note that the structure of the declaration is strict: first qualifiers, then init-declarators (each of which is a declarator, and then an initiator).
This rule: qualifiers apply to the entire declaration, and declarators apply to only one init-declarator (to one element of a comma-separated list).
Also note that the cv qualifier can be used both as a qualifier and as a declarator. As a declarator, grammar restricts their use only with pointers.
So, to process the four ads you posted:
one
int i = 0, *const p = &i;
The specifier part contains only one qualifier: int . This is the part to which all declarators will apply.
There are two init declarators: i = 0 and * const p = &i .
The first has one declarator i and initializer = 0 . Since there is no type modification declarator, type i is specified by int qualifiers in this case.
The second init-declarator has three declarations: * , const and p . And initializer = &i .
The declarers * and const change the base type to a "constant pointer to the base type". The base type specified by the qualifiers is int ; type p will be a "constant pointer to int ".
2
int j = 0, const c = 2;
Again, one qualifier: 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. It is not, therefore, a mistake.
3
int *const p1 = nullptr, i1 = 0;
One specifier: int , two init-declarators: * const p1 = nullptr and i1 = 0 .
For the first init-declarator declarations: * , const and p1 . We have already considered such an init-declarator (the second in case 1 ). It adds a "constant pointer to the base type" to the specifier-defined base type (which is still int ).
For the second init-declarator i1 = 0 this is obvious. No type modifications, use specifiers (s) as is. So i1 becomes int .
four
int const j1 = 0, c1 = 2;
Here we have a fundamentally different situation from the previous three. We have two qualifiers: int and const . And then two init-declarators, j1 = 0 and c1 = 2 .
None of these initialization initiators have declarators that change the type in them, so they both use the type from the qualifiers, which is equal to const int .