48 votes

Déclarations en C++

D'après ce que j'ai compris, les déclarations/initialisations en C++ sont des déclarations avec 'base type' suivi d'une liste de déclarateurs séparés par des virgules.

Considérons les déclarations suivantes :

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.

Est const int un type de base ou un type composé ?

D'après l'erreur dans la deuxième déclaration ci-dessus, il semble s'agir d'un type de base. Si c'est le cas, qu'en est-il de la première déclaration ?

En d'autres termes, si la première déclaration est légale, pourquoi la seconde ne l'est-elle pas ? De même, pourquoi le comportement diffère-t-il entre la troisième et la quatrième déclaration ?

42voto

Angew Points 53063

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 .

7voto

Zeta Points 34033

Ce paramètre est spécifié dans [dcl.dcl] et [dcl.decl] comme faisant partie de l'interface utilisateur. simple-declaration * et se résume à des différences entre les branches en 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

La bifurcation importante dans les règles se trouve dans ptr-declarator :

ptr-declarator:
    noptr-declarator
    ptr-operator ptr-declarator

Essentiellement, noptr-declarator dans votre contexte est un id-expression uniquement. Il ne peut pas contenir de cv-qualifier mais des identifiants qualifiés ou non. Toutefois, un ptr-operator peut contenir un cv-qualifier .

Cela indique que votre première affirmation est parfaitement valide, puisque votre deuxième init-declarator

 *const p = &i;

est un ptr-declarator de la forme ptr-operator ptr-declarator con ptr-operator être * const dans ce cas et ptr-declarator étant un identifiant non qualifié.

Votre deuxième déclaration n'est pas légale parce qu'elle n'est pas valide. ptr-operator :

 const c = 2

A ptr-operator doit commencer par * , & , && ou un spécificateur de nom imbriqué suivi de * . Puisque const c ne commence pas par l'un ou l'autre de ces jetons, nous considérons que const c como noptr-declarator qui ne permet pas const ici.

De même, pourquoi le comportement diffère-t-il entre la troisième et la quatrième déclaration ?

Parce que int est le type-specifier et le * fait partie de la init-declarator ,

*const p1

déclare un pointeur constant.

Cependant, en int const nous avons un decl-specifier-seq de deux decl-specifier , int (a simple-type-specifier ) et const (a cv-qualifier ), voir trailing-type-specifier . Les deux forment donc un seul spécificateur de déclaration.


* Note : J'ai omis toutes les alternatives qui ne peuvent pas être appliquées ici et j'ai simplifié certaines règles. Voir la section 7 "Déclarations" et la section 8 "Déclarateurs" de C++11 ( <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf">n3337 </a>) pour plus d'informations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X