OVH Cloud OVH Cloud

Template et constante

64 réponses
Avatar
Etienne Rousee
Bonjour,

Le code suivant ne compile pas parce que
k n'est pas une constante:

template <int N> class Entier
{
........
};

int k = 5;

Entier <k> l;

Y aurait il une autre construction permettant de faire ça ?
Mon contexte est l'implémentation des anneaux Z/nZ,
des polynômes à coeefficients là dedans, et de quelques
calculs de groupes.

--

Etienne

10 réponses

3 4 5 6 7
Avatar
James Kanze
Vincent Lascaux wrote:
En dehors des optimisations, la solution avec un paramètre
référence (disons int const& N) conserve le typage fort.


Non...
const int x = 1;
const int y = 1;
Entier<x> et Entier<y> seraient des types différents.


Certes, et ? Si ce n'est pas ce que tu veux, ne le fais pas.

Si on veut faire une classe polynome avec le degré en parametre templat e, si
on utilise un reférence, on n'a pas possibilité (enfin, il me semble)
d'écrire la dérivation (qui retourne un polynome de degré moindre).

-- Il y a le template instantié sur une référence. Tout est
donc reglé lors de la compilation. Tu as les validations de
type, mais tu n'as pas des optimisations éventuelles du fait
que le compilateur sache la valeur, puisqu'il ne le sait
pas. Tu as toujours autant de copies du code généré que tu
as d'instantiations du template, même s'il sont toutes
identiques.


En fait j'ai du mal à saisir l'interet de cette méthode. Par
rapport à celle qui consiste à passer N au constructeur,
pourquoi est-ce plus typé ?


Parce que des instantiations sur deux variables différentes ont
des types différents.

Pour le problème cité ici, tu utiliserais combien d'entier
statics en tout ? Si tu n'en utilise qu'un c'est identique à
utiliser une variable globale pour stoquer N...


Si tu n'en utilise qu'un. L'intérêt, c'est si tu utilises deux
ou trois. Tu as un type différent par variable.

Mais j'avoue que c'est assez limité quand même. Tu ne peux pas,
par exemple, dit que le nombre de types est ouvert, qu'il dépend
des valeurs que tu peux lire.

--
James Kanze (GABI Software) email:
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
Vincent Lascaux
shifter ?!
tu veux surement dire un &= 7


Oui :)

--
Vincent

Avatar
dieu.tout.puissant
James Kanze wrote:
[...]
MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType(/*opérations sur les membres de lhs et rhs*/);
}


