Dans la plupart des langages de programmation généralistes, on trouve la notion de type énuméré W pour permettre au codeur de grouper des constantes dans une liste pour former un ensemble de valeurs à effectif réduit, par exemple :

  • les jours de la semaines (lundi, mardi…),
  • les mois de l'année (janvier, février…),
  • les enseignes des cartes à jouer (carreau, pique…),
  • les points cardinaux géographiques (nord, est…),
  • les codes des bandes de couleur des résistors (noir, marron, rouge, orange…),
  • les modes de fonctionnement d'un système (arrêt, marche avant, marche arrière…), etc.

Dans un programme source, on pourrait coder ces valeurs comme de simples nombres. Par exemple, dans le cas des jours de la semaine, on se baserait sur la convention :
1 pour lundi, 2 pour mardi, etc.
Mais cette pratique serait difficilement lisible, même si elle était documentée par un commentaire au début du programme. En effet, il faudrait avoir en permanence en tête la correspondance entre chaque jour et le nombre qui le code pour comprendre la manipulation des données de type jour, comme par exemple :
int nomDuJour = 6; // etc.

Pour bien faire, on pourrait déjà définir chaque nom de valeur comme l'identificateur d'une constante déclarée, par exemple :
const int LUNDI = 1, MARDI = 2, // etc.
ce qui permettrait par la suite de déclarer et initialiser de façon une variable entière pour coder le nom du jour d'une date, par exemple :
int nomDuJour = SAMEDI;
En employant directement ces identificateurs de constantes dans les expressions, on gagnerait en lisibilité ; on n'aurait pas besoin de mémoriser de correspondance entre des noms et des valeurs numériques de codage.

En programmation, le concept de type énuméré apporte justement une syntaxe spécifique pour faciliter cette approche. Il permet de regrouper en une seule déclaration le codage de toutes les valeurs constantes qu'une variable peut prendre. Il évite d'avoir recours à une suite de déclarations individuelles de constantes comme ci‑dessus.

En langages C et C++, les types énumérés sont des types dérivés de types entiers. D'un point de vue pédagogique, il est donc pertinent de les étudier avec les types élémentaires, à la suite des types entiers.

Néanmoins, il faut avoir conscience dès le départ que, dans ces deux langages, l'emploi des types énumérés recèle quelques difficultés :

  • D'abord, il existe deux possibilités de déclaration des données de types énumérés :
    • soit via un type anonyme, ce qui est commode lorsqu'une seule variable est requise ;
    • soit via un type nommé, cette pratique étant recommandée dans le cas général.
    Et à ces deux possibilités se combinent celles offertes pour toute déclaration de donnée(s) :
    • déclarer une ou plusieurs données dans la même instruction (cf. chap. C2-III ) ;
    • initialiser ou non les variables déclarées, etc. (cf. chap. C2-III )
  • Par ailleurs – et surtout – on va voir que l'affectation des données déclarées de types énumérés est traitée de manières différentes selon le langage employé.
    • En C, la valeur affectée peut être de n'importe quel type élémentaire, le compilateur effectuant une conversion implicite pour rendre l'affectation possible ;
    • En C++, la valeur affectée ne peut être que du même type énuméré que celui la donnée ; si ce n'est pas le cas, il faut procéder à une conversion explicite pour rendre l'affectation possible.

Incontournables en programmation, mais loin d'être simples d'emploi, les types énumérés méritent un chapitre qui leur soit exclusivement consacré. Dans cet objectif, on abordera les deux thèmes essentiels :

  • la déclaration des types énumérés et de leur données, avec les différentes syntaxes possibles selon le langage adopté, C ou C++ ;
  • la manipulation des données de types énumérés avec :
    • en préliminaire, la question de la compatibilité ou non avec les types entiers ;
    • ensuite, les opérations les plus usuelles (affectation, calculs arithmétiques, comparaison) ;
    et pour chacun de ces aspects, là encore, de nombreuses différences selon le langage adopté, C ou C++.

