OVH Cloud OVH Cloud

Specialisation de templates et types prédéfinis

29 réponses
Avatar
diego-olivier.fernandez-pons
Bonjour,

Comme mon code C/C++ doit tourner sur une multitude de plateformes
=E9tranges, je red=E9finis les types pr=E9d=E9finis (bool, int, float) avec
=E9ventuellement suivant des plateformes des cast affreux (typedef float
myInt) pour contourner les multiples bugs des plateformes/compilateurs
(machines 64 bits avec entiers 32 bits, machines avec entiers 16 bits,
etc.)

Mais du coup le syst=E8me de types est totalement perdu dans la
sp=E9cialisation des templates :

template <typename T> class Array {...} // tableau de pointeurs sur des
objets

// optimisations classiques avec les types natifs "unboxed"
template <> class Array <myBool> {...}
template <> class Array <myInt> {...}
template <> class Array <myFloat> {...}

Oui mais avec myInt =3D myFloat =3D float, le compilateur se perd.
Forc=E9ment.

Quelqu'un sait comment expliquer au compilateur que je veux qu'en
machine myInt =3D myFloat mais syntaxiquement myInt !=3D myFloat ?

Diego Olivier

10 réponses

1 2 3
Avatar
Fabien LE LEZ
On 7 Nov 2005 13:01:31 -0800,
:

Quelqu'un sait comment expliquer au compilateur que je veux qu'en
machine myInt = myFloat mais syntaxiquement myInt != myFloat ?


Il n'y a pas de solution simple à ce problème.
Regarde le thread "Notation hongroise", et en particulier
<news: et suivants.

Avatar
Marc Boyer
a écrit :
Comme mon code C/C++ doit tourner sur une multitude de plateformes
étranges, je redéfinis les types prédéfinis (bool, int, float) avec
éventuellement suivant des plateformes des cast affreux (typedef float
myInt) pour contourner les multiples bugs des plateformes/compilateurs
(machines 64 bits avec entiers 32 bits, machines avec entiers 16 bits,
etc.)

Mais du coup le système de types est totalement perdu dans la
spécialisation des templates :

template <typename T> class Array {...} // tableau de pointeurs sur des
objets

// optimisations classiques avec les types natifs "unboxed"
template <> class Array <myBool> {...}
template <> class Array <myInt> {...}
template <> class Array <myFloat> {...}

Oui mais avec myInt = myFloat = float, le compilateur se perd.
Forcément.

Quelqu'un sait comment expliquer au compilateur que je veux qu'en
machine myInt = myFloat mais syntaxiquement myInt != myFloat ?


Il y a la piste que t'a indiqué Fabien, qui consiste (si je
devine bien son intention) à faire non pas un typedef mais
une nouvelle classe.

typedef myInt class Truc<float,...>; // avec Truc un peu compliqué
typedef myFloat class Truc<float,...>; // avec Truc un peu compliqué

et tu obtiens deux classes vraiment différente (avec le
pb à gérer de savoir si tu veux pouvoir convertir myInt en
myFloat ou pas).

Une autre, ce serait simplement de jouer avec le préprocesseur.

#if !(MYFLOAT_ID == MYINT_ID && defined(ARRAY_INT)
template <> class Array <myFloat> {...}
#endif

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangereux
que prendre un boulevard dans le sens légal. À qui la faute ?

Avatar
Fabien LE LEZ
On Tue, 8 Nov 2005 07:57:31 +0000 (UTC), Marc Boyer
:

(avec le
pb à gérer de savoir si tu veux pouvoir convertir myInt en
myFloat ou pas).


J'ose espérer que non, du moins pas par conversion implicite.

Une conversion explicite est toujours possible :
myFloat IntToFloat (myInt);

Avatar
kanze
wrote:

Comme mon code C/C++ doit tourner sur une multitude de
plateformes étranges, je redéfinis les types prédéfinis (bool,
int, float) avec éventuellement suivant des plateformes des
cast affreux (typedef float myInt) pour contourner les
multiples bugs des plateformes/compilateurs (machines 64 bits
avec entiers 32 bits, machines avec entiers 16 bits, etc.)


