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

Avatar
dieu.tout.puissant
Sylvain wrote:
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).


Cet inconvénient n'existe pas si le compilateur supporte le NRVO
(Named return value optimization).


On pourrait aussi écrire :

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


même "défaut".



L'optimisation d'un "return" de cette complexité n'est pas
normalisée, mais ce n'est pas impossible. Je ne sais pas si certains
compilateurs actuels optimisent ce genre de choses.

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


Regarde bien le type de lhs.
lhs est un paramètre passé par valeur, ce qui peut conduire le
compilateur à certaines optimisations (= meilleur que la création
brut d'un temporaire mais moins bon qu'une RVO)


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


D'après la définition de "EntierDynamique" donnée précédemment,
effectivement ça ne va pas.

les paramètres d'un template doivent
être des constantes.


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

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;
}


Avatar
fabien.chene
"James Kanze" writes:

A noter que:

template <int& N>
class foo { ... };

int x;

foo<x> bar;

est possible.


Est-ce que « template< int const& N > » ne serait pas mieux ?
Voire même avec « int const » comme paramètre. (Mais
attention : il faut que l'int ait un linkage global, donc,
qu'il soit défini dans une portée référentielle et qu'il ait une
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

durée de vie statique.


Est-ce que cela exclu la définition dans une autre TU ?

Ce code a l'air valide d'après les compilateurs que j'ai essayé.

//-------- TU ----------
template <int& N>
struct Foo {};

void f()
{
extern int n; // n défini avec un linkage extern dans une autre TU
Foo<n> foo;
}
//---------------------


--
Fab


Avatar
Sylvain
wrote on 02/11/2006 23:12:

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



Regarde bien le type de lhs.
lhs est un paramètre passé par valeur, ce qui peut conduire le
compilateur à certaines optimisations (= meilleur que la création
brut d'un temporaire mais moins bon qu'une RVO)


farceur ! ;) ok, valide, en effet.

toutefois, je ne crois pas à la création "brute d'un temporaire", je
veux dire si la structure est compliquée, on aura fourni un constructeur
de copie aussi intelligent que possible.

de plus dans ce contexte, ie strcuture compliquée, on n'aura pas envie
de coder 2 fois l'opération (elle ne sera vraisemblablement pas inlinée
et la dupliquer double les risques d'erreurs), ie on ne réalise pas:

friend T operator? (const T& a, const T& b){
plein de machin réalisant l'operation
}
T& operator?= (const T& x){
plein de machin réalisant la même operation
}

mais plutôt (comme indiqué par James)

T& operator?= (const T& x){
l'operation
}
friend T operator? (const T& a, const T& b){
T clone(a);
return (clone ?= b);
}

ta variante

friend T operator? (T a, T const& b){ }

créera une copie sur la pile de l'appelant au lieu de mettre le tempo
sur celle de l'appelé, cela ne fera pas vraiment de différence, mais
utiliser un passage par valeur me gène quand même (blocage psycho-rigide
subjectif, j'en conviens).

Sylvain.



Avatar
dieu.tout.puissant
Sylvain wrote:
wrote on 02/11/2006 23:12:

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 souhai té.



Regarde bien le type de lhs.
lhs est un paramètre passé par valeur, ce qui peut conduire le
compilateur à certaines optimisations (= meilleur que la création
brut d'un temporaire mais moins bon qu'une RVO)


farceur ! ;) ok, valide, en effet.


=)


toutefois, je ne crois pas à la création "brute d'un temporaire", je
veux dire si la structure est compliquée, on aura fourni un constructeur
de copie aussi intelligent que possible.

de plus dans ce contexte, ie strcuture compliquée, on n'aura pas envie
de coder 2 fois l'opération (elle ne sera vraisemblablement pas inlin ée
et la dupliquer double les risques d'erreurs), ie on ne réalise pas:

friend T operator? (const T& a, const T& b){
plein de machin réalisant l'operation
}
T& operator?= (const T& x){
plein de machin réalisant la même operation
}

mais plutôt (comme indiqué par James)

T& operator?= (const T& x){
l'operation
}
friend T operator? (const T& a, const T& b){
T clone(a);
return (clone ?= b);
}

ta variante

friend T operator? (T a, T const& b){ }

créera une copie sur la pile de l'appelant au lieu de mettre le tempo
sur celle de l'appelé, cela ne fera pas vraiment de différence, mais
utiliser un passage par valeur me gène quand même (blocage psycho-rig ide
subjectif, j'en conviens).

Sylvain.



En fait, il peut y avoir une différence si, lors de l'appel à
operator+, l'argument de gauche est lui-même un objet temporaire. Dans
ce cas, puisque lhs est un paramètre passé par valeur, l'objet
temporaire est directement créé dans lhs (pas de copie).

Exemple :
x + y + z

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

Cependant si le compilateur supporte la NRVO, la version de James est
plus optimisée, mais tous les compilateurs actuels n'implémentent pas
la NRVO (VC++ 2005 la supporte il me semble, je vais vérifier).

Quoiqu'il en soit la version la plus optimisable est celle que j'ai
donnée plus haut :
MonType
operator+( MonType const& lhs, MonType const& rhs )
{
return MonType(/*opérations sur les membres de lhs et rhs*/);
}


Mais elle n'est pas applicable à tous les cas, alors que celle de
James est utilisable dans à peu près tous les cas.

Donc, le choix entre les différentes versions d'operator+ est vraiment
une question de contexte (compilateur, performance, lisibilité,
faisabilité ...).




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


Je crois que tu n'as pas compris le but de ma classe EntierDynamique (qui a
un très mauvais nom, ce qui explique surement ton incompréhension en
partie).