Toutes ces subtilités peuvent sembler pénibles dans un premier temps, mais elles se justifient par l'évolution que représente le langage C++ en termes de sécurité des programmes par rapport au langage C. Et quoi qu'il en soit, ces subtilités doivent être maîtrisées pour pouvoir bien employer les types énumérés.

Déclaration des types énumérés et de leurs données

Dans cette section, par souci de simplification :

  • la syntaxe est toujours présentée pour la déclaration d'une seule variable ; il est facile d'extrapoler à une syntaxe séquentielle en appliquant les principes généraux de la déclaration des données en langages C et C++ (cf. chap. C2‑III ) ;
  • les contraintes auxquelles doivent répondre les expressions d'initialisation des données sont passées sous silence. Elles sont décrites à la section suivante.

Syntaxe déclarative avec un type anonyme

En langages C et C++, on peut déclarer une variable de type énuméré anonyme par une instruction de la forme :

enum {
  nom de constante 0, // 1ᵉ constante ( = 0)
  nom de constante 1, // 2ᵉ constante ( = 1)
   ⋮
  nom de constante n  // (n+1)ᵉ constante ( = n)
} nom de la variable  
[valeur initiale
 ];

où la valeur initiale optionnelle est une expression à valeur entière.

Dans la syntaxe ci‑dessus, les noms de constantes listés entre accolades sont également des identificateurs déclarés dans l'espace de visibilité de la déclaration (cf. chap. C2‑III ). Ces constantes :

  • prennent implicitement les valeurs entières par défaut 0, 1, 2… respectivement dans l'ordre de la liste ;
  • ont leurs valeurs implicitement encodées par défaut dans le type sous‑jacent int (en anglais, underlying type) ; ces valeurs constituent a priori l'ensemble restrictif des valeurs que la variable de type énuméré est censée prendre ;
  • ont chacune, dans le reste du code, le « statut privilégié » d'expression constante entière (contrairement à des constantes déclarées individuellement – cf. chap. C2‑II ).

En règle générale de bonne pratiques, on code ces noms de constantes en majuscules.

Pour un programme de gestion d'un feux de circulation, on peut coder l'instruction :

enum {GREEN, YELLOW, RED} trafficLightColor = GREEN;

qui déclare :

  • les trois constantes entières GREEN, YELLOW et RED valant respectivement 0, 1 et 2, sachant qu'elles sont encodées dans le type sous‑jacent int ;
  • la variable énumérée trafficLightColor, initialisée à la valeur GREEN (donc, 0).

À la suite de cette déclaration, et dans tout son espace de visibilité, on peut alors affecter directement à trafficLightColor l'une des trois constantes listées GREEN, YELLOW et RED dont les valeurs sont les seules que la variable est censée prendre (si le programme est correctement codé).

Lorsque les identificateurs des constantes entières sont courts et peu nombreux, il est possible de formater l'instruction de déclaration d'une variable de type énuméré sur une seule ligne comme dans l'exemple ci‑dessus.

Sinon, il est préférable de procéder sur plusieurs lignes comme dans la syntaxe générique proposée supra  ou dans la variante ci‑dessous :

enum {GREEN, 
      YELLOW, 
      RED
} trafficLightColor = GREEN;

Notion de type anonyme

Dans la forme de déclaration supra , le type énuméré de la variable déclarée est dit anonyme car il n'a pas d'identificateur déclaré.

Le recours à un type anonyme présente :

  • l'avantage de simplifier la déclaration d'une (voir plusieurs) variable(s) énumérée(s) dans la même instruction ;
  • l'inconvénient de limiter à une seule instruction la déclaration de données de ce type ; en effet, toute nouvelle instruction dans le même espace de visibilité entraînerait un conflit sur les identificateurs des constantes avec la première instruction.

En règle générale de bonnes pratiques, il est préférable d'employer un type nommé.

Syntaxes déclaratives avec un type nommé

Forme usuelle de déclaration d'un type énuméré

En langages C et C++, pour déclarer une variable de type énuméré nommé, il faut préalablement déclarer son type. Usuellement, on procède dans une instruction séparée de la forme :

