Inclure une énumération dans une autre

Le problème exposé dans ce sujet a été résolu.

Bonjour. :)

Je possède dans mon appli les 2 énumérations suivantes :

1
2
3
enum { BOUTON1, BOUTON2, NB_BOUTONS };

enum { BOUTON1, BOUTON2, CLIQUABLE, NB_CLIQUABLES };

A noter que j'ai des noms différents, ici c'est juste pour faire comprendre la conception. :D Seulement à chaque fois que je rajoute un bouton, il me faut le rajouter aux deux énumérations, j'aurais voulu une solution plus sure. J'ai donc pensé à utiliser des valeurs explicites :

1
2
3
enum { BOUTON1, BOUTON2, NB_BOUTONS };

enum { CLIQUABLE = NB_BOUTONS, NB_CLIQUABLES };

De ce fait les valeurs sont incrémentées à partir de NB_BOUTONS (si je ne me trompe pas :euh: ). Seulement, j'ai utilisé un type pour la seconde énumération :

1
enum Cliaquable_t { BOUTON1, BOUTON2, CLIQUABLE, NB_CLIQUABLES };

Du coup les boutons ne sont plus considérés comme cliquables. Si vous avez des idées, des propositions.

Merci !! ;)

+0 -0

Salut,

Présenté comme cela, c'est assez abstrait. Pourquoi as-tu besoin de deux énumérations et non d'une seule ? Qu'entends-tu par « du coup les boutons ne sont plus considérés comme cliquables » ? Pourrais-tu décrire ta question de manière un peu plus précise ?

De ce fait les valeurs sont incrémentées à partir de NB_BOUTONS (si je ne me trompe pas :euh: ).

Maluna34

C'est exact.

+0 -0

Pour une appli, je dispose de différents boutons. J'ai donc un tableau d'images (mes boutons) :

1
std::vector<sf::CircleShape> boutons(NB_BOUTONS);

