programmez-en-d/cast.whata

452 lines
22 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Conversions de Types, [c cast]"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Les variables doivent être compatibles avec les expressions dans lesquelles elles apparaissent. Comme vous l'avez peut-être déjà remarqué dans les programmes que nous avons vus jusqu'à maintenant, D est un langage [* statiquement typé], ce qui veut dire que la compatibilité des types est vérifiée lors de la compilation.
Toutes les expressions que nous avons écrites jusqu'à maintenant avaient des types compatibles parce que dans le cas contraire le code aurait été rejeté par le compilateur. Ce qui suit est un exemple de code qui a des types incompatibles~ :
[code=d <<<
char[] tranche;
writeln(tranche + 5); // ← ERREUR de compilation
>>>]
Le compilateur rejette le code à cause de l'incompatibilité des types [c char~[~]] et [c int] pour l'opération d'addition~ :
[output <<<
Error: incompatible types for ((tranche) + (5)): 'char[]' and 'int'
>>>]
L'incompatibilité des types ne veut pas dire que les types sont différents~ ; des types différents peuvent en effet être utilisés dans les expressions en toute sécurité. Par exemple, une variable [c int] peut être utilisée à la place d'une valeur [c double] sans problème~ :
[code=d <<<
double somme = 1.25;
int increment = 3;
somme += increment;
>>>]
Même si [c somme] et [c increment] sont de types différents, ce code est correct parce qu'incrémenter une variable [c double] avec une valeur [c int] est légal.
[ = Conversion automatique des types
Les conversions automatiques de types sont aussi appelées [* conversions implicites de types].
Même si [c double] et [c int] sont des types compatibles dans l'expression précédente, l'opération d'addition doit toujours être évaluée comme un type spécifique au niveau du microprocesseur. Comme nous l'avons vu dans le [[doc:virgule_flottante | chapitre sur les virgules flottantes]], le type 64 bits [c double] est [* plus large] que le type 32 bits [c int]. De plus, toute valeur qui peut être représentée dans une variable [c int] peut être représentée dans une variable [c double].
Quand le compilateur rencontre une expression qui implique des types incompatibles, il convertit d'abord les parties de l'expression vers un type commun et évalue ensuite l'expression entière. Les conversions automatiques qui sont effectuées par le compilateur se font dans une direction qui évite toute perte de données. Par exemple, [c double] peut contenir toute valeur que [c int] peut contenir mais l'inverse n'est pas vrai. Dans l'exemple précédent, l'opération [c +=] peut fonctionner parce que toute valeur [c int] peut être convertie vers [c double] en toute sécurité.
La valeur résultant d'une conversion automatique est toujours une variable anonyme et elle est souvent temporaire. La valeur originale ne change pas. Par exemple, dans notre cas la conversion automatique lors de l'opération [c +=] ne change pas le type de [c increment]~ ; celui-ci reste un [c int]. Par contre, une valeur temporaire de type [c double] est construite à partir de la valeur d'[c increment]. La conversion qui se déroule en arrière-plan est équivalente au code suivant~ :
[code=d <<<
{
double une_valeur_double_anonyme = increment;
somme += une_valeur_double_anonyme;
}
>>>]
Le compilateur convertit la valeur [c int] vers une valeur temporaire [c double] et utilise cette valeur dans l'opération. Dans cet exemple, la variable temporaire vit uniquement pendant l'opération [c +=].
Les conversions automatiques ne sont pas limitées aux opérations arithmétiques. Il y a d'autres cas où les types sont convertis vers d'autres types automatiquement. Tant que les conversions de types sont correctes, le compilateur en profite pour utiliser les valeurs dans les expressions. Par exemple, une valeur [c byte] peut être passée en argument pour un paramètre [c int]~ :
[code=d <<<
void fonc(int nombre)
{
// ...
}
void main()
{
byte petiteValeur = 7;
fonc(petiteValeur); // conversion automatique
}
>>>]
Dans ce code, une valeur [c int] temporaire est d'abord construite et ensuite la fonction est appelée avec cette valeur.
[ = Promotions entières
Les valeurs des types qui sont dans la colonne de gauche du tableau suivant ne font jamais partie d'expressions arithmétiques en tant que tels. Chaque type est d'abord promu vers le type de la colonne à droite du tableau.
|= De |= Vers |
| [c bool] | [c int] |
| [c byte] | [c int] |
| [c ubyte] | [c int] |
| [c short] | [c int] |
| [c ushort] | [c int] |
| [c char] | [c int] |
| [c wchar] | [c int] |
| [c dchar] | [c uint] |
Les promotions entières sont également appliquées aux valeurs [c enum].
Les raisons d'être des promotions entières sont aussi historiques (où les règles viennent du C) que techniques, le type arithmétique naturel du processeur étant [c int]. Par exemple, même si les deux variables suivantes sont [c ubyte], l'opération d'addition n'est effectuée qu'après que les deux valeurs aient été promues en [c int]~ :
[code=d <<<
ubyte a = 1;
ubyte b = 2;
writeln(typeof(a + b).stringof); // l'addition ne se fait pas en ubyte
>>>]
La sortie :
[output <<<
int
>>>]
Notez que les types respectifs des variables [c a] et [c b] ne changent pas~ ; seules leurs valeurs sont temporairement promues en [c int] pour la durée de l'opération d'addition.
]
[ = Conversions arithmétiques
Il y a d'autres règles de conversions qui sont appliquées pour les opérations arithmétiques. En général, les conversions arithmétiques automatiques sont appliquées dans la direction sûre~ : du type le plus [* étroit] au type le plus [* large]. Même si cette règle est facile à retenir et est correcte dans la plupart des cas, les règles de conversions automatiques sont très compliquées et, dans les cas de conversion signé vers non signé, sont de potentielles causes de bogues.
Les règles de conversions arithmétiques sont les suivantes~ :
# Si l'une des valeurs est [c real], alors l'autre valeur est convertie en [c real].
# Sinon, si l'une des valeurs est [c double], alors l'autre valeur est convertie en [c double].
# Sinon, si l'une des valeurs est [c float], alors l'autre valeur est convertie en [c float].
# Sinon, les promotions entières sont appliquées selon le tableau de la section précédente, et les règles suivantes sont ensuite appliquées~ :
# Si les deux types sont les mêmes, alors aucune autre étape n'est nécessaire.
# Si le type signé est plus large que le type non signé, alors la valeur non signée est convertie vers le type signé.
# Sinon le type signé est converti vers le type non signé.
Malheureusement, cette dernière règle peut être à l'origine de bogues subtils~ :
[code=d <<<
int a = 0;
int b = 1;
size_t c = 0;
writeln(a - b + c); // Résultat surprenant !
>>>]
Étonnement, le résultat n'est pas [c -1] mais [c size_t.max]~ :
[output <<<
18446744073709551615
>>>]
Même si l'on s'attendrait à ce que [c 0 - 1 + 0] donne [c -1], selon les règles ci-avant, le type de l'expression entière est [c size_t] et non [c int] ; et comme [c size_t] ne peut pas représenter des valeurs négatives, le résultat soupasse et devient [c size_t.max].
]
[ = Conversions [c const]
Comme nous l'avons vu précédemment dans le [[doc:parametres_de_fonctions | chapitre sur les paramètres de fonctions], les types référence peuvent être automatiquement convertis vers le [c const] du même type. Les conversions vers [c const] sont sûres parce que la largeur du type ne change pas et [c const] est une promesse de ne pas modifier la variable~ :
[code=d <<<
dchar[] entre_accolades(const dchar[] texte)
{
return "{" ~ texte ~ "}";
}
void main()
{
dchar[] bienvenue;
bienvenue ~= "bonjour le monde";
entre_accolades(bienvenue);
}
>>>]
La variable mutable [c bienvenue] est automatiquement convertie vers un [c const dchar~[~]] lorsqu'elle est passée à la fonction [c entre_accolades].
Comme nous l'avons aussi vu précédemment, la conversion inverse n'est pas automatique. Une référence [c const] n'est pas automatiquement convertie vers une référence mutable~ :
[code=d <<<
dchar[] entre_accolades(const dchar[] texte)
{
dchar[] argument = texte; // ERREUR de compilation
// ...
}
>>>]
Notez que cette partie concerne uniquement les références~ ; comme les variables de types valeur sont copiées, il n'est de toute façon pas possible d'assigner l'original à travers la copie~ :
[code=d <<<
const int nombreDeCoins = 4;
int laCopie = nombreDeCoins; // compile (type valeur)
>>>]
Cette conversion de [c const] vers mutable est légale parce que la copie n'est pas une référence vers l'original.
]
[ = Conversions [c immutable]
[c immutable] indiquant qu'une variable ne peut jamais changer, aucune conversion depuis ou vers [c immutable] n'est automatique~ :
[code=d <<<
string a = "salut"; // caractères immuables
char[] b = a; // Erreur de compilation
string c = b; // Erreur de compilation
>>>]
Comme pour la section précédente (conversions de [c const]), cette partie ne concerne que les types référence. Comme les variables de types valeur sont de toute façon copiées, les conversions depuis et vers [c immutable] sont valides~ :
[code=d <<<
immutable a = 10;
int b = a; // compile (type valeur)
>>>]
]
[ = Conversions [c enum]
Comme nous l'avons vu dans le [[doc:enum | chapitre sur les énumérations]], [c enum] définit des constantes nommées~ :
[code=d <<<
enum Couleur { pique, coeur, carreau, trefle }
>>>]
Souvenez-vous que comme aucune valeur n'est spécifiée explicitement, les valeurs des membres de l'énumération commencent par zéro et sont automatiquement incrémentées de 1 en 1. Ainsi, la valeur de [c Couleur.trefle] est [c 3].
Les valeurs [c enum] sont automatiquement converties vers des types entiers. Par exemple, la valeur de [c Couleur.coeur] est comprise comme 1 dans le calcul suivant et le résultat est 11~ :
[code=d <<<
int resultat = 10 + Couleur.coeur;
assert(resultat == 11);
>>>]
La conversion inverse n'est pas automatique~ : les valeurs entières ne sont pas automatiquement converties vers les valeurs [c enum] correspondantes. Par exemple, on pourrait s'attendre que la variable [c couleur] ci-dessous prenne pour valeur [c Couleur.carreau], mais le code ne peut pas être compilé~ :
[code=d <<<
Couleur couleur = 2; // ERREUR de compilation
>>>]
Comme nous le verrons juste après, les conversions depuis les entiers vers les valeurs [c enum] sont néanmoins possibles mais elle doivent être explicites.
]
[ = Conversions booléennes
[c false] et [c true] sont automatiquement convertis vers 0 et 1, respectivement~ :
[code=d <<<
int a = false;
assert(a == 0);
int b = true;
assert(b == 1);
>>>]
La conversion inverse est également automatique mais seulement pour deux valeurs spéciales. Les littéraux 0 et 1 sont convertis automatiquement vers [c false] et [c true], respectivement~ :
[code=d <<<
bool a = 0;
assert(!a); // false
bool b = 1;
assert(b); // true
>>>]
Les autres valeurs littérales ne peuvent pas être converties vers [c bool] automatiquement~ :
[code=d <<<
bool b = 2; // ERREUR de compilation
>>>]
]
[ = Conversions automatiques vers [c bool] dans les instructions conditionnelles
Certaines instructions utilisent des expressions logiques~ : [c if], [c while], etc. Pour les expressions logiques de telles instructions, en plus de [c bool], la plupart des autres types peuvent également être utilisés. La valeur zéro est automatiquement convertie vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
[code=d <<<
int i;
// ...
if (i) { // ← valeur int utilisée comme une expression logique
// ... 'i' ne vaut pas zéro
} else {
// ... 'i' vaut zero
}
>>>]
De manière similaire, les références nulles ([c null]) sont automatiquement converties vers [c false] et les références non nulles sont automatiquement converties vers [c true]. Cela facilite la vérification qu'une référence n'est pas nulle avant de s'en servir~ :
[code=d <<<
int[] a;
// ...
if (a) { // ← conversion booléenne automatique
// ... non null; 'a' peut être utilisé...
} else {
// ... null; 'a' ne peut pas être utilisé...
}
>>>]
]
[ = Conversions de types explicites
Comme nous venons de le voir, il y a des cas où les conversions automatiques ne sont pas possibles~ :
- les conversions depuis des types plus larges vers des types plus restreints~ ;
- les conversions depuis [c const] vers mutable~ ;
- les conversions [c immutable]~ ;
- les conversions depuis les entiers vers les valeurs [c enum].
S'il sait qu'une telle conversion est sûre, le programmeur peut explicitement demander une conversion de types avec l'une des trois méthodes suivantes~ :
- en appelant la fonction [c std.conv.to]~ ;
- en appelant la fonction [c std.exception.assumeUnique]~ ;
- en utilisant l'opérateur [c cast].
[ = La fonction [c to] pour la plupart des conversions
La fonction [c to], que nous avons déjà utilisée surtout pour convertir des valeurs vers [c string], peut en fait être utilisée pour beaucoup d'autres types. Sa syntaxe complète est la suivante~ :
[code=d <<<
to!(TypeDestination)(valeur)
>>>]
Puisque c'est un modèle, on peut utiliser la fonction [c to] avec la notation raccourcie des paramètres de modèles~ : quand le type de destination ne consiste qu'en un seul [i=FIXME token] (en général, qu'un seul mot), elle peut être appelée sans la première paire de parenthèses :
[code=d <<<
to!TypeDestination(valeur)
>>>]
Le programme suivant essaie de convertir une valeur [c double] vers [c short] et une chaîne vers [c int]~ :
[code=d <<<
void main()
{
double d = -1.75;
short s = d; // ERREUR de compilation
int i = "42"; // ERREUR de compilation
}
>>>]
Comme toutes les valeurs [c double] ne peuvent pas être représentées par [c short] et comme toute chaîne ne peut pas être représentée comme un entier, ces conversions ne sont pas automatiques. Quand le programmeur sait que les conversions sont en fait sûres ou que les conséquences d'une mauvaise conversion sont acceptables, les types peuvent être convertis par [c to()]~ :
[code=d <<<
import std.conv;
void main()
{
double d = -1.75;
short s = to!short(d);
assert(s == -1);
int i = to!int("42");
assert(i == 42);
}
>>>]
Notez que puisque [c short] ne peut pas représenter les valeurs fractionnaires, la valeur convertie est -1.
La fonction [c to] est sûre~ : elle lève une exception quand une conversion n'est pas possible.
]
[ = [c assumeUnique()] pour des conversions [c immutable] rapides
La fonction [c to] peut également effectuer des conversions [c immutable]~ :
[code=d <<<
int[] tranche = [ 10, 20, 30 ];
auto trancheImmuable = to!(immutable int[])(tranche);
>>>]
Pour garantir que les éléments de [c trancheImmuable] ne changeront jamais, elle ne peut pas partager ses éléments avec [c tranche]. Pour cette raison, la fonction [c to] crée une tranche nouvelle avec des éléments [c immutable]. Sinon, les modifications des éléments de [c tranche] entraîneraient également la modification des éléments de [c trancheImmuable]. Ce comportement est le même avec la propriété [c .idup] des tableaux.
Nous pouvons voir que les éléments de [c trancheImmuable] sont en fait des copies des éléments de [c tranche] en regardant l'adresse de leurs premiers éléments~ :
[code=d <<<
assert(&(tranche[0]) != &(trancheImmuable[0]));
>>>]
Parfois, cette copie n'est pas nécessaire et peut ralentir le programme de façon négligeable dans certain cas. Par exemple, considérons la fonction suivante qui prend une tranche [c immutable]~ :
[code=d <<<
void calculer(immutable int[] coordonnees)
{
// ...
}
void main()
{
int[] nombres;
nombres ~= 10;
// ... autres diverses modifications ...
nombres[0] = 42;
calculer(nombres); // ERREUR de compilation
}
>>>]
Ce programme ne peut pas être compilé parce que l'appelant ne passe pas un argument [c immutable] à la fonction [c calculer]. Comme nous l'avons vu auparavant, une tranche [c immutable] peut être créée par la fonction [c to]~ :
[code=d <<<
import std.conv;
// ...
auto nombresImmuables = to!(immutable int[])(nombres);
calculer(nombresImmuables); // ← maintenant, compile
>>>]
Cependant, si on n'a besoin de [c nombres] que pour produire cet argument et qu'elle ne sera jamais utilisée après l'appel de la fonction, copier ses éléments vers [c nombresImmuables] n'est pas nécessaire. La fonction [c assumeUnique] rend les éléments d'une tranche immuables sans les copier~ :
[code=d <<<
import std.exception;
// ...
auto nombresImmuables = assumeUnique(nombres);
calculer(nombresImmuables);
assert(nombres is null); // La tranche originale devient nulle
>>>]
[c assumeUnique] retourne une nouvelle tranche qui fournit un accès immuable aux éléments existants. Elle rend aussi la tranche originale [c null] pour empêcher les éléments d'être accidentellement modifiés.
]
[ = L'opérateur [c cast]
Les fonctions [c to()] et [c assumeUnique()] utilisent toutes deux l'opérateur de conversion [c cast], qui est également disponible pour le programmeur.
L'opérateur [c cast] prend le type de destination entre parenthèses~ :
[code=d <<<
cast(TypeDestination)valeur
>>>]
[c cast] est utile pour les conversions que la fonction [c to()] ne peut pas effectuer de façon sûre. [c to()] ne réalise pas les conversions suivantes~ :
[code=d <<<
Couleur couleur = to!Couleur(2); // ERREUR de compilation
bool b = to!bool(2); //ERREUR de compilation
>>>]
Parfois, seul le programmeur peut savoir si une valeur entière correspond à une valeur [c enum] valide ou s'il fait sens de traiter une valeur entière comme un booléen. L'opérateur [c cast] peut être utilisé quand on sait que la conversion est correcte selon la logique du programme~ :
[code=d <<<
Couleur couleur = cast(Couleur)2;
assert(couleur == Couleur.carreau);
bool b = cast(bool)2;
assert(b);
>>>]
Bien que cela arrive rarement, certaines interfaces de bibliothèques C demandent de stocker une valeur de pointeur dans un type non-pointeur. S'il est garanti que la conversion préserve la bonne valeur, [c cast] peut aussi effectuer la conversion entre des types pointeur et non-pointeur~ :
[code=d <<<
size_t valeurPointeurSauvée = cast(size_t) p;
// ...
int * p2 = cast(int*)valeurPointeurSauvée;
>>>]
]
]
[ = Résumé
- Les conversions de types automatiques sont principalement celles qui se font dans le sens sûr~ : du type le plus restreint au type le plus large et de mutable vers [c const].
- Cependant, les conversions vers les types non signés peuvent avoir des effets surprenant parce que les types non signés ne peuvent pas avoir de valeur négatives.
- Les types [c enum] peuvent automatiquement être convertis vers des valeurs entières mais la conversion inverse n'est pas automatique.
- [c false] et [c true] sont automatiquement convertis vers [c 0] et [c 1], respectivement. De manière similaire, les valeurs nulles (dans le sens «~ zéro~ ») sont automatiquement converties vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
- Les références nulles sont automatiquement converties vers [c false] et les références non nulles sont automatiquement converties vers [c true].
- La fonction [c to] couvre la plupart des conversions explicites.
- La fonction [c assumeUnique] convertit vers [c immutable] sans copier.
- L'opérateur [c cast] est l'outil de conversion le plus puissant.
]