enum nom du type {
  nom de constante 0, // 1ᵉ constante ( = 0)
  nom de constante 1, // 2ᵉ constante ( = 1)
   ⋮
  nom de constante n  // (n+1)ᵉ constante ( = n)
};

Le nom du type déclaré peut ensuite être employé comme élément d'un descripteur de type, notamment pour déclarer des données de ce type énuméré.

Remarquons que dans cette syntaxe, le nom du type est codé non pas après la liste des constantes, mais avant – et juste après le mot‑clef enum.

Par ailleurs, comme dans la déclaration d'une variable de type énuméré anonyme, la déclaration d'un type énuméré seul a pour effet de déclarer aussi les constantes entières listées entre accolades, avec les mêmes règles implicites d'initialisation (cf. supra ).

En reprenant l'exemple du programme de gestion de feux de circulation, on peut préalablement déclarer un type énuméré Color comme ci‑dessous :

enum Color {GREEN, YELLOW, RED};

Grâce à cette déclaration, l'identificateur Color permet ensuite de déclarer une ou plusieurs données du type qu'il représente (cf. infra ).

De plus, on peut utiliser les identificateurs GREEN, YELLOW et RED pour composer des expressions de toutes sortes.

Syntaxe alternative en langage C

En langage C, il existe une syntaxe alternative pour la déclaration d'un type énuméré, via le mot‑clef typedef (cf. chap C3‑I ) :

typedef enum {
  nom de constante 0, // 1ᵉ constante ( = 0)
  nom de constante 1, // 2ᵉ constante ( = 1)
   ⋮
  nom de constante n  // (n+1)ᵉ constante ( = n)
} nom du type;

Remarquons que dans cette syntaxe, le nom du type est codé après la liste des constantes et non pas avant (et il faut ne pas confondre avec la déclaration d'une variable d'un type anonyme – cf. cf. supra ).

L'avantage de recourir au mot‑clef typedef est qu'une telle déclaration définit le nom du type comme un descripteur de type complet. Cela permet d'omettre le rappel du mot‑clef enum dans toute déclaration ultérieure de données de ce type (cf. ci‑après).

Déclaration de données d'un type énuméré nommé

On déclare une variable d'un type énuméré préalablement déclaré par une instruction de la forme :
enum nom du type nom de la variable [= valeur initiale];
où la valeur initiale optionnelle est une expression à valeur entière.

On déclare une constante de ce même type en faisant précéder l'instruction ci‑dessus par le mot‑clef const.

Le codage de la valeur initiale est alors en principe obligatoire, l'affectation étant définitive.

Dans la déclaration d'une donnée d'un type énuméré nommé, le mot‑clef enum est :

  • facultatif si le programme est compilé en C++ ;
  • omis si le type énuméré a été déclaré avec le mot‑clef typedef.

Toujours dans le contexte d'un programme de gestion de feux de circulation, suite à la déclaration supra du type énuméré Color, les instructions ci‑dessous :

enum Color mainTrafficLightColor     = GREEN;
enum Color secondTrafficLightColor   = RED;
const enum Color DEFAULT_LIGHT_COLOR = YELLOW;

déclarent respectivement deux variables et une constante du même type Color.

Avec l'utilisation du mot‑clef typedef, les instructions déclaratives ci‑dessus se codent :

typedef enum {GREEN, YELLOW, RED} Color;

Color mainTrafficLightColor     = GREEN;
Color secondTrafficLightColor   = RED;
const Color DEFAULT_LIGHT_COLOR = YELLOW;

Remarque. Ici, les déclarations sont codées dans le même bloc, mais elles pourraient très bien l'être chacune dans des blocs différents, tant que ces blocs restent dans l'espace de visibilité de la déclaration du type Color.

Convention pour les identificateurs de types

Lorsqu'un programme ne requiert qu'une seule variable d'un type énuméré, il est d'usage d'employer presque le même identificateur pour les deux – leur seule différence portant sur la lettre initiale :

  • majuscule pour le nom du type,
  • minuscule pour le nom de la variable.

Pour un programme simple de calendrier, on peut coder les deux instructions ci‑dessous :

