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

1 2 3 4 5
Avatar
Etienne Rousee
"Sylvain" a écrit ...
Etienne Rousee wrote on 31/10/2006 18:25:

[] est-il seulement un paramètre (qlq) utilisé pour les
opérateurs mathématiques de Entier ?
Aussi.



ok, donc N est un paramètre ""global et variable"" propre à un run.


Oui, je peux avoir plusieurs N dans une même session,
et même simultanément si je veux construire par exemple z/nZ x z/mZ.

une appli plus "grosse" (feuille de calcul à la mapple) devrait géré
différents ensemble (anneau ou pas) avec N distincts, mais je ne suis
pas sur que cela soit vital ici.


Ben, c'est ce que je voudrais.

ie
Entier<N> operator+ (Entier<N> const& a, Entier<N> const& b){
return Entier<N>((a + b) % N);
}


Oui, j'ai écrit ça, mais en surcharge de méthode, pas de fonction
globale.



je ne l'ai pas présente in-situ mais je pensais à une méthode friend
bien sur (pas à une globale qui ne verrait que les instances "a" et "b",
ce ne sont pas 'a' ni 'b' qui sont sommées mais le nombre qu'ils
représentent).


Ok, via la surcharge.

"surcharge de méthode" est un peu vague, les opérateurs "à un paramètre"
(+=, *=, ...) sont des opérateurs membres,


Oui.

les opérateurs "à deux paramètres" (+, *, ...) sont (nécessairement) des
opérateurs non-membres


Non, les deux choix sont possibles.
Le seul cas qui impose le choix global est le cas d'un opérateur binaire
avec un premier type sur lequel je n'ai pas de possibilité d'action,
soit parce qu'il est standard (int,long,float,double ....), soit parce qu'il
a été fabriqué par un voisin cachottier.

question subsidiaire, quelles est la limite de N ?
(32, 64 bits ou précision arbitraire ("BigInteger"))


En théorie, la plus grande possible, mais soyons réalistes,
pour le moment des int ou des long ordinaires me suffisent.


c'est bien la 1ière fois que je lis un matheux se contentant de "nombres
physiquement valides" ;)


A quoi tu vois que je suis matheux ? :)

De plus, je vais avoir une classe Anneau contenant une collection
d'Entier, et des opérateurs fonctionnant dessus.


je comprends le point ... et le problème venant du choix de mettre un
modulo dans Entier - la définition même de l'anneau présuppose
l'existence de N, mais pas celle des nombres entiers (surtout hors d'un
anneau), vouloir borner les nombres est une chose, figer le code (par
une écriture statique d'une classe template) en est une autre.


Ben oui, c'est pas clair.

ctory d'Entier (ou un ajustement dans son
constructor) peut suffire.


Pourquoi pas, je vais étudier ça.

--

Etienne



Avatar
Sylvain
Etienne Rousee wrote on 01/11/2006 00:13:

les opérateurs "à deux paramètres" (+, *, ...) sont (nécessairement) des
opérateurs non-membres


Non, les deux choix sont possibles.
Le seul cas qui impose le choix global est le cas d'un opérateur binaire
avec un premier type sur lequel je n'ai pas de possibilité d'action,
soit parce qu'il est standard (int,long,float,double ....), soit parce qu'il
a été fabriqué par un voisin cachottier.


si tu veux, j'aurais du dire: cela a moins de sens et/ou peut être
confusant d'utiliser des opérateurs membres; des opérateurs 'globaux' me
paraissent plus compréhensible, en contre-partie ils seront (pour ne pas
se pénaliser) amis.

ainsi, en effet, le codage suivant est valide:

struct entier {
int val;
explicit entier(int x) { val = x; }
entier operator+ (entier const& x) const { return val + x.val; }
};

je préfère néanmoins:

struct entier {
...
friend entier operator+ (entier const& a, entier const& b){
return a.val + b.val;
}
};

simplement parce 'a' et 'b' sont totalement interchangeables et qu'il
n'y a pas de raison particulière dans

entier a(3);
entier b(2);
entier c = a + b;

à faire
a.operator+(b)
ou
b.operator+(a);

donc entier::operator+(a, b) me plait mieux (mais les gouts z'et les
couleurs... à moins qu'il y ait une 'bonne' raison qui m'a echappée).

Sylvain.


Avatar
Vincent Lascaux
D'accord, mais ce n'est pas utilisable en pratique.
Mettons, par exemple, que je veuille construire
l'ensemple de tous les entiers modulo 10000:

cout << "N = ";
cin >> N; // je saisis 10000
vector <EntierDynamique <0,N> > Vect;
// puis 100 appels à Execute