C'est une optimisation qui pourrait être utile dans certains
cas, oui. Il y a aussi des cas où c'est plus logique de définir
+= en termes de +, puis =; c'est souvent le cas des classes de
string, par exemple. (Encore que dans le cas de string, je
définis les deux en termes d'une fonction membre « replace ».)
Mais je crois que ce sont plutôt des exceptions (et je me
méfierais d'une classe avec des implémentations indépendantes de
+ et de += -- l'un doit dépendre de l'autre, afin d'être
certain qu'elles ont une sémantique compatible).

Cela permet au compilateur d'effectuer des optimisations
supplémentaires. Sinon, dans la version avec l'opérateur +=, il n 'est
pas certain que le compilateur supporte la " named return value
optimization".


CFront le supportait. Il y a plus de quinze ans. Il n'y a pas
d'excuse qu'un compilateur moderne ne le supporte pas.

On pourrait aussi écrire :

MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType ( lhs ) += rhs;
}

Mais très peu de compilateurs pourront utiliser la RVO ici étant
donné la complexité de l'instruction. D'ailleurs, la manière
d'optimiser dans ces cas-là n'est pas normalisée.


La manière d'optimiser n'est jamais normalisée. On pourrait
imaginer un compilateur qui reconnaît certains modèles, et les
traite spécialement, avec une optimisation accrue. Je ne vois
pas de raison de supposer a priori que l'une des trois solutions
proposées soit systèmatiquement plus rapide ; si, comme CFront,
le compilateur implémente NRVO mais non RVO en général, il y a
de fortes chances que si la copie est chère, ma première
solution en soit la plus rapide. Mais sans faire des mesures, on
n'en sait rien, et avec des mesures, on n'en sait quelque chose
que pour une version donnée d'un compilateur donné sur une
plateforme donnée.


La suppression contextuelle de copies d'objets est décrite dans la
norme. cf Chpt 12 (en particulier 12.2 et 12.8). Je parle bien de
description (non d'obligation).


J'utilise systèmatiquement la première version que j'ai
présentée, parce qu'il me paraît la plus propre, et la plus
facile à implémenter. (En fait, j'utilise le Barton and Nackman
trick -- la classe MaClasse hérite de
ArithmeticOperators< MaClasse >, et si elle implémente +=,
l'opérator + y est, sans que j'ai quoique ce soit à faire.)

Enfin, on pourrait également écrire :

MonType
operator+( MonType lhs, MonType const& rhs )
{
return lhs += rhs;
}

Mais ce sera de toute façon moins performant qu'une version avec RVO.


Selon la version, le compilateur et la plateforme. On ne peut
pas dire des choses comme ça d'avance.


Voilà ce que permet cette version d'operator+:

Analyse de l'expression "x + y + z" :
"x + y" => création d'un object temporaire ...
"temp + z" => ... dans l'argument lhs.

Il y a optimisation lorsque l'opérande de gauche est un objet
temporaire (cette optimisation est supportée par *beaucoup* de
compilateurs). Mais dans tous les autres cas, la RVO est supérieure.

En tout les cas, et si c'est possible, il vaut mieux utiliser la
version operator+ amie qui sera optimisable par un plus grand nombre de
compilateurs.


N'importe quoi. D'abord, tu n'en sais rien, et puis, la plupart
du temps, toutes les versions seront suffisamment rapides, et il
serait carrément sot de ne pas adopter la version la plus facile
à comprendre et à maintenir.


Bien sûr que je le sais. La RVO (unnamed) est supportée par un plus
grand nombre de compilateurs que la NRVO. Il n'y aurait quère que
Cfront qui propose la NRVO sans URVO par contre plus de compilateurs
proposent une URVO sans NRVO (dans les compilateurs populaires : gcc
version inférieure à 3.1(URVO) et supérieure ou égale à 3.1
(URVO+NRVO)).

Rappel : je parle de cette version là :
MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType(/*opérations sur les membres de lhs et rhs*/);
}

Cette version amie n'a besoin que de la URVO. De plus, suivant le
compilateur et les définitions du contructeur, etc.., elle peut être
-légèrement- plus performante sans même tenir compte de la RVO. Mais
bien sûr, l'inconvénient de cette version est qu'elle n'est pas
applicable à tous les cas.

"suffisamment rapide": cela dépend du programme (operator+ appelé n
fois ?) et du niveau de performances exigées.

Comme je disais tout dépend du contexte lorsqu'il s'agit de choisir
l'implémentation d'operator+.


Avatar
dieu.tout.puissant
Vincent Lascaux wrote:
Oui, et je maintiens ce que j'ai dit :

cin >> N; // je saisis 10000
vector <EntierDynamique <0,N> > Vect;


Avec la définition de "EntierDynamique" donnée dans ce contexte :

template<int Low, int High>
struct EntierDynamique


Les paramètres Low et High doivent être constants ("cin >> N"
impossible dans ce cas).


Oui, d'une part c'est impossible, d'autre part ca n'a aucun interet d'avo ir
un vecteur d' "EntierDynamique" (c'est une classe qui ne contient pas de
données). EntierDynamique pourrait être une fonction template (j'ai u tilisé
une structure parceque je fais ca plus souvent pour les fonctors). Bref,
c'est tout faux quoi...