enum DayOfWeek {
  MONDAY, TUESDAY, WEDNESDAY,
  THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
enum DayOfWeek dayOfWeek = TUESDAY;

où la variable dayOfWeek (avec minuscule initiale) est la seule définie du type DayOfWeek (avec majuscule initiale).

Attention aux conflits d'identificateurs

Attention, lorsqu'on emploie des types énumérés, qu'ils soient anonymes ou nommé, tout identificateur de constante entière figurant dans la liste d'un type énuméré n'est pas utilisable dans tout autre déclaration dans son espace de visibilité. Il s'agit d'un conflit d'identificateur que le compilateur détecte immanquablement.

En prolongement de l'exemple pour le programme de calendrier, on ne peut pas coder :

enum DayOfWeek {
  MONDAY, TUESDAY, WEDNESDAY,
  THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
enum WeekEnd {SATURDAY, SUNDAY}; 
// Error: identifiers already declared!

au motif que les identificateurs SATURDAY et SUNDAY sont déjà utilisés pour déclarer le type DayOfWeek.

Déclaration explicite des valeurs de constantes énumérées

Dans la déclaration d'un type énuméré (anonyme ou nommé), on peut coder, pour chaque constante entière listée, une valeur explicite qui soit différente de celle attribuée implicitement par défaut.

Il suffit de procéder comme pour les initialisations dans une déclaration séquentielle de données (cf. chap. C2‑III ), en codant une affectation de la forme :
, nom constante k = valeur explicite ,

Attention, dans cette forme syntaxique, toute valeur explicite doit être :

  • en langage C, une expression constante entière (cf. chap. C2‑II ) ;
  • en langage C++, une expression constante (cf. chap. C2‑II ) de type entier.

Et à ce sujet, cf. la remarque faite au chap. C2‑V .

Par ailleurs, toute constante entière non explicitement affectée prend alors la valeur incrémentée de 1 de la constante précédente dans la liste.

Pour un programme de jeu de Belote W, les valeurs des cartes à l'atout peuvent être codées par le type énuméré Atout codé ci‑dessous avec des valeurs explicites pour la plupart des constantes entières listées :

enum Atout {
  SEPT  =  0, HUIT = 0, 
  DAME  =  3, ROI, 
  DIX   = 10, AS, 
  NEUF  = 14, 
  VALET = 20
};

Implicitement :