De ce fait je peux boucler dessus lors de la création des sprites (puisqu'ils possèdent la même texture), ou encore lors de l'affichage :

1
2
for (int i = 0; i < NB_BOUTONS; i++)
  window.draw(boutons[i]);

De manière plus générale, j'ai aussi une énumération avec l'ensemble de mes objets (des images) qui sont cliquables. J'ai donc les boutons + d'autres éléments. De même je peux boucler dessus.

Ici, dès que j'ajoute un objet à l'énumération, les valeurs NB_BOUTONS ou encore NB_CLIAQUABLES sont automatiquement mises à jour, pas besoin de changer l'affichage ou le chargement des sprites. Seulement mes boutons sont des objets cliquables, donc l'enum cliquables comprend la 1e. Et je cherche des moyens de l'expliciter (inclure une énum dans une autre). La solution que j'ai proposé semble fonctionner :

1
2
3
enum { BOUTON1, BOUTON2, NB_BOUTONS };

enum { CLIQUABLE = NB_BOUTONS, NB_CLIQUABLES };

Malheureusement, dans le cas où on a des types, ça ne fonctionne plus. :(

+0 -0

Il t'est vraiment nécessaire de recourir à deux types distincts ? Parce qu'il est parfaitement possible de fusionner les deux énumérations comme suit :

1
enum { BOUTON1, BOUTON2, NB_BOUTONS, CLIQUABLE = NB_BOUTONS, NB_CLIQUABLES };

Sinon, une autre solution est de ne pas définir de types et de recourir plutôt au préprocesseur, mais dans ce cas tu dois adapter les valeurs toi-même.

1
2
3
4
5
#define BOUTON1         0x00
#define BOUTON2         0x01
#define NB_BOUTONS      (BOUTON2 + 1)
#define CLIQUABLE       NB_BOUTONS
#define NB_CLIQUABLES   (CLIQUABLE + 1)

EDIT: suppression du script awk.

+1 -3

Sérieusement Taurre ? On parle C++ là. On évite autant que faire ce peu les macros, et surtout dans des cas tel que celui ci où c'est pas justifié.

Avec une classe bouton qui contient les propriétés qui vont bien ( cliquable ou non, texte et/ou image, position) + signal/slot, c'est plié.

+0 -0

Sérieusement Taurre ? On parle C++ là. On évite autant que faire ce peu les macros, et surtout dans des cas tel que celui ci où c'est pas justifié.

Davidbrcz

J'étais sérieux, oui. D'une part, une énumération n'a pas réellement d'avantages comparée à des macroconstantes si ce n'est la fixation de valeurs de manière automatique et la présence de symbole en phase de test, d'autre part, qu'est-ce qui ne justifie pas leur utilisation dans ce cas ci (et, par la même occasion, les remplace de manière adéquate) ?

+0 -0
  • Les defines n'ont aucun type, aucune existence propre, aucun scope et sont remplacé sauvagement et silencieusement par le préprocesseur. Ajoutons que tu n'a aucune consistance globale (chaque TU peut avoir sa valeur du define) puisqu'il suffit de coller un undef pour nuker le define. Bref, pas besoin d'épiloguer…

  • Les enums n'ont aucun scope, un tout typage faible à cause de la conversion implicite. Ca s'arrange en C++11 avec les enum fortement typées.

Si tu veux des constantes dans ton code, ce sont des variables globales constantes typées (éventuellement template, C++14 powa) dans un namespace dédié. Y'a meme std::integral_constant qui wrap une constante pour offrir des traits dessus

La bonne solution propre, c'est de refaire une primitive bouton qui encapsule tout ca et qui va déclencher un signal quand un événement arrive. Avec std::function, c'est trivial.

+0 -0

Les defines n'ont aucun type, aucune existence propre, aucun scope et sont remplacé sauvagement et silencieusement par le préprocesseur.

Davidbrcz

En effet, mais c'est le principe même d'un préprocesseur de ne pas avoir de liens avec le langage qu'il manipule. Également, si les macros n'ont pas de type, les expressions par lesquelles elles sont remplacées, elles, en ont un. Quant à la portée, les macros en ont une : elles sont utilisables du lieu de leur déclaration jusqu'à la fin du fichier sauf si elles sont supprimées auparavant.

Ajoutons que tu n'a aucune consistance globale (chaque TU peut avoir sa valeur du define) puisqu'il suffit de coller un undef pour nuker le define. Bref, pas besoin d'épiloguer…

Davidbrcz

Au contraire, épiloguons ! :)
Qu'entends-tu par : « consistance globale » ?

  • Les enums n'ont aucun scope, un tout typage faible à cause de la conversion implicite. Ca s'arrange en C++11 avec les enum fortement typées.

Davidbrcz

Les types énumérés, de même que les constantes énumérées, ont une portée qui est déterminée de la même manière que pour les autres identificateurs. Sinon, oui, le typage est faible dans la mesure ou un type énuméré n'est qu'un alias pour un type entier.

Si tu veux des constantes dans ton code, ce sont des variables globales constantes typées (éventuellement template, C++14 powa) dans un namespace dédié. Y'a meme std::integral_constant qui wrap une constante pour offrir des traits dessus.

Davidbrcz

Pour ma part, je reste d'avis que ce n'est pas spécialement une bonne solution : cela réserve de la mémoire inutilement sans apporter de gain(s) réel(s).

+0 -0

Lu'!

Si tu veux des constantes dans ton code, ce sont des variables globales constantes typées (éventuellement template, C++14 powa) dans un namespace dédié. Y'a meme std::integral_constant qui wrap une constante pour offrir des traits dessus.

Davidbrcz

Pour ma part, je reste d'avis que ce n'est pas spécialement une bonne solution : cela réserve de la mémoire inutilement sans apporter de gain(s) réel(s).

Taurre

Non, ça ne réserve pas la moindre mémoire si ton compilateur est raisonnablement optimisant. Par ailleurs, comme dit précédemment, le typage c'est important. En l'occurrence, il n'est pas là pour faire chier et le typage plus fort du C++ par rapport au C est justement un des arguments qui jouent en faveur du premier.

En effet, mais c'est le principe même d'un préprocesseur de ne pas avoir de liens avec le langage qu'il manipule. Également, si les macros n'ont pas de type, les expressions par lesquelles elles sont remplacées, elles, en ont un.

C'est typiquement pour ca qu'il ne faut pas l'utiliser. Tu te prives de tous les mécanismes de sécurité, de portée, de typage.

Quant à la portée, les macros en ont une : elles sont utilisables du lieu de leur déclaration jusqu'à la fin du fichier sauf si elles sont supprimées auparavant.

Ouais, c'est limite rien que sur le principe de pouvoir faire disparaître un symbole qui devrait être constant.

Au contraire, épiloguons ! :)
Qu'entends-tu par : « consistance globale » ?

Consistance globale de la valeur d'un symbole à travers le sous projet. EN vertue du principe de moindre surprise, une fois symbole défini, surtout une constante, tu es en droit de t'attende à ce qu'il ne change plus. Cette garantie basique, tu ne l'a pas avec des macros.