J'avoue ne pas en comprendre l'intérêt. Les bogues de
compilateur qui concerne les types de base sont assez rare. (Et
un typedef n'est pas un cast.)

Mais du coup le système de types est totalement perdu dans la
spécialisation des templates :

template <typename T> class Array {...} // tableau de pointeurs sur des
objets


Pourquoi des pointeurs ?

// optimisations classiques avec les types natifs "unboxed"
template <> class Array <myBool> {...}
template <> class Array <myInt> {...}
template <> class Array <myFloat> {...}


On ne « boxe » pas les types en C++. On ne « boxe » que quand le
langage exige un pointeur, et qu'on n'en a pas. En C++, si on
veut des valeurs, on se sert des valeurs. Si on veut des
pointeurs, on se sert des pointeurs. Le langage en supporte les
deux très bien.

Oui mais avec myInt = myFloat = float, le compilateur se perd.
Forcément.


Il n'y a pas que le compilateur. Moi aussi, je m'y perdrais.

Quelqu'un sait comment expliquer au compilateur que je veux
qu'en machine myInt = myFloat mais syntaxiquement myInt !=
myFloat ?


Tu as toujours le droit d'en faire autant de types que tu veux,
au moyen des classes. Et de leur en donner des opérateurs que tu
veux.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Marc Boyer
Le 08-11-2005, Fabien LE LEZ a écrit :
On Tue, 8 Nov 2005 07:57:31 +0000 (UTC), Marc Boyer
:

(avec le
pb à gérer de savoir si tu veux pouvoir convertir myInt en
myFloat ou pas).


J'ose espérer que non, du moins pas par conversion implicite.


J'ai souligné la nuance parce que c'est un comportement
différent du typedef.
En fait, je n'arrive pas à imaginer quel contexte
peut ammener à faire un
typedef float myInt;
donc ensuite, savoir si les conversions myInt <-> myFloat
implicites sont désirables ou pas dans ce contexte, je
n'en ait aucune idée.

Le fait de faire un typedef où les entiers sont
des flottants ammène dans un monde étrange, où
d'autres étrangetées peuvent être localement plus
cohérentes que la version classique.
Ou peut-être pas ;-)

False => True, c'est ça qu'est bien.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangereux
que prendre un boulevard dans le sens légal. À qui la faute ?


Avatar
diego-olivier.fernandez-pons
Bonjour,

Je réponds aux diverses questions

[Marc Boyer ]
Une autre, ce serait simplement de jouer avec le préprocesseur.
#if !(MYFLOAT_ID == MYINT_ID && defined(ARRAY_INT)
template <> class Array <myFloat> {...}
#endif


Je n'ai pas très bien compris, je vais avoir besoin à la fois de
tableaux d'entiers, de flottants et de booléens. Avec des ensembles
c'est plus parlant, j'ai à la fois besoin d'ensembles de :
- entiers (arbre de recherche "patricia" spécialisé pour les entiers)
- flottants (arbre de recherche binaire sans indirection)
- objets pointés (arbre de recherche binaire avec indirection)

Le problème c'est que si int = float, alors le template ne sait pas
comment spécialiser set<int> car il y a syntaxiquement deux
implémentations candidates (patricia trees et avl). Mais
sémantiquement il n'y en a qu'une : on veut "patricia" qui exploite le
fait que les données sont entières (et donne en retour plein de
propriétés sympathiques) et non pas avl.

[Fabien Le Lez]
J'ose espérer que non, du moins pas par conversion implicite.


Non, jamais.