  • la constante ROI prend la valeur 4 (la valeur de la constante précédente DAME incrémentée de 1) ;
  • la constante AS prend la valeur 11 (la valeur de la constante précédente DIX incrémentée de 1).

Toutefois, pour une meilleure lisibilité, il est préférable de coder une valeur explicite exhaustivement pour chaque constante, comme ci‑dessous :

enum Atout {
  SEPT  =  0, HUIT =  0, 
  DAME  =  3, ROI  =  4, 
  DIX   = 10, AS   = 11, 
  NEUF  = 14, 
  VALET = 20
};

Remarque. À travers cet exemple, on voit qu'il est tout à fait possible, dans la déclaration d'un type énuméré ou d'une donnée de ce type, d'affecter la même valeur explicite à plusieurs constantes (c'est le cas des constantes SEPT et HUIT qui ont toutes les deux la valeur 0).

Type d'encodage des constantes énumérées

On a vu supra  que les valeurs des constantes entières listées dans la déclaration d'un type énuméré sont encodées par défaut dans le type sous‑jacent standard int.

Toutefois, le type sous‑jacent est automatiquement ajusté lorsque certaines de ces constantes sont affectées explicitement de valeurs hors de l'étendue du type int. Cet ajustement n'est pas normalisé, il est laissé à la liberté de l'implémentation.

Pour un programme de calcul scientifique, on peut coder un type énuméré avec des constantes définissant des préfixes de puissances de dix comme ci‑dessous :

enum Prefix {
  KILO = 1000, 
  MEGA = 1000000, 
  GIGA = 1000000000, 
  TERA = 1000000000000
};

Ici, la valeur explicite pour la constante TERA est tellement grande qu'elle forcément supérieure à INT_MAX (cf. chap. C3‑II ), et ce quelle que soit l'implémentation du programme .

En conséquence, les valeurs :

  • de toutes les constantes entières listées dans le type Prefix,
  • ainsi que de toutes les variables déclarées ultérieurement de type Prefix,

seront encodées dans un type sous‑jacent dont l'étendue inclut la valeur de TERA, à savoir a priori le type long long.

Spécification d'un type sous‑jacent (en C++)

En langage C++, le type sous‑jacent d'encodage des valeurs des constantes entières listées et des données d'un type énuméré peut être spécifié par champ optionnel de descripteur de type délimité entre le séparateur : et l'accolade ouvrante { de la liste des constantes.

  • Dans le cas d'un type énuméré anonyme, la syntaxe de déclaration est de la forme :
  • enum : descripteur de type {
      nom de constante 0, 
    
  • Dans le cas d'un type énuméré nommé, la syntaxe de déclaration est de la forme :
  • enum nom du type : descripteur de type {
      nom de constante 0, 
    

En reprenant l'exemple du programme de gestion de feux de circulation, si la machine cible est une carte à microcontrôleur disposant de peu de mémoire, il peut être judicieux de coder la déclaration du type énuméré nommé Color comme ci‑dessous :

enum Color : uint8_t {GREEN, YELLOW, RED};

c'est‑à‑dire en imposant uint8_t comme type sous‑jacent car il est le plus petit possible – et ici largement suffisant puisqu'on n'a que 3 valeurs.

Manipulation des données de types énumérés

Pour savoir commenter manipuler des données de types énumérés et des constantes entières listées dans la déclaration d'un tel type, il est essentiel d'aborder préalablement la question de la compatibilité de ces constantes et données avec celles des autres données de types élémentaires.

Compatibilité avec les autres types de données

On rappelle que les valeurs des constantes entières listées et des données déclarées d'un type énuméré sont encodées dans un type entier sous‑jacent (underlying type).

Ce type sous‑jacent ne doit pas être confondu avec le type énuméré lui‑même. En effet, ce dernier n'est pas pas un type élémentaire en langage C++ ; c'est un type dérivé du type sous‑jacent.

Formation d'expressions en général (sauf affectation)

En langages C et C++, on peut a priori former des expressions avec des données de types énumérés, les constantes entières listées dans leurs déclaration, ainsi que des données d'autres types élémentaires (constantes littérales, données déclarées), et en utilisant tous les opérateurs du langages, des appels de fonctions, etc.

Toutefois, on verra infra que le langage C++ impose des limitations lorsque l'expression compose une affectation .

Lors de l'évaluation d'une telle expression, les valeurs des données de types énumérés et des constantes entières sont implicitement converties (cf. chap. C2‑IV  et C3‑VI ) dans leur type sous‑jacent.

Et comme pour toute expression, le type final de l'expression dépend de la composition de l'expression et de l'application des règles usuelles de typage (cf. chap. C3‑I ). Mais dans tout les cas, on peut être certain du fait qu'il ne s'agit jamais d'un type énuméré, du fait des conversions implicites que l'on vient de mentionner.

Pour le programme du jeu de Belote, suite à la déclaration du type énuméré Atout (cf. supra ), on peut déclarer une constante nommée TOTAL_POINTS_ATOUT qui totalise la somme des points de toutes les cartes à l'atout :

const int TOTAL_POINTS_ATOUT = SEPT  + HUIT + NEUF + DIX +
                               VALET + DAME + ROI   + AS;

En C comme en C++, l'expression d'initialisation dans cette déclaration est compilable, même sans recours à une conversion explicite.

Après la déclaration supra  de la variable dayOfWeek pour le programme de calendrier, et si n est une variable déclarée de type entier, alors l'expression ci‑dessous est compilable :
1 + (dayOfWeek + n) % (SUNDAY + 1)
Elle donne la valeur numérique du nom du n‑ième jour après celui mémorisé dans dayOfWeek, avec la convention Monday = 1, Tuesday = 2, etc. (Pour mémoire, le type DayOfWeek est déclaré avec une numérotation implicite de ses constantes entières, donc MONDAY vaut 0, TUESDAY vaut 1, etc.)

On peut vérifier le caractère cyclique des valeurs prises par cette expression codée à la ligne nº 12 du programme de test ci‑dessous.

#include <stdio.h>

enum DayOfWeek {
  MONDAY, TUESDAY, WEDNESDAY,
  THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
enum DayOfWeek dayOfWeek = SUNDAY;

int main(void)
{
  for (int n = 1; n <= 14; n++) {
    printf("%d ", 1 + (dayOfWeek + n) % (SUNDAY + 1));
  }
  printf("\n");
  return 0;
}

Sur le terminal d'exécution, on obtient la sortie standard suivante :

1 2 3 4 5 6 7 1 2 3 4 5 6 7

où le numéro affiché suit un cycle répétitif de 1 à 7.

Formations d'expressions constantes entières (en langage C)

Parce qu'elle en ont elles‑même le statut, les constantes entières listées dans la déclaration d'un type énuméré peuvent être composées avec tous les opérateurs du langage C et des constantes littérales pour former des expressions constantes entières (cf. chap. C2‑II ).

Ces constantes entières peuvent donc être employées pour coder notamment :

  • les étiquettes de cas d'une bifurcation multiple switch (cf. chap. C2‑V ) ;
  • le nombre d'éléments d'un tableau, aussi bien dans une déclaration de type que de donnée (cf. chap. C5‑III ) ;

Pour le programme de calendrier évoqué supra , on peut afficher le nom du jour dans n'importe quelle langue, en testant chaque valeur possible de la variable dayOfWeek via la bifurcation multiple codée ci‑dessous :

  switch (dayOfWeek) {
    case MONDAY : 
      printf("lundi");
    break; 
    case TUESDAY : 
      printf("mardi");
    break; 
    case WEDNESDAY : 
      // ...
  }

Dans l'exemple ci‑dessus, on peut légitimement trouver que la solution est « lourde ». Toutefois, il faut savoir qu'il n'existe aucun moyen direct pour afficher à l'écran ou plus généralement récupérer dans une chaîne de caractères les identificateurs des constantes listées dans la déclaration d'un type énuméré.

En effet, comme tous les identificateurs, ce sont des éléments de langage de haut niveau qui sont numérisés sous forme d'adresses dans le code exécutable. D'une manière ou d'une autre, il est incontournable d'associer une chaîne de caractère à chaque identificateur (ce qui ouvre d'ailleurs un éventail de possibilités).

Néanmoins, cette association peut se coder d'une manière plus efficace que par le biais d'une structure de contrôle comme ci‑dessus : on peut aussi employer un tableau de chaînes de caractères dont chaque élément est indexé par la même valeur numérique qu'une constante du type énuméré. Mais ces aspects ne seront étudiés que dans la partie C5 du module…

Affectation d'une valeur à une donnée de type énuméré

Les considérations qui suivent sont valables :

  • aussi bien pour l'initialisation d'une donnée dans le cadre d'une déclaration (cf. chap C2‑III ) ;
  • que pour une opération d'affectation d'une variable déjà déclarée (cf. chap C2‑IV ).

Pour ne pas alourdir inutilement le cours, on ne parlera donc que d'affectation.

En langage C

En langage C, on peut coder une affectation = (cf. chap C2‑IV ) sur une donnée déclarée d'un type énuméré en codant la r‑value dans n'importe quel type élémentaire, grâce aux conversions implicites opérées par le compilateur.

De même, les opérateurs à affectation composée (++ -- etc. – cf. chap C2‑IV ) sont applicables sans restriction aux variables de type énumérées.

En langage C, on peut donc affecter à une donnée de type énuméré n'importe quelle valeur entière de son type sous‑jacent, même si elle n'est égale à aucune des constantes entières listées dans la déclaration de son type (et avec tous les risques d'erreurs d'exécution que cette possibilité comporte).

Bien évidemment, il n'est pas question de céder à cette facilité. En règle absolue de bonne pratique, toute affectation d'une valeur constante à une donnée énumérée doit être codée exclusivement avec une constante entière listée dans la déclaration de son type.

Pour le programme de gestion de feux de circulation présenté supra , pour imposer une couleur particulière au feu principal, on pourrait maladroitement coder :

  mainTrafficLightColor = 2; // Not easy to read!

alors que la seule solution propre est :

  mainTrafficLightColor = RED; // cristal clear!

Ce dont il faut avoir conscience, c'est qu'une instruction absurde comme :

  mainTrafficLightColor = -10; // nonsense!!

est malheureusement parfaitement compilable en langage C.

Toujours pour le programme de gestion de feux de circulation, pour faire évoluer le feu principal à la couleur suivante en codant l'instruction :

  mainTrafficLightColor = (mainTrafficLightColor + 1) % (RED + 1);

Remarque. L'opérateur % permettant le retour à la valeur 0 (c'est‑à‑dire GREEN) dès que la variable mainTrafficLightColor dépasse la valeur RED.

La longue instruction ci‑dessus aurait pu aussi être codée en deux instructions plus courtes :

  mainTrafficLightColor++;          // next color
  mainTrafficLightColor %= RED + 1; // after RED, back to GREEN

En langage C++

En langage C++, l'opérateur d'affectation = s'applique aux données déclarées d'un type énuméré seulement si la r‑value est du même type énuméré que celui de la r‑value – le compilateur n'effectuant aucune conversion implicite si le type est différent.

Quant aux opérateurs à affectation composée (++ -- etc. – cf. chap C2‑IV ), ils ne sont pas applicables aux variables de type énumérées, puisqu'ils mettent en œuvre des opérations hétérogènes en termes de typage.

Cette contrainte de typage homogène, qui est conforme à la règle de bonne pratique énoncée supra pour le langage C, peut néanmoins être contournée que via une conversion explicite, en employant l'identificateur du type énuméré comme un opérateur unaire (cf. chap C3‑VI ).

En requérant ainsi une opération « inhabituelle » de la part du codeur, le compilateur lui permet de prendre conscience qu'il code une instruction non conforme aux usages et qu'il faut faire preuve de vigilance.

Toujours pour le programme de gestion de feux de circulation, l'instruction malpropre ci‑dessous pour imposer une couleur particulière au feu principal :

  mainTrafficLightColor = 2; // not OK in C++

provoque heureusement une erreur de compilation avec un message du genre :

  main.cpp:15:29: error: invalid conversion from ‘int’ to ‘main()::Color’ [-fpermissive]

En revanche, avec une conversion explicite comme :

  mainTrafficLightColor = Color(2); // OK, even if not good!

l'instruction devient compilable – sachant qu'ici, elle n'est pas satisfaisante en termes de lisibilité (encore une fois, pourquoi utiliser des valeurs numériques alors qu'on dispose des identificateurs des constantes entières ?) Mais l'intérêt de cette possibilité d'affectation via une conversion explicite devient manifeste lorsque la valeur à affecter est celle prise par une expression variable (cf. ci‑après).

Plus généralement, ce codage d'affectation fonctionne avec n'importe quelle valeur entière de son type sous‑jacent prise par l'opérande de l'opérateur de conversion Color, y compris pour celles qui ne correspondent à aucune des constantes listées dans la déclaration du type Color. Ainsi, une instruction absurde comme :

  mainTrafficLightColor = Color(-10); // nonsense!!

reste malheureusement compilable en langage C++.

L'instruction pour faire passer le feu principal à la couleur suivante proposée supra  n'est pas compilable telle quelle en C++. Pour y remédier, il faut coder une conversions explicites comme ci‑dessous :

  mainTrafficLightColor = Color((mainTrafficLightColor + 1) % (RED + 1));

La contrainte de typage homogène des données de types énumérés ne doit donc pas être perçue comme un motif pour éviter leur emploi, puisque in fine, toutes les opérations sont codables, moyennant des conversions explicites. Le fait d'imposer au codeur une vigilance particulière au regard de l'hétérogénéité des expressions ne peut que renforcer la sécurité des programmes.

C'est d'ailleurs pourquoi il est recommandé d'appliquer les mêmes précautions de codage avec les types énumérés en langage C.