Tu peux mettre le code qui appelle 100 fois execute à l'interieur d'Execute.
Ou avec un fonctor, faire comme ca :

template<int Low, int High, class TToExecute>
struct EntierDynamic
{
static void Execute(int n, TToExecute& foo)
{
const int Mid = (Low + High) / 2;
if (n < Mid)
{
EntierDynamique<Low, Mid - 1>::Execute(n);
} else if (n > Mid) {
EntierDynamique<Mid + 1, Low>::Execute(n);
} else {
foo.Execute<N>();
}
}
};
template<int N, template<int N> class TToExecute>
struct EntierDynamique<N, N, TToExecute>
{
static void Execute(int n, TToExecute& foo) { }
};
template<int N, template<int N> class TToExecute>
struct EntierDynamique<N, N - 1>
{
static void Execute(int n, TToExecute& foo) { }
};

struct Code
{
template<int N> void Execute()
{
Entier<N> bla;
std::cin >> bla;
bla *= 5; //...
}
};

cout << "N = ";
cin >> N; // je saisis 10000
EntierDynamic<0, 100000>::Execute(N, Code());


Par contre, je suis d'accord que ce n'est pas utilisable en pratique avec
une valeur de N aussi grande. Il peut y avoir des problèmes ou cette
technique a ces avantages. J'ai un petit morceau de code pour faire un
filtre gaussien sur une image. Avec les template sur la taille du filtre,
j'arrive a avoir un code relativement performant (le compilo optimize bien
les boucles avec des bornes fixées à des petits entiers). Si je veux donner
le choix de la taille du filtre à l'utilisateur (disons entre 1 et 10 en x
et y), ca me fait 100 différents filtres, et ca peut valoir le coup de
conserver le template sur le filtre (donc un exe plus gros) pour avoir 100
codes générés et optimisés indépendamment par le compilateur.

--
Vincent

Avatar
Etienne Rousee
"Sylvain" a écrit ...
donc entier::operator+(a, b) me plait mieux (mais les gouts z'et les
couleurs... à moins qu'il y ait une 'bonne' raison qui m'a echappée).


Non, tu as raison.
Si j'ai utilisé plutôt la forme fonction membre, c'st par flemme,
il y a moins de caractères à taper :)

--

Etienne

Avatar
Etienne Rousee
"Vincent Lascaux" a écrit ...
Par contre, je suis d'accord que ce n'est pas utilisable en pratique avec
une valeur de N aussi grande. Il peut y avoir des problèmes ou cette
technique a ces avantages. J'ai un petit morceau de code pour faire un
filtre gaussien sur une image. Avec les template sur la taille du filtre,
j'arrive a avoir un code relativement performant (le compilo optimize bien
les boucles avec des bornes fixées à des petits entiers). Si je veux
donner

le choix de la taille du filtre à l'utilisateur (disons entre 1 et 10 en x
et y), ca me fait 100 différents filtres, et ca peut valoir le coup de
conserver le template sur le filtre (donc un exe plus gros) pour avoir 100
codes générés et optimisés indépendamment par le compilateur.


Tu as raison pour de petites valeurs, et puis il faut dire que pour le
moment,
j'en suis encore à la phase de conception, pas encore d'optimisation.

--

Etienne

Avatar
dieu.tout.puissant
Sylvain wrote:
Etienne Rousee wrote on 01/11/2006 00:13:

les opérateurs "à deux paramètres" (+, *, ...) sont (nécessai rement) des
opérateurs non-membres


Non, les deux choix sont possibles.
Le seul cas qui impose le choix global est le cas d'un opérateur bina ire
avec un premier type sur lequel je n'ai pas de possibilité d'action,
soit parce qu'il est standard (int,long,float,double ....), soit parce qu'il
a été fabriqué par un voisin cachottier.


si tu veux, j'aurais du dire: cela a moins de sens et/ou peut être
confusant d'utiliser des opérateurs membres; des opérateurs 'globaux' me
paraissent plus compréhensible, en contre-partie ils seront (pour ne pas
se pénaliser) amis.

ainsi, en effet, le codage suivant est valide:

struct entier {
int val;
explicit entier(int x) { val = x; }
entier operator+ (entier const& x) const { return val + x.val; }
};

je préfère néanmoins:

struct entier {
...
friend entier operator+ (entier const& a, entier const& b){
return a.val + b.val;
}
};

simplement parce 'a' et 'b' sont totalement interchangeables et qu'il
n'y a pas de raison particulière dans

entier a(3);
entier b(2);
entier c = a + b;

à faire
a.operator+(b)
ou
b.operator+(a);