En effet rien ne te garantie que le define soit le même d'une TU à une autre. Tu peux définir un symbole comme un double dans un fichier, une string dans un autre et ne pas le définir dans un troisième.

J'ai deja vu des bugs subtils à cause d'un changement d'ordre d'inclusion dans les headers car y'avait un undef+nouveau define sur un symbole et qu'on perdait en précision avec ce nouveau define, ce qui donnait des résultats totalement faux…

Avec une variable globale namespacée constante, une fois celle ci définie, bah tu ne peux plus la cacher et tu manges une erreur si tu essayes de définir un symbole avec le même nom ou de redéfinir le même symbole.

Les types énumérés, de même que les constantes énumérées, ont une portée qui est déterminée de la même manière que pour les autres identificateurs.

Ya aucune portée dans les enums…

1
2
enum color{orange};
enum fruit{orange}; //erreur

Pour ma part, je reste d'avis que ce n'est pas spécialement une bonne solution : cela réserve de la mémoire inutilement sans apporter de gain(s) réel(s).

Non. Le compilateur va optimiser anyway. Pas contre, ca offre des garanties de typage (et les opérations qui vont avec) fortes appréciables. Notons aussi que tu te prives de tous les constantes générées par des fonctions constexpr si tu utilises des macros au lieu des variables, ce qui serait dommage vu l'avancé que ca représente.

+0 -0

Consistance globale de la valeur d'un symbole à travers le sous projet. EN vertue du principe de moindre surprise, une fois symbole défini, surtout une constante, tu es en droit de t'attende à ce qu'il ne change plus. Cette garantie basique, tu ne l'a pas avec des macros.

En effet rien ne te garantie que le define soit le même d'une TU à une autre. Tu peux définir un symbole comme un double dans un fichier, une string dans un autre et ne pas le définir dans un troisième.

Davidbrcz

Ce n'est pas une variable globale qui t'avanceras plus de ce point de vue :

— autre.h

1
extern int var;

— autre.c

1
int var = 10;

— main.c

1
2
3
4
5
6
7
8
9
static int var = 20;
#include "autre.h"


int
main(void)
{
        return var; /* 20 */
}

Ya aucune portée dans les enums…

1
2
enum color{orange};
enum fruit{orange}; //erreur

Davidbrcz

Tu parles de la notion d'espaces de noms ou de celle de portée ? Les types énumérés et les constantes énumérées ont tous les deux une portée. Cela se traduit très bien avec l'exemple ci-dessous qui mélange portée et masquage.

1
2
3
4
5
6
7
8
9
enum fruit { pomme = 10, poire, orange, banane  };

int
main(void)
{
        enum fruit { pomme = 20, poire, orange, banane, citron };

        return orange; /* 22 */
}

En revanche, les constantes énumérées font effectivement partie de l'espace de noms principal, partagé avec les identificateurs d'objet, de fonction et de définition de type.

1
2
3
4
5
6
7
8
int
main(void)
{
        enum fruit { pomme, poire, orange, banane  };
        int pomme; /* redéfinition dans la même portée */

        return 0;
}

Non, ça ne réserve pas la moindre mémoire si ton compilateur est raisonnablement optimisant.

Ksass`Peuk

Non. Le compilateur va optimiser anyway.

Davidbrcz

Anyway le fait que la variable n'est pas propre à un module (translation unit) ? Je serais curieux de voir ça.

Notons aussi que tu te prives de tous les constantes générées par des fonctions constexpr si tu utilises des macros au lieu des variables, ce qui serait dommage vu l'avancé que ca représente.

Davidbrcz

Pourrais-tu préciser ta pensée ? Je ne vois pas en quoi l'utilisation de macroconstante empêche de déclarer une fonction avec le spécificateur constexpr, surtout quand je lis ceci :

The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time.

Par définition, si les macroconstantes sont remplacées par des constantes, le résultat est évaluable à la compilation.

+0 -0

Dépend ce que t'entends, c'est résolu au link.

Ksass`Peuk

Ce n'est pas l'impression que j'ai :

— main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>

extern int var;


int
main(void)
{
    std::cout << var << std::endl;
    return 0;
}

— autre.cpp

1
int var = 20;
1
2
3
$ g++ -O2 *.cpp
$ nm a.out | grep var
0000000000600c58 D var

Quant à l'utilisation du qualificateur const ou du spécificateur constexpr, ils ne permettent pas d'utiliser la variable entre différents fichiers :

