Inclure une énumération dans une autre

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

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
  • pour le premier, tu présentes le cas d'une variable constante propre à un fichier (truc.cpp) ;

Taurre

Histoire d'être sûr : on parle bien du cas de la déclaration en header hein. Parce que tu peux l'utiliser dans 12 fichiers si tu veux :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//truc.hh
#ifndef _TRUC
#define _TRUC

int const var{ 42 };

#endif

//foo.hh
#ifndef _FOO
#define _FOO

void foo();

#endif

//foo.cpp :
#include <iostream>
#include "truc.hh"
#include "foo.hh"

void foo(){
  std::cout<<var + 23<<std::endl;
}

//bar.hh
#ifndef _BAR
#define _BAR

void bar();

#endif

//bar.cpp
#include <iostream>
#include "truc.hh"
#include "bar.hh"

void bar(){
  std::cout<<var + var<<std::endl;
}

//main.cpp
#include <iostream>
#include "foo.hh"
#include "bar.hh"
#include "truc.hh"

int main(){
  std::cout<<var<<std::endl;
  foo();
  bar();
}

Résultat :

1
2
3
4
5
6
7
$ g++ -o main *.cpp -O2 -std=c++11
$ ./main 
42
65
84
$ nm ./main | grep var
$
  • 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.

Taurre

J'ai pris l'exemple pour qu'il soit rapide et simple, mais rien n'empêche de calculer cette valeur à partir de deux entiers signés :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>

static unsigned const var{ -12*(-14) };
//#define var -12*(-14)

void foo(unsigned){
  std::cout<<"unsigned"<<std::endl;
}

void foo(int){
  std::cout<<"int"<<std::endl;
}

int main(){
  foo(var);
}

Si les deux entiers sont négatifs et tiennent dans un int, le compilateur ne râlera pas (même avec tous les warnings)* pour l'expression const. Et on aura bien le résultat voulu pour foo. En revanche, si on veut le bon comportement pour le define, il faut se trimballer un cast supplémentaire : autant écrire directement une variable const.

Par ailleurs, l'indication que tu donnes (utiliser la bonne littérale), ne fonctionne comme qui dirait que pour les types à littérales, si on veut le faire avec ses propres définitions de type, c'est mort.

PS : * en revanche, si ça ne rentre pas, il nous enverra bouler, même en mode par défaut :

1
2
3
4
5
#include <iostream>

static unsigned const var{ 12*(-14) };

int main(){}
1
2
3
$ g++ -o main main.cpp -std=c++11 -O2
main.cpp:3:37: warning: narrowing conversion of ‘-168’ from ‘int’ to ‘const unsigned int’ inside { } [-Wnarrowing]
 static unsigned const var{ 12*(-14) };

Histoire d'être sûr : on parle bien du cas de la déclaration en header hein. Parce que tu peux l'utiliser dans 12 fichiers si tu veux :

Ksass`Peuk

Tout à fait, mais le soucis, c'est qu'il ne s'agit pas d'une variable partagée dans ce cas :

— autre.cpp

1
int const var = 10;

— main.cpp

1
2
3
4
5
6
7
int const var = 20;

int
main(void)
{
        return var;
}
1
2
3
4
$ g++ -O0 *.cpp
$ nm a.out | grep var
000000000040064c r _ZL3var
0000000000400650 r _ZL3var

dès lors, l'optimisation peut se faire à la compilation (et non à l'édition des liens).

J'ai pris l'exemple pour qu'il soit rapide et simple, mais rien n'empêche de calculer cette valeur à partir de deux entiers signés :

1
2
static unsigned const var{ -12*(-14) };
//#define var -12*(-14)

Ksass`Peuk

Mon argument reste le même : mauvais choix de type. Si l'on veut une expression non signée, il est nécessaire qu'au moins un des deux opérandes soit non signé.

Par ailleurs, l'indication que tu donnes (utiliser la bonne littérale), ne fonctionne comme qui dirait que pour les types à littérales, si on veut le faire avec ses propres définitions de type, c'est mort.

Ksass`Peuk

En effet, mais bon, je ne perçois pas vraiment cela comme un inconvénient (le type doit aussi être indiquer pour la déclaration de la variable constante).

+0 -0

Histoire d'être sûr : on parle bien du cas de la déclaration en header hein. Parce que tu peux l'utiliser dans 12 fichiers si tu veux :

Ksass`Peuk

Tout à fait, mais le soucis, c'est qu'il ne s'agit pas d'une variable partagée dans ce cas :

— autre.cpp

1
int const var = 10;

— main.cpp

1
2
3
4
5
6
7
int const var = 20;

int
main(void)
{
        return var;
}
1
2
3
4
$ g++ -O0 *.cpp
$ nm a.out | grep var
000000000040064c r _ZL3var
0000000000400650 r _ZL3var