donc entier::operator+(a, b) me plait mieux (mais les gouts z'et les
couleurs... à moins qu'il y ait une 'bonne' raison qui m'a echappée).

Sylvain.


Oui, l'opérateur+ est en général un opérateur non-membre ami. Outre
ce que tu as dis, il y a une autre raison à utiliser ce design. Dans
la version non-membre, les 2 opérandes peuvent être le résultat
d'une conversion automatique. Dans la version membre, l'opérande de
gauche (paramète implicite this) ne peut pas être le résultat d'une
telle conversion.



Avatar
James Kanze
Etienne Rousee wrote:
"Vincent Lascaux" a écrit ...


[...]
cout << "N = ";
cin >> N; // je saisis 10000
vector <EntierDynamique <0,N> > Vect;
// puis 100 appels à Execute


Ça ne va évidemment pas ; les paramètres d'un template doivent
être des constantes.

On peut le faire :

cin >> N ;
std::ostringstream cmd ;
cmd << "g++ -DN="
<< N
<< " -dynamic entierdynamic.cc -o entierdynamic.so"
system( cmd.str().c_str() ) ;
dlopen( "entierdynamic.so", ... )
// ...

(Remplacer l'invocation de g++ et l'appel à dlopen à ce qui
convient pour ton environement.)

Seulement, c'est extrèmement lourd. Et pas du tout portable.

--
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
James Kanze
wrote:

Oui, l'opérateur+ est en général un opérateur non-membre ami. Out re
ce que tu as dis, il y a une autre raison à utiliser ce design. Dans
la version non-membre, les 2 opérandes peuvent être le résultat
d'une conversion automatique. Dans la version membre, l'opérande de
gauche (paramète implicite this) ne peut pas être le résultat d'une
telle conversion.


Oui et non. Très, très souvent, l'implémentation de l'operator+
est simplement :

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

Et évidemment, le constructeur de copie et l'opérateur += sont
des fonctions publiques. Du coup, aucune raison de faire
operator+ un ami.

Sauf, évidemment, si on veut généraliser cette technique, au
moyen du Barton-Nackman trick. Mais alors, les opérateurs ne sont
pas amis afin d'accéder aux éléments privés, mais seulement
parce que c'est la seule façon de fournir l'implémentation d'une
fonction libre dans la définition de la classe. (Voir le
composant Operators, sous-système Basic dans ma bibliothèque
utilitaire, par exemple.)

--
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
dieu.tout.puissant
James Kanze wrote:
wrote:

Oui, l'opérateur+ est en général un opérateur non-membre ami. O utre
ce que tu as dis, il y a une autre raison à utiliser ce design. Dans
la version non-membre, les 2 opérandes peuvent être le résultat
d'une conversion automatique. Dans la version membre, l'opérande de
gauche (paramète implicite this) ne peut pas être le résultat d'u ne
telle conversion.


Oui et non. Très, très souvent, l'implémentation de l'operator+
est simplement :

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

Et évidemment, le constructeur de copie et l'opérateur += sont
des fonctions publiques. Du coup, aucune raison de faire
operator+ un ami.

Sauf, évidemment, si on veut généraliser cette technique, au
moyen du Barton-Nackman trick. Mais alors, les opérateurs ne sont
pas amis afin d'accéder aux éléments privés, mais seulement
parce que c'est la seule façon de fournir l'implémentation d'une
fonction libre dans la définition de la classe. (Voir le
composant Operators, sous-système Basic dans ma bibliothèque
utilitaire, par exemple.)



Exact, en supposant que tu voulais écrire :
MonType tmp( lhs ) ;
lhs += rhs ;
return lhs ;


MonType tmp( lhs ) ;
tmp += rhs ;
return tmp ;

Cependant, dans certains domaines (surtout celui de ce fil de
discussion, c'est-à-dire celui de classes représentant des valeurs
numériques), on peut utiliser une définition d'operator+ (ami) de ce
genre :

MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType(/*opérations sur les membres de lhs et rhs*/);
}


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". 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.

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.


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.


Avatar
Sylvain
wrote on 02/11/2006 19:04:

Exact, en supposant que tu voulais écrire :

MonType tmp( lhs ) ;
tmp += rhs ;
return tmp ;


qui aura ""l'inconvient" de nécessiter un temporaire, même pour des
structures simplissimes (comme celle de l'exemple).

On pourrait aussi écrire :

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


même "défaut".

Enfin, on pourrait également écrire :

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


non ça on ne pourra pas, lhs est modifié - ce qui n'est pas souhaité.

Sylvain.

1 2 3 4 5