N'empeche que la solution qui utilise EntierDynamique dans le bon sens
fonctionne, permet de conserver le fort typage (ce que ta solution ne per met
pas) avec les inconvenients que ca a (exe plus gros, temps de compilation
ralentit...) et les avantages que ca a (si quelqu'un utilise la bibliothe que
pour n'utiliser que des entiers modulo 8, il sera content que le compilat eur
fasse un shift de bits plutot qu'un modulo...).

--
Vincent



Entendons-nous bien, ce que j'ai proposé n'est pas une solution au
problème posé. C'était juste une illustration de :
les paramètres d'un template doivent
être des constantes.
Ca dépend à quel niveau: adresse ou valeur.



Pour ma part, je n'aurai jamais utilisé les templates au départ pour
ce genre de problème. Je suppose que cela peut être interesssant de
pousser la solution template jusqu'au bout, en ayant en tête que
l'interêt pratique est limité.



Avatar
dieu.tout.puissant
James Kanze wrote:
wrote:
James Kanze wrote:
les paramètres d'un template doivent
être des constantes.


Ca dépend à quel niveau: adresse ou valeur.


Toujours. En fait, les contraints pour paramètres de type
référence ou pointeur sont encore plus forts : l'expression ne
peut être que le nom d'un paramètre du template (quand on
instantie le template dans un autre template), ou l'adresse d'un
objet ou d'une fonction (adresse sans le '&', s'il s'agit d'un
paramètre de type référence). En gros, même des expresssions
d'adresse constantes (genre « toto + 5 », où toto est le nom
d'un tableau) ne sont pas permis ; de même, tu ne peux pas
initialiser un paramètre de type référence avec une autre
référence.

Le code ci-dessus pourrait marcher si la définition de
"EntierDynamique" le permettait.

Exemple :

#include <iostream>

template <int& N>
struct A {
static void aff() {std::cout << N;}
};

int N;

int main () {
std::cin >> N;
A<N>::aff();
return 1;
}


Certes. Parce que l'adresse de N est une constante.


C'est exactement ce que je voulais dire par :
" Ca dépend à quel niveau: adresse ou valeur."


