From what I have understood, declarations/initializations in C++ are statements with \'base type\' followed by a comma separated list of declarators.
Consider the follow
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.