Bonne question, avec une réponse compliquée. Pour vraiment la saisir, vous devez comprendre la structure interne des déclarations C++ de manière assez approfondie.
(Notez que dans cette réponse, j'omettrai totalement l'existence des attributs pour éviter toute complication excessive).
Une déclaration comporte deux éléments : une séquence de spécificateurs, suivi d'une liste d'éléments séparés par des virgules. init-déclarateurs .
Les spécificateurs sont des choses comme :
- les spécificateurs de classe de stockage (par exemple
static
, extern
)
- les spécificateurs de fonction (par exemple
virtual
, inline
)
-
friend
, typedef
, constexpr
-
spécificateurs de type qui comprennent :
- des spécificateurs de type simples (par exemple
int
, short
)
-
cv-qualificateurs (
const
, volatile
)
- d'autres choses (par exemple
decltype
)
La deuxième partie d'une déclaration est constituée des déclarateurs d'init séparés par des virgules. Chaque init-déclarateur consiste en une séquence de déclarateurs, éventuellement suivi d'un initialisation.
Ce que sont les déclarateurs :
- (par exemple, l'identifiant
i
en int i;
)
- les opérateurs de type pointeur (
*
, &
, &&
(syntaxe du pointeur au membre)
- syntaxe des paramètres de fonction (par exemple
(int, char)
)
- syntaxe des tableaux (par exemple
[2][3]
)
-
cv-qualificateurs si ceux-ci suivent un déclarateur de pointeur.
Remarquez que la structure de la déclaration est stricte : d'abord des spécificateurs, puis des déclarateurs d'initialisation (chacun étant des déclarateurs suivis éventuellement d'un initialisateur).
La règle est la suivante : les spécificateurs s'appliquent à l'ensemble de la déclaration, tandis que les déclarateurs ne s'appliquent qu'à l'unique init-déclarateur (à l'unique élément de la liste séparée par des virgules).
Notez également ci-dessus qu'un cv-qualifier peut être utilisé à la fois comme un spécificateur et un déclarateur. En tant que déclarateur, la grammaire les restreint à être utilisés uniquement en présence de pointeurs.
Donc, pour traiter les quatre déclarations que vous avez postées :
1
int i = 0, *const p = &i;
La partie spécificateur ne contient qu'un seul spécificateur : int
. C'est la partie à laquelle s'appliquent tous les déclarateurs.
Il y a deux déclarateurs d'init : i = 0
y * const p = &i
.
Le premier a un déclarateur, i
et un initialisateur = 0
. Comme il n'y a pas de déclarateur modificateur de type, le type de i
est donné par les spécificateurs, int
dans ce cas.
Le deuxième init-declarator a trois déclarateurs : *
, const
et p
. Et un initialisateur, = &i
.
Les déclarateurs *
y const
modifier le type de base pour signifier "pointeur constant vers le type de base". Le type de base, donné par les spécificateurs, est int
au type de p
sera "un pointeur constant vers int
."
2
int j = 0, const c = 2;
Encore une fois, un seul spécificateur : int
et deux init-déclarateurs : j = 0
y const c = 2
.
Pour le deuxième init-déclarateur, les déclarateurs sont const
y c
. Comme je l'ai mentionné, la grammaire n'autorise les cv-qualificateurs comme déclarateurs que si un pointeur est impliqué. Ce n'est pas le cas ici, d'où l'erreur.
3
int *const p1 = nullptr, i1 = 0;
Un prescripteur : int
, deux déclarateurs init : * const p1 = nullptr
y i1 = 0
.
Pour le premier init-déclarateur, les déclarants sont : *
, const
et p1
. Nous avons déjà traité d'un tel init-déclarateur (le second en cas de 1 ). Il ajoute le "pointeur constant au type de base" au type de base défini par le spécificateur (qui est encore int
).
Pour le deuxième init-déclarateur i1 = 0
c'est évident. Pas de modification de type, utilisez le(s) spécificateur(s) tel(s) qu'il(s) est (sont). Donc i1
devient un int
.
4
int const j1 = 0, c1 = 2;
Ici, nous avons une situation fondamentalement différente des trois précédentes. Nous avons deux spécificateurs : int
y const
. Et puis deux init-déclarateurs, j1 = 0
y c1 = 2
.
Aucun de ces déclarateurs init ne contient de déclarateur modificateur de type, ils utilisent donc tous deux le type des spécificateurs, à savoir const int
.