programmez-en-d/lvalue_rvalue.whata

167 lines
6.8 KiB
Plaintext

[set
title = "Lvalues et Rvalues"
partAs = chapitre
translator = "Olivier Pisano"
]
[comment <<< Note laissée par Raphaël Jake >>>]
[p Ndt~ : | Dans cette traduction, on a choisi de ne pas traduire les termes «~ rvalue~ » et «~ lvalue~ », mais il se peut qu'à certain endroit, «~ valeur à droite (du égal)~ » et «~ valeur à gauche (du égal)~ » soient utilisées occasionellement. Consultez la dernière partie du chapitre pour plus d'explication sur ces noms. ]
La valeur de chaque expression est, soit une ''lvalue'' soit une ''rvalue''. Une façon simple de différencier les deux est de voir les ''lvalues'' comme de véritables variables (y compris les éléments de tableaux et de tableaux associatifs) et les ''rvalues'' comme des résultats temporaires d'expressions (y compris les littéraux).
Comme démonstration, la première expression [c writeln] ci-après n'utilise que des lvalues et l'autre que des rvalues~ :
[code=d <<<
import std.stdio;
void main()
{
int i;
immutable(int) imm;
auto tab = [ 1 ];
auto ta = [ 10: "dix" ];
/* tous les arguments suivants sont des lvalues. */
writeln(i, // variable mutable
imm, // variable immutable
tab, // tableau
tab[0], // élément de tableau
ta[10]); // élément de tableau associatif
// etc.
enum message = "Bonjour";
/* tous les arguments suivants sont des rvalues. */
writeln(42, // un littéral
message, // une constante manifeste
i + 1, // une valeur temporaire
calculer(i)); // valeur de retour d'une fonction
// etc.
}
int calculer(int i)
{
return i * 2;
}
>>>]
[ = Les limitations des rvalues
Comparées aux lvalues, les rvalues présentent les trois limitations suivantes~ :
[ = Les rvalues n'ont pas d'adresse
Une lvalue peut se trouver à un emplacement dans la mémoire, une rvalue non.
Par exemple, il est impossible de prendre l'adresse de l'expression rvalue [c a + b] dans le programme suivant~ :
[code=d <<<
import std.stdio;
void main()
{
int a;
int b;
readf(" %s ", &a); // compile
readf(" %s ", &(a + b)); // Erreur: ne compile pas
}
>>>]
Erreur : a + b n'est pas une lvalue
]
[ = Les rvalues ne peuvent se voir affecter de nouvelles valeurs
Il est possible d'affecter une nouvelle valeur à une lvalue si elle est mutable. Ce n'est pas possible pour les rvalues~ :
a = 1; // compile
(a + b) = 2; // Erreur: ne compile pas
Erreur : a + b n'est pas une lvalue
]
]
[ = Les rvalues ne peuvent pas être passées par référence à des fonctions
Une lvalue peut être passée à une fonction qui prend un argument par référence. Ce n'est pas possible avec une rvalue~ :
[code=d <<<
void incrementerDeDix(ref int valeur)
{
value += 10;
}
// ...
incrementerDeDix(a); // compile
incrementerDeDix(a + b); // Erreur: ne compile pas
>>>]
[output <<<
Erreur : la fonction incrementerDeDix(ref int valeur) n'est pas appelable avec les types d'arguments (int)
>>>]
La raison principale de cette limitation est que les fonctions prenant un paramètre [c ref] pourraient stocker la référence pour une utilisation ultérieure, à un moment où la rvalue ne serait plus disponible.
À la différence de langages comme C++, en D une rvalue ne peut pas être passée à une fonction même si cette fonction ne modifie pas l'argument (par exemple en prenant une référence à [c const])~ :
[code=d <<<
void afficher(ref const(int) valeur)
{
writeln(valeur);
}
// ...
afficher(a); // compile
afficher(a + b); // Erreur: ne compile pas
>>>]
[output <<<
Erreur : la fonction afficher(ref const(int) valeur) n'est pas appelable en utilisant les types d'arguments (int)
>>>]
]
[ = Les paramètres auto ref pour accepter à la fois des rvalues et des lvalues
Comme mentionné dans le chapitre précédent, les paramètres auto ref des fonctions templates peuvent prendre à la fois des lvalues et des rvalues.
Quand l'argument est une lvalue, auto ref signifie par référence. À l'inverse, puisque les rvalues ne peuvent être pas être passées par référence, quand l'argument est une rvalue, auto ref signifie par copie. Pour que le compilateur puisse générer les différents codes pour ces deux cas de figure, la fonction doit être un template.
Nous verrons les templates dans un chapitre ultérieur. Pour l'instant, veuillez accepter que les parenthèses vides surlignées ci-après signifient que la définition de fonction suivante est une fonction template.
[code=d <<<
void incrementerDeDix()(auto ref int valeur)
{
/* AVERTISSEMENT: Le paramètre peut être une copie si l'argument est
une rvalue. Cela veut dire que la modification suivante peut ne pas
être visible par l'appelant. */
valeur += 10;
}
void main()
{
int a;
int b;
incrementerDeDix(a); // lvalue; passée par référence
incrementerDeDix(a + b); // rvalue; passée par valeur
}
>>>]
Comme mentionné dans le commentaire du code ci-avant, la mutation du paramètre peut ne pas être observable par l'appelant. Pour cette raison, auto ref est essentiellement utilisé dans des situations où le paramètre n'est pas modifié~ ; la plupart de temps comme [c auto ref const].
]
[ = Terminologie
Les noms lvalue et rvalue ne représentent pas précisément les caractéristiques de ces deux types de valeurs. Les initiales ''l'' et ''r'' viennent de l'anglais ''left'' et ''right'', faisant référence au côté gauche et au côte droit d'une expression de l'opérateur d'affectation~ :
- En partant du principe qu'elle est mutable, une lvalue peut être l'expression du côté gauche d'une opération d'affectation.
- Une rvalue ne peut pas être le côte gauche d'une opération d'affectation.
Les termes «~ valeur gauche~ » et «~ valeur droite~ » sont trompeurs, car en général, lvalues et rvalues peuvent à la fois se trouver de chaque côté d'une opération d'affectation~ :
[code=d <<<
// rvalue 'a + b' sur la gauche, lvalue 'a' sur la droite :
tableau[a + b] = a;
>>>]
]