dès lors, l'optimisation peut se faire à la compilation (et non à l'édition des liens).

Taurre

Alors effectivement en O0, ça n'optimise pas (c'est le concept), j'en dirais pas autant de 02 :

1
2
3
$ g++ -o main main.cpp autre.cpp -O2 -std=c++11
$ nm main | grep var
$

(Evidemment, j'ai pris le même code que celui que tu indiques …)

Mais encore une fois, ici ce n'est pas une variable de configuration globale : elle est liée au scope. Ce qui fait qu'on aurait exactement le même comportement avec un define. Evidemment, deux variables de même nom dans des scopes différents ne sont pas les mêmes variables et peuvent avoir des valeurs différentes (rien de neuf sous le soleil). Mais si tu passes ne serait-ce que l'une des deux dans le scope global (ajout de extern ici), le compilateur t'enverra gentiment bouler.

J'ai pris l'exemple pour qu'il soit rapide et simple, mais rien n'empêche de calculer cette valeur à partir de deux entiers signés :

1
2
static unsigned const var{ -12*(-14) };
//#define var -12*(-14)

Ksass`Peuk

Mon argument reste le même : mauvais choix de type. Si l'on veut une expression non signée, il est nécessaire qu'au moins un des deux opérandes soit non signé.

Taurre

En vertu de quoi ? Si je suis capable de donner la garantie que la valeur que je donne à une variable est bien dans le range voulu, c'est définit et sémantiquement complètement valide, en particulier si je veux être sûr par la suite que l'élément en question est bien positif (typiquement, si c'est une taille).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int const e1{ -42 };
int const e2{ -12 };

static_assert(0 <= e1*e2 && e1*e2 <= std::numeric_limits<int>::max(), 
              "cet assert est inutile, le compilateur le vérifie déjà");
unsigned const var{ e1*e2 };

int main(){
  return var;
}

Par ailleurs, l'indication que tu donnes (utiliser la bonne littérale), ne fonctionne comme qui dirait que pour les types à littérales, si on veut le faire avec ses propres définitions de type, c'est mort.

Ksass`Peuk

En effet, mais bon, je ne perçois pas vraiment cela comme un inconvénient (le type doit aussi être indiquer pour la déclaration de la variable constante).

Taurre

L'inconvénient n'est pas dans le nommage explicite mais dans la limitation aux types primitifs, si on veut une variable const de type complexe, on n'a pas de littérale pour ça :

1
2
3
4
5
6
7
8
#include <iostream>
#include <complex>

std::complex<double> const cplx{ 3., 4. };

int main(){
  return std::norm(cplx);
}

Outre le fait qu'avec un define, ce serait casse gueule (ndlr : pas ici, en revanche pour les tableaux …), encore une fois le compilateur sera optimisant :

1
2
3
4
5
6
$ g++ -o main main.cpp -O0 -std=c++11
$ nm ./main | grep cplx
0000000000400da0 r _ZL4cplx
$ g++ -o main main.cpp -O2 -std=c++11
$ nm ./main | grep cplx
$

Pour le reste, en C++, c'est 0 avantage. Inversement, l'utilisation de variables const définies dans le bon scope apporte tout ce dont on a besoin pour avoir le même niveau d'optimisation que les define avec un typage plus fort et surtout valable pour tous les types y compris les non-primitifs.

Mais encore une fois, ici ce n'est pas une variable de configuration globale : elle est liée au scope. Ce qui fait qu'on aurait exactement le même comportement avec un define. Evidemment, deux variables de même nom dans des scopes différents ne sont pas les mêmes variables et peuvent avoir des valeurs différentes (rien de neuf sous le soleil). Mais si tu passes ne serait-ce que l'une des deux dans le scope global (ajout de extern ici), le compilateur t'enverra gentiment bouler.

Ksass`Peuk

Au temps pour moi, je ne sais pas pourquoi j'ai continué mon raisonnement avec l'idée de variable partagée… Donc oui, cette technique donne le même résultat qu'une macroconstante. J'avoue cependant qu'en tant que programmeur C, je ne peux m'empêcher de trouver l'inculsion de définitions via un en-tête assez crade. Mais bon, cela reste deux langages différents.

En vertu de quoi ?

Ksass`Peuk

Mon argument portait sur la macroconstante, par sur la variable. La définition correcte devrait être celle ci-dessous dans le cas où l'on souhaite obtenir une expression non-signée.

1
#define var ((-12)*(-14)*1U)

L'inconvénient n'est pas dans le nommage explicite mais dans la limitation aux types primitifs, si on veut une variable const de type complexe, on n'a pas de littérale pour ça :

Ksass`Peuk

Si, il y a les littéraux aggrégats :

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

#define CPLX ((std::complex<double>) { 3., 4. })

int
main(void)
{
        return std::norm(CPLX);
}

Mais bon, ce n'est pas l'idéal, en effet…

+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