(En fait,
parce que l'expression « N » nom directement un objet avec
linkage extern. (En rélisant la norme, je vois que je me suis
trompé quand j'ai dit « à la portée référencielle ». On
accepte aussi des membres statiques d'une classe. En revanche,
il faut bien uniquement un nom, éventuellement qualifié, mais
pas une expression contante générale.)

Note bien que si tu as l'avantage du typage, chaque variable
générant une instantiation différente, tu ne peux pas utiliser N
comme une constante dans le template, c-à-d des choses du
genre :
int array[ N ] ;
ne va pas.



hehe c'est exactement ce que j'avais répondu un peu plus tôt dans ce
fil de discussion :

wrote:

Par exemple, tu ne peux pas définir foo de cette manière :
template <int& N>
class foo {
int tab[N];
};

alors que c'est possible avec "template <int N>"




Avatar
Gabriel Dos Reis
writes:

[...]

| > > Enfin, on pourrait également écrire :
| >
| > > MonType
| > > operator+( MonType lhs, MonType const& rhs )
| > > {
| > > return lhs += rhs;
| > > }
| >
| > > Mais ce sera de toute façon moins performant qu'une version avec RVO.
| >
| > Selon la version, le compilateur et la plateforme. On ne peut
| > pas dire des choses comme ça d'avance.
|
| Voilà ce que permet cette version d'operator+:
|
| Analyse de l'expression "x + y + z" :
| "x + y" => création d'un object temporaire ...
| "temp + z" => ... dans l'argument lhs.
|
| Il y a optimisation lorsque l'opérande de gauche est un objet
| temporaire (cette optimisation est supportée par *beaucoup* de
| compilateurs). Mais dans tous les autres cas, la RVO est supérieure.

Scott Meyers, d'autres personne dont John Potter et moi même avions eu
de longues discussions sur ce sujet il y a quelques temps dans
comp.lang.c++.moderated (après que j'ai osé dire que sa description
dans son bouquin était inadéquate). Google devrait donner quelques
choses ; John Potter a posté des tests.

-- Gaby
Avatar
dieu.tout.puissant
Gabriel Dos Reis wrote:
writes:

[...]

| > > Enfin, on pourrait également écrire :
| >
| > > MonType
| > > operator+( MonType lhs, MonType const& rhs )
| > > {
| > > return lhs += rhs;
| > > }
| >
| > > Mais ce sera de toute façon moins performant qu'une version avec RVO.
| >
| > Selon la version, le compilateur et la plateforme. On ne peut
| > pas dire des choses comme ça d'avance.
|
| Voilà ce que permet cette version d'operator+:
|
| Analyse de l'expression "x + y + z" :
| "x + y" => création d'un object temporaire ...
| "temp + z" => ... dans l'argument lhs.
|
| Il y a optimisation lorsque l'opérande de gauche est un objet
| temporaire (cette optimisation est supportée par *beaucoup* de
| compilateurs). Mais dans tous les autres cas, la RVO est supérieure.

Scott Meyers, d'autres personne dont John Potter et moi même avions eu
de longues discussions sur ce sujet il y a quelques temps dans
comp.lang.c++.moderated (après que j'ai osé dire que sa description
dans son bouquin était inadéquate). Google devrait donner quelques
choses ; John Potter a posté des tests.

-- Gaby


Je pense avoir trouvé cette discussion (dont le titre est : "code
critique ?").

Intéressante discussion !

Le message concernant la version de l'operator+ dont on parle :

John Potter wrote:
Ok, I could not resist testing this. A simple test.

struct S {
int v;
S (int v = 0);
~S ();
S (S const&);
S& operator= (S const&);
S& operator+= (S const&);
};
inline
#ifdef USE_NRVO
S operator+ (S const& lhs, S const& rhs) {
S tmp(lhs);
#else
S operator+ (S tmp, S const& rhs) {
#endif
tmp += rhs;
return tmp;
}
void addS(S& res, S const& lhs, S const& rhs) {
res = lhs + rhs;
}


Using g++, no nrvo, on cygwin with -S -O2, the value parameter version
generated one more instruction. Without inline, it generated two more
instructions. Changing the two lines here to your one line changed
nothing.


I see no reason to ever use other than the correct three line nrvo
enabling version. Can you give some measurements that indicate
otherwise?


En réalité, la version avec paramètre par valeur peut effectivement
être meilleure si la NRVO est non supportée. C'est le cas que je
décris :
x + y + z : il y a normalement une copie de moins pour la version avec
param par valeur

Avatar
dieu.tout.puissant
James Kanze wrote:
[...]
si, comme CFront,
le compilateur implémente NRVO mais non RVO en général, il y a
de fortes chances que si la copie est chère, ma première
solution en soit la plus rapide. Mais sans faire des mesures, on
n'en sait rien, et avec des mesures, on n'en sait quelque chose
que pour une version donnée d'un compilateur donné sur une
plateforme donnée.



D'après la discussion vers laquelle Gabriel m'a dirigé, John Potter
avait déjà répondu :

John Potter wrote:
James Kanze wrote:
Andrei Alexandrescu wrote:
found no compiler that implements NRVO but not URVO.
CFront:-)?



Nope, it had both. The real URVO implementation would be

return Complex(re + c.re, im + c.im);


I seem to remember some compiler following the semantics without
removing any temporaries by constructing this temp and copying
it to the return area then destroying it. Not Cfront.


John




wrote:
En tout les cas, et si c'est possible, il vaut mieux utiliser la
version operator+ amie qui sera optimisable par un plus grand nombre de
compilateurs.


N'importe quoi. D'abord, tu n'en sais rien, et puis, la plupart
du temps, toutes les versions seront suffisamment rapides, et il
serait carrément sot de ne pas adopter la version la plus facile
à comprendre et à maintenir.


Bien sûr que je le sais. La RVO (unnamed) est supportée par un plus
grand nombre de compilateurs que la NRVO. Il n'y aurait quère que
Cfront qui propose la NRVO sans URVO par contre plus de compilateurs
proposent une URVO sans NRVO (dans les compilateurs populaires : gcc
version inférieure à 3.1(URVO) et supérieure ou égale à 3.1
(URVO+NRVO)).

Rappel : je parle de cette version là :
MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType(/*opérations sur les membres de lhs et rhs*/);
}

Cette version amie n'a besoin que de la URVO. De plus, suivant le
compilateur et les définitions du contructeur, etc.., elle peut être
-légèrement- plus performante sans même tenir compte de la RVO.


Enfin je dois quand même préciser, pour rétablir l'équilibre, qu'il
y a aussi des cas où les performances seraient légèrement moins
bonnes. C'est très dépendant de la définition du ctor / copy ctor /
operators et du compilateur. Donc, *sans tenir compte de la RVO*, on ne
peut pas vraiment juger si une version est plus performante que
l'autre.

La seule chose que l'on peut dire est ce que j'ai dit plus haut :
"la version operator+ amie sera optimisable par un plus grand nombre de
compilateurs."
Optimisable : au niveau de la RVO.



Avatar
Gabriel Dos Reis
(Fabien Chêne) writes:

| Gabriel Dos Reis writes:
|
| > | Sinon, comme suggéré par Herb, retourner «T const» au lieu de «T»
| > | empêche d'écrire a + b = c; Je ne sais pour quelle raison boost ne
| > | l'utilise pas.
| >
| > Je ne sais pas, mais je ne retourne pas « T const ».
|
| Apparemment, ICC -- donc peut-être d'autres compilos utilisant le
| front-end d'EDG -- avait dans le temps un bug, et le fait de changer
| la cv-qualification du type de retour faisait échouer le NRVO.
|
| Boost à l'habitude de recourrir à des instructions conditionelles de
| préprocesseur pour pallier à des bugs de compilateurs, j'ai donc du
| mal à croire que ce soit pour cette raison qu'ils ne renvoient pas
| «T const».
|
| Si cela peut éviter des erreurs sans pénaliser l'exécution, je trouve
| cela positif. Dire que ça complique la relecture, mouuais :-|
|
| ((si tu veux faire suivre sur fclc++, c'est sans problème))

Je ne retourne pas le type de retour « T const » pour à peu près la
même raison que je ne déclare pas le type paramètre « T const »

void f(T const);

quand « T » est un type d'objet.
Sauf que dans le cas de type de retour, le « const » n'est pas
systématiquement ignoré. Il n'est ignoré que si le type T n'est pas
une class. Je ne vois pas l'intérêt de cette « tromperie », même si je
comprends les raisons avancées -- je ne les trouve pas convaincantes.
Je n'ai pas encore tracé un seul bug dans mon code ou celui de mes
collègues pour des raisons liées à cette facetie.

Je tiens à dire que je souscris entièrement à la notion de
vérification statique de type au cas où il y avait un doute. :-)

-- Gaby
Avatar
fabien.chene
Gabriel Dos Reis writes:

(Fabien Chêne) writes:

| Gabriel Dos Reis writes:
|
| > | Sinon, comme suggéré par Herb, retourner «T const» au lieu de «T»
| > | empêche d'écrire a + b = c; Je ne sais pour quelle raison boost ne
| > | l'utilise pas.
| >
| > Je ne sais pas, mais je ne retourne pas « T const ».
|
| Apparemment, ICC -- donc peut-être d'autres compilos utilisant le
| front-end d'EDG -- avait dans le temps un bug, et le fait de changer
| la cv-qualification du type de retour faisait échouer le NRVO.
|
| Boost à l'habitude de recourrir à des instructions conditionelles de
| préprocesseur pour pallier à des bugs de compilateurs, j'ai donc du
| mal à croire que ce soit pour cette raison qu'ils ne renvoient pas
| «T const».
|
| Si cela peut éviter des erreurs sans pénaliser l'exécution, je trouve
| cela positif. Dire que ça complique la relecture, mouuais :-|
|
| ((si tu veux faire suivre sur fclc++, c'est sans problème))

Je ne retourne pas le type de retour « T const » pour à peu près la
même raison que je ne déclare pas le type paramètre « T const »

void f(T const);

quand « T » est un type d'objet. Sauf que dans le cas de type de
retour, le « const » n'est pas systématiquement ignoré. Il n'est
ignoré que si le type T n'est pas une class. Je ne vois pas
l'intérêt de cette « tromperie », même si je comprends les raisons
avancées -- je ne les trouve pas convaincantes.


Le cas de l'operator+ n'est pas le meilleur exemple. Si on considère
le cas de la surcharge de ces chers operator++(int) et operator++(),
il est bien difficile d'admettre que ++++it; fera ce à quoi on
s'attend, et it++++; non - it étant un itérateur de la bibliothèque
standard par exemple.

Je trouve cet exemple ci plus convainquant, mais j'avoue que je serai
géné de retourner «T const» pour une surcharge d'operateur, et pas
pour un banal accesseur -- la problématique m'a l'air d'être la même.
Finalement, je me demande aussi comment sera comprise la déclaration
suivante par un développeur C++ moyen: T const Foo::getT() const;
Croiras t'il qu'on ne peut définitivement pas modifier le retour de la
fonction ?

Je n'ai pas encore tracé un seul bug dans mon code ou celui de mes
collègues pour des raisons liées à cette facetie.


J'en ai tracé un récemment dans le code d'un de mes collègues -- ce
n'était pas avec une surcharge d'opérateur mais avec une fonction
membre.


--
Fab

3 4 5 6 7