OVH Cloud OVH Cloud

Valeur par défaut / paramètre passé par référence

9 réponses
Avatar
PurL
Salut,

soit le prototype d'une fonction :
---
void Fct(long &param = 0);
---

et le code de celle-ci :
---
void Fct(long &param)
{
param *=2;
}
---

si je fais :
---
int a = 3;
Fct(a);
---
Je comprend que ma variable locale 'a' a été passée par référence à la
fonction Fct laquelle a pu accéder à son contenu.
Mais si je fais ça :

---
Fct();
---

Quelle référence de variable Fct reçoit-elle ? un temporaire ?
si oui, l'appel à Fct(a) passe-t-il aussi par un temporaire ? si oui
pourquoi ?

J'ai été surpris de voir que mon compilateur ne bronchait pas quand j'ai
écrit Fct() !
Je pensais qu'il m'aurait écrit un truc du genre quand on fait cela :

long &fct()
{
long res;
return res;
}

où là, il y effectivement un pb, puisque on renvoit une référence d'une
variable qui sera détruite en sortant de fct.


Merci de m'éclairer,

PurL

9 réponses

Avatar
Falk Tannhäuser
PurL wrote:
void Fct(long &param = 0);


Ça ne devrait même pas compiler (et ça ne compile pas chez moi avec gcc) !
Si l'argument (y compris un argument par défaut) pour un paramètre
de référence n'est pas une "l-value", il faut absolument que
le type référencé par ce paramètre soit qualifié "const" - et
dans ce cas-là, le compilateur crée effectivement un temporaire
du bon type et l'initialise avec l'argument.

void Fct(long const& param = 0);
serait donc légal - mais ne permet pas de modifier 'param' ;
et particulier, on ne peut pas faire

{
param *=2;
}



si je fais :
---
int a = 3;
Fct(a);
---
Je comprend que ma variable locale 'a' a été passée par référ ence à la
fonction Fct laquelle a pu accéder à son contenu.