1
2
3
4
$ g++ -O2 *.cpp
/tmp/ccahYUfw.o: In function `main':
main.cpp:(.text.startup+0x6): undefined reference to `var'
collect2: error: ld returned 1 exit status
+0 -0

Evidemment le premier cas ne peut pas être optimisé : on a une variable qui est susceptible de changer, on ne peut pas la virer.

Pour la définition dans un module externe effectivement, le compilateur ne fait pas l'optimisation complète, je suis surpris qu'il n'y aie pas ce type d'optimisation au moment du link, ça semble trivial autant techniquement que pour assurer le sens sémantique*.

*autant pour moi, en faisant des tests, j'ai eu l'idée d'utiliser std::array et on obtient le même problème que pour l'utilisation d'un paramètre de fonction const : au moment de la compilation du fichier la valeur de l'élément n'est pas connue et le typage ne peut donc pas être réalisé.

Par contre, en restant sur le problème d'avoir des valeurs constantes typées (c'était un peu le sujet quand même), si on a besoin de constantes définies globalement, on passera par un header et dans ce cas, l'optimisation est (heureusement) complète. Grosso-modo on obtient le comportement de réécriture des define/macro avec le type-checking et le test de redéfinition en plus (que du positif quoi).

PS : pour que le link fonctionne sur le cas const, il faut également déclarer var extern dans autre pour la faire sortir du scope.

Par contre, en restant sur le problème d'avoir des valeurs constantes typées (c'était un peu le sujet quand même), si on a besoin de constantes définies globalement, on passera par un header et dans ce cas, l'optimisation est (heureusement) complète. Grosso-modo on obtient le comportement de réécriture des define/macro avec le type-checking et le test de redéfinition en plus (que du positif quoi).

Ksass`Peuk

Est-ce que tu peux me donner un exemple qui fonctionne ? Je ne visualise pas bien ce que tu veux dire en lisant ton explication. Également, je ne vois pas pourquoi tu parles de vérification de type. Je veux dire, la macroconstante sera remplacée par une expression typée. Dès lors, qu'est-ce qu'une variable apporte de plus ?

PS : pour que le link fonctionne sur le cas const, il faut également déclarer var extern dans autre pour la faire sortir du scope.

Ksass`Peuk

Aah ! Effectivement, la compilation fonctionne dans ce cas, merci.
M'enfin, c'est assez étrange étant donné qu'une définition en dehors de tout bloc a normalement une liaison externe (external linkage) par défaut.

+0 -0

Est-ce que tu peux me donner un exemple qui fonctionne ? Je ne visualise pas bien ce que tu veux dire en lisant ton explication.

Taurre

truc.hh

1
2
3
4
5
6
#ifndef _TRUC
#define _TRUC

int const var{ 42 };

#endif

truc.cpp

1
2
3
4
5
6
#include "truc.hh"

int main(void)
{
  return var;
}

Résultat :

1
2
3
4
5
6
$ g++ -o truc truc.cpp -O0 -std=c++11
$ nm truc | grep var
0000000000400594 r _ZL3var
$ g++ -o truc truc.cpp -O2 -std=c++11
$ nm truc | grep var
$

EDIT - Petite note : bien entendu la même chose avec une variable mutable entraînera des erreurs de compilation si le fichier est inclut plusieurs fois.

Également, je ne vois pas pourquoi tu parles de vérification de type. Je veux dire, la macroconstante sera remplacée par une expression typée. Dès lors, qu'est-ce qu'une variable apporte de plus ?

Taurre

truc.hh

1
2
3
4
5
6
7
#ifndef _TRUC
#define _TRUC

//#define var 42
unsigned const var{42};

#endif

truc.cpp :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
#include "truc.hh"

void foo(unsigned){
  std::cout<<"c'est un unsigned"<<std::endl;
}
void foo(int){
  std::cout<<"c'est un int"<<std::endl;
}

int main(void)
{
  foo(var);
}

Avec le define :

1
2
$ ./truc 
c'est un int

Avec la const var :

1
2
$ ./truc 
c'est un unsigned

M'enfin, c'est assez étrange étant donné qu'une définition en dehors de tout bloc a normalement une liaison externe (external linkage) par défaut

Taurre

Pas saisit la raison non plus …

Les deux exemples ne me paraîssent pas convaincant :

  • pour le premier, tu présentes le cas d'une variable constante propre à un fichier (truc.cpp) ;
  • pour le second, tu n'attribues pas le bon type à ta constante entière, la définition de la macroconstante devrait être #define var 10U.
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte