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).
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).
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).
En dehors des optimisations, la solution avec un paramètre
référence (disons int const& N) conserve le typage fort.
-- 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 dehors des optimisations, la solution avec un paramètre
référence (disons int const& N) conserve le typage fort.
-- 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 dehors des optimisations, la solution avec un paramètre
référence (disons int const& N) conserve le typage fort.
-- 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.
James Kanze wrote:wrote: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.
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'e st
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.
James Kanze wrote:
dieu.tout.puissant@gmail.com wrote:
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.
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'e st
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.
James Kanze wrote:wrote: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.
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'e st
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.
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 é.
dieu.tout.puissant@gmail.com 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 é.
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 é.
wrote on 02/11/2006 23:12:
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.
dieu.tout.puissant@gmail.com wrote on 02/11/2006 23:12:
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.
wrote on 02/11/2006 23:12:
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.
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.)
Fabien Chêne wrote:
"James Kanze" <james.kanze@gmail.com> 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.)
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.)
[...]
pour n'utiliser que des entiers modulo 8, il sera content que le compilateur
fasse un shift de bits plutot qu'un modulo...).
[...]
pour n'utiliser que des entiers modulo 8, il sera content que le compilateur
fasse un shift de bits plutot qu'un modulo...).
[...]
pour n'utiliser que des entiers modulo 8, il sera content que le compilateur
fasse un shift de bits plutot qu'un modulo...).
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.)
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.)
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.)