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.
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é ?
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...
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.
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é ?
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...
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.
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é ?
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...
shifter ?!
tu veux surement dire un &= 7
shifter ?!
tu veux surement dire un &= 7
shifter ?!
tu veux surement dire un &= 7
[...]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.
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.
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.
[...]
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.
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.
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.
[...]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.
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.
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.
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
les paramètres d'un template doivent
être des constantes.
Ca dépend à quel niveau: adresse ou valeur.
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
les paramètres d'un template doivent
être des constantes.
Ca dépend à quel niveau: adresse ou valeur.
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
les paramètres d'un template doivent
être des constantes.
Ca dépend à quel niveau: adresse ou valeur.
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.
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>"
dieu.tout.puissant@gmail.com 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.
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>"
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.
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>"
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
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?
dieu.tout.puissant@gmail.com 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
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?
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
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?
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.
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
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.
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.
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
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.
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.
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
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.
(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.
fabien.chene@gmail.com (Fabien Chêne) writes:
| Gabriel Dos Reis <gdr@integrable-solutions.net> 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.
(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.