Pareil - ça ne passe qu'avec "void Fct(long const& param)"
(création d'un temporaire du type "long" qui est initialisé avec "3")
ou bien avec "void Fct(int& param)" (passage de "a" par référence,
les modifications faites par "Fct" sur ce paramètre sont donc répercu tées
dans "a")

J'ai été surpris de voir que mon compilateur ne bronchait pas quand j'ai
écrit Fct() !


C'est quel compilateur ?

Falk

Avatar
Anthony Fleury
PurL wrote:

Salut,


Salut,

soit le prototype d'une fonction :
---
void Fct(long &param = 0);
---


Euh, quel compilateur accepte ca ?
C'est pas autorisé par la norme. Une lvalue est requise pour initialiser une
référence, donc ce code est incorrect. Avec un passage par adresse, et un
long* param = 0, ca devient correct. Mais pas en passage par référence. Par
contre dans ce cas le code de la fonction est à changer, et un test sur
NULL à faire avant de faire toute opération de déréférencement sur param.

Mais si je fais ça :
Fct();
Quelle référence de variable Fct reçoit-elle ? un temporaire ?
si oui, l'appel à Fct(a) passe-t-il aussi par un temporaire ? si oui
pourquoi ?


L'appel à Fct(a) n'a pas à ma connaissance à passer par un temporaire, vu
que c'est un passage par référence. Et vu que le code d'origine n'est pas
conforme, je ne peux pas répondre à la première question.

J'ai été surpris de voir que mon compilateur ne bronchait pas quand j'ai
écrit Fct() !


Et moi je serai curieux de savoir qui accepte le code du prototype de la
fonction.

Anthony
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra

Avatar
PurL
Effectivement mon exemple ne compile pas non plus chez moi.
Désolé j'ai été un peu vite en besogne :(

Mais au départ, j'avais le probleme avec AnsiString à la place de long et
c'est qu'en j'écris :

void Fct(AnsiString &a = (AnsiString)"");

que le compilateur ne bronche pas, mais je pense que dans ce cas, si je ne
précise pas le parametre dans l'appel de ma fonction Fct, un temporaire de
type AnsiString initialisé avec "" est créé et assigné (du moins sa
référence) à ma variable 'a'.

PurL
Avatar
James Kanze
Falk Tannhäuser wrote:
PurL wrote:


void Fct(long &param = 0);



Ça ne devrait même pas compiler (et ça ne compile pas chez moi
avec gcc) ! Si l'argument (y compris un argument par défaut)
pour un paramètre de référence n'est pas une "l-value", il
faut absolument que le type référencé par ce paramètre soit
qualifié "const" - et dans ce cas-là, le compilateur crée
effectivement un temporaire du bon type et l'initialise avec
l'argument.


C'est clair que le paramètre par défaut est illégal. Mais est-ce
que ça veut dire que le code ne doit pas compiler si on ne s'en
sert pas ? (Évidemment, même si c'est légal, un bon compilateur
donnerait un avertissement.)

void Fct(long const& param = 0);


serait donc légal - mais ne permet pas de modifier 'param' ;
et particulier, on ne peut pas faire


{
param *=2;
}



si je fais :
---
int a = 3;
Fct(a);
---
Je comprend que ma variable locale 'a' a été passée par
référence à la fonction Fct laquelle a pu accéder à son
contenu.



Et voilà le problème fondamental. Pour passer le paramètre a à
une fonction qui veut un long, il faut une conversion. Dont le
résultat est un temporaire. Donc, si on accepte l'appel (ce
qu'on ne doit pas faire en C++ moderne, mais il fut un temps où
c'était légal), logiquement, on lie le temporaire à la
référence. Ce qui veut dire que la modification se fait sur le
temporaire, et disparaît à la fin de l'expression.

C'est précisement à cause des erreurs de ce genre qu'on a
interdit la liaison d'un temporaire à une référence non-const.

Pareil - ça ne passe qu'avec "void Fct(long const& param)"
(création d'un temporaire du type "long" qui est initialisé
avec "3") ou bien avec "void Fct(int& param)" (passage de "a"
par référence, les modifications faites par "Fct" sur ce
paramètre sont donc répercutées dans "a")


J'ai été surpris de voir que mon compilateur ne bronchait pas
quand j'ai écrit Fct() !



C'est quel compilateur ?


La plupart font pareil. Il y a fort longtemps, c'était légal. Et
ils ne veulent pas cassé du code.

Pourquoi autant nén génèrent même pas d'avertissement, c'est
plus difficile à comprendre. S'ils avaient commencer à générer
l'avertissement dès que la construction a été interdite, ils
pourraient commencer à penser à l'enlever réelement aujourd'hui.

--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
PurL wrote:
Effectivement mon exemple ne compile pas non plus chez moi.
Désolé j'ai été un peu vite en besogne :(


Mais au départ, j'avais le probleme avec AnsiString à la place
de long et c'est qu'en j'écris :


void Fct(AnsiString &a = (AnsiString)"");


que le compilateur ne bronche pas, mais je pense que dans ce
cas, si je ne précise pas le parametre dans l'appel de ma
fonction Fct, un temporaire de type AnsiString initialisé avec
"" est créé et assigné (du moins sa référence) à ma variable
'a'.


C'est pareil. La référence n'étant pas const, il est interdit de
l'initialiser avec un temporaire.

Mais comme j'ai dit, beaucoup de compilateurs sont laxiste à cet
égard.

--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Avatar
James Kanze
Anthony Fleury wrote:
PurL wrote:


soit le prototype d'une fonction :
---
void Fct(long &param = 0);
---



Euh, quel compilateur accepte ca ?


La plupart.

C'est pas autorisé par la norme. Une lvalue est requise pour
initialiser une référence, donc ce code est incorrect.


Ce n'est pas si évident. Je l'ai vérifié, et c'est bien
interdit, mais si la fonction était l'instanciation d'un
template, il serait tout à fait légal, tant que le programmeur
se servait pas de la valeur par défaut.

Avec un passage par adresse, et un long* param = 0, ca devient
correct. Mais pas en passage par référence. Par contre dans ce
cas le code de la fonction est à changer, et un test sur NULL
à faire avant de faire toute opération de déréférencement sur
param.


Mais si je fais ça :
Fct();
Quelle référence de variable Fct reçoit-elle ? un temporaire ?
si oui, l'appel à Fct(a) passe-t-il aussi par un temporaire ?
si oui pourquoi ?



L'appel à Fct(a) n'a pas à ma connaissance à passer par un
temporaire, vu que c'est un passage par référence.


Sauf que dans son exemple, le paramètre réel n'avait pas le même
type que le paramètre formel. Donc, il y a une conversion, qui
donne un temporaire.

Et vu que le code d'origine n'est pas conforme, je ne peux pas
répondre à la première question.


J'ai été surpris de voir que mon compilateur ne bronchait pas
quand j'ai écrit Fct() !



Et moi je serai curieux de savoir qui accepte le code du
prototype de la fonction.


Pour des raisons historiques, des compilateurs qui le rejette
carrément sont même plutôt rares. La constrution a été légale,
et les bons compilateurs n'aiment pas casser du code existant.

En revanche, je le trouve plutôt anormal c'un compilateur
moderne d'émet même pas d'avertissement. Surtout que ce n'est
pas un changement particulièrement récent -- 1989, à peu près,
je crois. De toute façon, CFront 3.0 donnait déjà
l'avertissement.

--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
PurL
J'utilise le compilateur Borland C++ 5.5.1
L'exemple avec les long ne compile pas du tout :

[C++ Erreur] Unit1.cpp(12): E2357 Référence initialisée avec 'int',
nécessite lvalue de type 'long'
[C++ Erreur] Unit1.cpp(12): E2151 Mauvaise correspondance de type dans la
valeur par défaut pour le paramètre 'a'
la ligne 12 étant la ligne du prototype de la fonction

Par contre avec AnsiString, aucun avertissement n'est généré.

PurL
Avatar
Anthony Fleury
James Kanze wrote:

Anthony Fleury wrote:
PurL wrote:

soit le prototype d'une fonction :
---
void Fct(long &param = 0);
---


Euh, quel compilateur accepte ca ?


La plupart.


J'aurai appris quelque chose :-)

C'est pas autorisé par la norme. Une lvalue est requise pour
initialiser une référence, donc ce code est incorrect.


Ce n'est pas si évident. Je l'ai vérifié, et c'est bien
interdit, mais si la fonction était l'instanciation d'un
template, il serait tout à fait légal, tant que le programmeur
se servait pas de la valeur par défaut.


En effet, j'ai testé avec Comeau Online, et ceci :

template<class T> void fct(T& p = 0);
template<class T> void fct(T& p) {
}
int main() {
int a;
fct<int>(a);
}

Compile sans problème à ma plus grande surprise.
L'initialisation d'une référence devant toujours être pour moi une lvalue,
je ne vois pas pourquoi ceci compile, même si on n'instancie pas fct de
manière à ce qu'elle utilise la valeur par défaut. Vu qu'il est _possible_
d'instancier fct de manière à avoir ce comportement, ca me parait être une
erreur.
Par contre, vu que :

template<class T> void fct(long &p = 0);
template<class T> void fct(long& p) {
}
int main() {
int a;
fct<int>(a);
}

Ne compile pas pour la même raison que le code de départ avec Comeau online,
je me pose une question, y-a-t-il un type en C++ pour lequel
l'initialisation d'une référence à 0 est valide ? et donc ce que je dis sur
les lvalues serait faux pour ce type ? car si il rejette le premier dans le
cas d'un paramètre T inconnu et pas le second, c'est que ca doit être
permis pour un type et donc que ce code *pourrait* être valide quelque
part. Me tromperai-je ? J'ai toujours eu du mal avec les « bizarreries »
des templates et je suis souvent étonné par le comportement des
compilateurs...

Mais si je fais ça :
Fct();
Quelle référence de variable Fct reçoit-elle ? un temporaire ?
si oui, l'appel à Fct(a) passe-t-il aussi par un temporaire ?
si oui pourquoi ?


L'appel à Fct(a) n'a pas à ma connaissance à passer par un
temporaire, vu que c'est un passage par référence.


Sauf que dans son exemple, le paramètre réel n'avait pas le même
type que le paramètre formel. Donc, il y a une conversion, qui
donne un temporaire.


Ah oui pardon j'avais zappé ca !

Anthony
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra



Avatar
kanze
Anthony Fleury wrote:

[...]
Ce n'est pas si évident. Je l'ai vérifié, et c'est bien
interdit, mais si la fonction était l'instanciation d'un
template, il serait tout à fait légal, tant que le
programmeur se servait pas de la valeur par défaut.


En effet, j'ai testé avec Comeau Online, et ceci :

template<class T> void fct(T& p = 0);
template<class T> void fct(T& p) {
}
int main() {
int a;
fct<int>(a);
}

Compile sans problème à ma plus grande surprise.


Pourquoi à ta surprise ? Il y a des choses semblabes dans la
bibliothèque standard -- des paramètres par défaut qui prenent
un T(), par exemple, alors qu'on peut très bien instantier le
template sur une classe sans constructeur par défaut.

L'initialisation d'une référence devant toujours être pour moi
une lvalue, je ne vois pas pourquoi ceci compile, même si on
n'instancie pas fct de manière à ce qu'elle utilise la valeur
par défaut.


Et moi, je ne vois pas pourquoi ça ne compilera pas, du moment
que je ne me sers pas de quelque chose d'illégal. C'est un
principe important des templates, dont on fait beaucoup usage
dans la bibliothèque standard : non seulement dans des
paramètres par défaut, mais par exemple dans l'operator-> des
itérateurs.

Vu qu'il est _possible_ d'instancier fct de manière à avoir ce
comportement, ca me parait être une erreur.


Du moment que c'est possible de faire une erreur, c'est une
erreur ? Ça ne laisserait plus grand chose au C++.

Par contre, vu que :

template<class T> void fct(long &p = 0);
template<class T> void fct(long& p) {
}
int main() {
int a;
fct<int>(a);
}

Ne compile pas pour la même raison que le code de départ avec
Comeau online, je me pose une question, y-a-t-il un type en
C++ pour lequel l'initialisation d'une référence à 0 est
valide ?


Non.

et donc ce que je dis sur les lvalues serait faux
pour ce type ?


On a effectivement un paramètre par défaut qui sert à rien,
parce qu'il ne peut jamais servir.

car si il rejette le premier dans le cas d'un paramètre T
inconnu et pas le second, c'est que ca doit être permis pour
un type et donc que ce code *pourrait* être valide quelque
part. Me tromperai-je ?


Oui. La norme n'exige pas de tel analyse de la part du
compilateur. Dans le cas d'un non-template, le type est connu,
et la norme dit que si on ne peut pas initialiser le type avec
le paramètre par défaut, c'est une erreur. Dans le cas des
templates, le type n'est pas connu d'office. Alors, on a voulu
laisser la liberté de fournir la fonctionnalité pour les types
où ça en a un sens, sans pour autant provoquer l'erreur où ça
n'en a pas. C'est un principe de base : une fonctionalité d'un
template qui ne sert pas ne serait pas instantier, et donc ne
provoquera pas d'erreur. Donc, les itérateurs ont un ->, bien
que ça n'a un sens que pour les types de classe, et ça ne
provoque pas d'erreur si tu instancies l'itérateur avec un
value_type d'int, dans la mésure que tu ne te sers pas de
l'opérateur. De la même façon, un des constructeurs de
std::vector a un paramètre par défaut de T(), ce qui n'empèche
pas que tu puisses instantier std::vector sur un type qui n'a
pas de constructeur par défaut.

Dans le cas que tu présentes, évidemment, le paramètre par
défaut ne pourrait jamais servir, quelque soit le type
d'instantiation. Mais la norme n'exige pas que le compilateur
fasse cet analyse (qui est simple ici, mais qui pourrait être
fort compliqué dans certains cas). Ta fonction templatée est
légale, et tant que tu t'en sers d'une façon légale (avec un
paramètre lvalue long), le code est parfaitement légal ; un
compilateur n'a pas le droit de le réfuser.

--
James Kanze GABI Software
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