Le but n'est pas de faire un wrapper de la classe template Entier, mais un
wrapper du CODE qui utilise entier, de façon à forcer le compilateur à
compiler ce code pour chacun des N possibles (de ce point de vue, c'est un
peu comme la compilation à la volée de James, mais en portable, et avec la
compilation faite pour tout les N à l'avance).

On ne crée par un vecteur de ma classe, on crée un fonctor template qui sera
utilisé par ma classe pour chaque valeur de N.
Au lieu d'écrire

std::cin >> N;
switch (N)
{
case 1: Entier<1> x(34); std::cout << x << std::endl; break;
case 2: Entier<2> x(34); std::cout << x << std::endl; break;
case 3: Entier<3> x(34); std::cout << x << std::endl; break;
case 4: Entier<4> x(34); std::cout << x << std::endl; break;
case 5: Entier<5> x(34); std::cout << x << std::endl; break;
.....
case 10000: Entier<10000> x(34); std::cout << x << std::endl; break;
}

on écrit

struct output34
{
template<int N>
void Execute()
{
Entier<N> x(34);
std::cout << x << std::endl;
}
};

std::cin >> N;
EntierDynamic<0, 10000>::Execute(N, output34());

On conserve ainsi le typage fort, qui a ses interet pour permettre au
compilo de faire des optimisations (c'est probablement irrélevant pour le
cas ici, mais ca pourrait l'être dans d'autres, comme pour le filtre
gaussien que j'ai évoqué dans un autre message de ce thread).

--
Vincent

Avatar
dieu.tout.puissant
Vincent Lascaux wrote:
Le code ci-dessus pourrait marcher si la définition de
"EntierDynamique" le permettait.


Je crois que tu n'as pas compris le but de ma classe EntierDynamique (qui a
un très mauvais nom, ce qui explique surement ton incompréhension en
partie).

Le but n'est pas de faire un wrapper de la classe template Entier, mais un
wrapper du CODE qui utilise entier, de façon à forcer le compilateur à
compiler ce code pour chacun des N possibles (de ce point de vue, c'est un
peu comme la compilation à la volée de James, mais en portable, et av ec la
compilation faite pour tout les N à l'avance).

On ne crée par un vecteur de ma classe, on crée un fonctor template q ui sera
utilisé par ma classe pour chaque valeur de N.
Au lieu d'écrire

std::cin >> N;
switch (N)
{
case 1: Entier<1> x(34); std::cout << x << std::endl; break;
case 2: Entier<2> x(34); std::cout << x << std::endl; break;
case 3: Entier<3> x(34); std::cout << x << std::endl; break;
case 4: Entier<4> x(34); std::cout << x << std::endl; break;
case 5: Entier<5> x(34); std::cout << x << std::endl; break;
.....
case 10000: Entier<10000> x(34); std::cout << x << std::endl; break;
}

on écrit

struct output34
{
template<int N>
void Execute()
{
Entier<N> x(34);
std::cout << x << std::endl;
}
};

std::cin >> N;
EntierDynamic<0, 10000>::Execute(N, output34());

On conserve ainsi le typage fort, qui a ses interet pour permettre au
compilo de faire des optimisations (c'est probablement irrélevant pour le
cas ici, mais ca pourrait l'être dans d'autres, comme pour le filtre
gaussien que j'ai évoqué dans un autre message de ce thread).

--
Vincent


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


Avatar
Vincent Lascaux
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'avoir
un vecteur d' "EntierDynamique" (c'est une classe qui ne contient pas de
données). EntierDynamique pourrait être une fonction template (j'ai utilisé
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 permet
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 bibliotheque
pour n'utiliser que des entiers modulo 8, il sera content que le compilateur
fasse un shift de bits plutot qu'un modulo...).

--
Vincent


Avatar
James Kanze
Fabien Chêne wrote:
"James Kanze" writes:

A noter que:

template <int& N>
class foo { ... };

int x;

foo<x> bar;

est possible.


Est-ce que « template< int const& N > » ne serait pas mieux ?
Voire même avec « int const » comme paramètre. (Mais
attention : il faut que l'int ait un linkage global, donc,
qu'il soit défini dans une portée référentielle et qu'il ait une
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

durée de vie statique.


Est-ce que cela exclu la définition dans une autre TU ?


Non, pourquoi est-ce qu'il l'exclurait ? (En fait, toute
variable définie dans une portée référentielle a une durée de
vie statique. L'inverse n'est pas forcément vrai.)

Ce code a l'air valide d'après les compilateurs que j'ai essayé.

//-------- TU ----------
template <int& N>
struct Foo {};

void f()
{
extern int n; // n défini avec un linkage extern dans une autre TU
Foo<n> foo;
}
//---------------------


Il l'est. À condition, évidemment, que tu définis n dans une
autre module. À portée référentielle, évidemment.

--
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:
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. (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.

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