Votre méthode (utilisation de classes spéciales pour chaque type de
base, un peu comme dans C#) me semble la plus proche de ce qu'il me
faut. Je crains juste que la classe enveloppe (wrapper) ne disparaisse
pas à la compilation et que j'encoure un ralentissement systématique
sur tous les types de base. Ce qui risque d'être problématique étant
donné leur large utilisation.

[kanze]
J'avoue ne pas en comprendre l'intérêt. Les bogues de
compilateur qui concerne les types de base sont assez rare. (Et
un typedef n'est pas un cast.)


Sur une machine raisonnable on a :
Table of the numbers of bytes used for different data type
char : 1
int : 4
long int : 4
float : 4
double : 8
long double : 12

Mais toutes les machines ne sont pas raisonnables !

Pourquoi des pointeurs ?


Tableau de pointeurs sur des objets, implémentation classique.

On ne « boxe » pas les types en C++ [...] deux très bien.


Et optimisation classique pour les objets de type prédéfini.

[Marc Boyer]
En fait, je n'arrive pas à imaginer quel contexte
peut ammener à faire un
typedef float myInt;
donc ensuite, savoir si les conversions myInt <-> myFloat
implicites sont désirables ou pas dans ce contexte, je
n'en ait aucune idée.


Quand le type entier est pourri, on calcule en entiers sur les
flottants (ou sur n'importe quoi qui ait une taille/comportement
raisonnable). Au fait, j'utilise flottant au sens large (nombre à
virgule flottante) et non pas float par opposition à double ou long
double.

Diego Olivier

Avatar
Marc Boyer
a écrit :
Bonjour,

Je réponds aux diverses questions

[Marc Boyer ]
Une autre, ce serait simplement de jouer avec le préprocesseur.
#if !(MYFLOAT_ID == MYINT_ID && defined(ARRAY_INT)
template <> class Array <myFloat> {...}
#endif


Je n'ai pas très bien compris, je vais avoir besoin à la fois de
tableaux d'entiers, de flottants et de booléens. Avec des ensembles
c'est plus parlant, j'ai à la fois besoin d'ensembles de :
- entiers (arbre de recherche "patricia" spécialisé pour les entiers)
- flottants (arbre de recherche binaire sans indirection)
- objets pointés (arbre de recherche binaire avec indirection)

Le problème c'est que si int = float, alors le template ne sait pas
comment spécialiser set<int> car il y a syntaxiquement deux
implémentations candidates (patricia trees et avl). Mais
sémantiquement il n'y en a qu'une : on veut "patricia" qui exploite le
fait que les données sont entières (et donne en retour plein de
propriétés sympathiques) et non pas avl.


Je propose simplement de gérer ça par compilation conditionnelle.
C'est lourd à faire à la main s'il y a beaucoup de spécialisations
à faire, mais pas compliqué.
Je fais un peu plus de code.

On gère à la main les MYTRUC_ID avec des conventions
comme char <-> 0, int <-> 1, etc...

// Dit au compilo C++ de spécialiser Array<myInt>
template <> class Array <myInt> {...}
// Dit au préprocesseur qu'on a spécialisé Array<myInt>
#define ARRAY_INT

// Dit au préprocesseur de ne laisser le compilateur
// spécialiser Array<myFloat> que si on a pas donné
// les même ID pour myFloat et myInt et qu'on a pas
// spécialisé Array<myInt>
#if !(MYFLOAT_ID == MYINT_ID && defined(ARRAY_INT)
template <> class Array <myFloat> {...}
#endif


C'est pas beau non.

[Fabien Le Lez]
J'ose espérer que non, du moins pas par conversion implicite.


Non, jamais.

Votre méthode (utilisation de classes spéciales pour chaque type de
base, un peu comme dans C#) me semble la plus proche de ce qu'il me
faut. Je crains juste que la classe enveloppe (wrapper) ne disparaisse
pas à la compilation et que j'encoure un ralentissement systématique
sur tous les types de base. Ce qui risque d'être problématique étant
donné leur large utilisation.


Non, à moins d'un compilateur ridicule:
class Integer {
private:
int value;
public:
Integer(int v):value(v);
Integer operator++(){value++;}
}
Integer i=0;
i++; // Donnera le même code que i++ sur un int

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangereux
que prendre un boulevard dans le sens légal. À qui la faute ?


Avatar
Fabien LE LEZ
On 8 Nov 2005 09:26:34 -0800,
:

et que j'encoure un ralentissement systématique
sur tous les types de base.


Ça, c'est quasiment impossible à dire a priori. Faut essayer, et voir
ce qu'en dit le profiler... sur toutes les plate-formes cibles.

Avatar
kanze
wrote:

Je réponds aux diverses questions

[Marc Boyer ]
Une autre, ce serait simplement de jouer avec le préprocesseur.
#if !(MYFLOAT_ID == MYINT_ID && defined(ARRAY_INT)
template <> class Array <myFloat> {...}
#endif


Je n'ai pas très bien compris, je vais avoir besoin à la fois
de tableaux d'entiers, de flottants et de booléens. Avec des
ensembles c'est plus parlant, j'ai à la fois besoin
d'ensembles de :
- entiers (arbre de recherche "patricia" spécialisé pour les
entiers)
- flottants (arbre de recherche binaire sans indirection)
- objets pointés (arbre de recherche binaire avec indirection)

Le problème c'est que si int = float,


C'est effectivement le problème. J'ai du mal à comprendre ce que
ça veut dire, « si int = float ».

Int et float sont deux types de base. Tu ne peux pas les
changer. Si en revanche tu veux de nouveaux types, c'est à toi
de jouer. Même si ces types ont un comportement à peu près comme
int ou comme float. Mais ils ne sont pas int ou float ; ils sont
tes types à toi.

alors le template ne sait pas comment spécialiser set<int> car
il y a syntaxiquement deux implémentations candidates
(patricia trees et avl).


C'est que tu veux deux types distincts. Ou peut-être même deux
templates distincts, pour des collections différentes.

Mais sémantiquement il n'y en a qu'une : on veut "patricia"
qui exploite le fait que les données sont entières (et donne
en retour plein de propriétés sympathiques) et non pas avl.


Je ne suis pas sur que la spécialisation soit ce qu'il te faut.
J'aurais toujours une tendance à dire que tu as trois
collections générique différentes -- le choix de
l'implémentation dépend de l'utilisateur, et non du type.

Si l'arbre patricia n'est qu'une optimisation quand toutes les
valeurs sont entières, je peux le concevoir. Mais dans ce
cas-là, je verrais une sort de méta-programmation, où
l'implémentation est choisi sur la base de
std::numeric_limits<T>::is_integer. Ensuite, tu spécialises
numeric_limits pour les types que tu définis, en mettant
is_integer vrai si le type se comporte comme un entier. (Même si
interne, tu te sers de double pour l'implémenter, c'est son
comportement au niveau de l'interface qui compte.)

[Fabien Le Lez]
J'ose espérer que non, du moins pas par conversion
implicite.


Non, jamais.


Alors, déjà, il faut oublier int, double et leurs compagnons.

Votre méthode (utilisation de classes spéciales pour chaque
type de base, un peu comme dans C#) me semble la plus proche
de ce qu'il me faut. Je crains juste que la classe enveloppe
(wrapper) ne disparaisse pas à la compilation et que j'encoure
un ralentissement systématique sur tous les types de base. Ce
qui risque d'être problématique étant donné leur large
utilisation.


Ça dépend du compilateur. Il y a, effectivement, encore beaucoup
de compilateurs qui optimisent assez mal le passage des
paramtres et des valeurs de retour des types classe. N'empèche
que si tu as besoin des types distincts, c'est la seule façon
d'y arriver en C++.

[kanze]
J'avoue ne pas en comprendre l'intérêt. Les bogues de
compilateur qui concerne les types de base sont assez rare. (Et
un typedef n'est pas un cast.)


Sur une machine raisonnable on a :
Table of the numbers of bytes used for different data type
char : 1
int : 4
long int : 4
float : 4
double : 8
long double : 12


Tu as une drôle d'idée de « raisonable ». Je n'ai jamais encore
vu une machine comme ça ; pour commencer, long double a toujours
été ou 8, 10 ou 16, jamais 12. Et sur la plupart de machines que
j'utilise aujourd'hui, long est 8.

Ce que C++ garantit, c'est le nombre de bits minimum des types
entiers seulement :
char 8
short 16
int 16
long 32
Dans la pratique, ça varie réelement. Une machine ne devient
« exotique » que quand char n'est pas 8 bits, ou qu'il n'utilise
pas complément à 2 (ou éventuellement si le nombre de char dans
un int n'est pas une puissance de deux).

Dans la pratique, on peut souvent décider de limiter la
portabilité ; la plupart du code que j'écris aujourd'hui suppose
que int a au moins 32 bits.

Si on a le <stdint.h> de C, on peut aller plus loin :
int_fast32_t designe, par exemple, le type entier le plus rapide
qui a au moins 32 bits.

Ma question est, cependant, pourquoi est-ce que ça ne te suffit
pas ? Pour les calcules, les utilisations de int comme un
entier, il suffit de garantir un intervale suffisant, ce qui
revient à garantir simplement un nombre minimum de bits.

Mais toutes les machines ne sont pas raisonnables !


Il y a eu des microprocesseurs assez bizarre, au milieu des
années 1970, mais pour la reste, je ne vois pas le problème.

Pourquoi des pointeurs ?


Tableau de pointeurs sur des objets, implémentation classique.


En fait, si je comprends ce que tu as dit ci-dessus, ce que tu
veux, c'est des collections de pointeurs avec déréférencement
automatique. Ou au moins, un type « set » qui considère
l'égalité des objets pointés, plutôt que l'égalité des
pointeurs.

La solution qui convient la plus souvent (et celle adoptée pour
std::set dans la norme), c'est simplement de donner un paramètre
supplémentaire au template, qui spécifie comment faire la
comparaison. C'est AMHA nécessaire dans le cas général de toute
façon, parce qu'on veut bien pouvoir créer des ensembles
d'objets qui ne supportent pas d'office la comparaison, ou des
ensembles triés sur des critères divers.

On ne « boxe » pas les types en C++ [...] deux très bien.


Et optimisation classique pour les objets de type prédéfini.


Le C++ travaille par valeur. « Boxer », c'est une pessimisation
nécessaire dans des langages où on ne travaille qu'avec des
pointeurs. Je ne vois pas de rélévance ici.

[Marc Boyer]

En fait, je n'arrive pas à imaginer quel contexte peut
ammener à faire un typedef float myInt; donc ensuite, savoir
si les conversions myInt <-> myFloat implicites sont
désirables ou pas dans ce contexte, je n'en ait aucune idée.


Quand le type entier est pourri, on calcule en entiers sur les
flottants (ou sur n'importe quoi qui ait une
taille/comportement raisonnable).


Mais ça veut dire quoi : pourri ? Tu es garanti un type entier
d'au moins 32 bits. Le C, entre temps, exige un d'au moins 64
bits (type long long), et toutes les implémentations de C++ que
je connais en supportent un aussi, bien que parfois sous de noms
différents.

Sinon, effectivement, j'ai déjà vu une classe qui utilisait
double pour contenir des valeurs entiers, parce que les double
avait 52 bits de précision, et les long seulement 32. Mais ça
coûter cher en termes de temps d'exécution, parce qu'il fallait
constamment surveiller que la valeur dans le double était bien
un entier. Mais ça date d'un certain temps, avant les long
long, et je doute qu'on le fasse comme ça aujourd'hui.

Au fait, j'utilise flottant au sens large (nombre à virgule
flottante) et non pas float par opposition à double ou long
double.


Quand tu dis « flottant », c'est ce que je comprends. Quand tu
dis « float », en revanche, j'entends bien un mot clé de
C/C++/Java. (Même en anglais, je fais la différence entre
« floating point » et « float ».)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
diego-olivier.fernandez-pons
Bonjour

[DOFP]
Sur une machine raisonnable on a :
Table of the numbers of bytes used for different data type
char : 1
int : 4
long int : 4
float : 4
double : 8
long double : 12


[Kanze]
Tu as une drôle d'idée de « raisonable ». Je n'ai jamais encore
vu une machine comme ça ;


Ma machine de développement prétend s'appeler Intel Pentium III 500.
Et elle dit même qu'elle a plein de soeurs et de cousines qui lui
ressemblent.

Diego Olivier

1 2 3