Conversion explicite en "style" C

    Le langage C/C++ est un langage fortement typé qui effectue cependant un certain nombre de tranformations implicites appelées promotions (entières ou en virgule flottantes) ou conversions implicites.

    Dans le langage C, et compatible en C++, la conversion explicite d'un objet en un autre type, si elle est possible, fait précéder l'identificateur de l'objet du type dans lequel il doit être converti, le type étant parenthésé :
 
(type_cible) objet

    En voici quelques exemples.

Exemple 1
 
double d1, d2, d3;
int    i = 3;
int    j = 10;

d1 = i / j;                    // {1}
d2 = (double) i / (double) j;  // {2}
d3 =          i / (double) j;  // {3}

    A la ligne {1}, c'est une division entière qui est effectuée, le résultat (0) étant ensuite affecté à d1.

    A la ligne {2}, c'est une division réelle qui est effectuée, chaque opérante étant explicitement converti en double, le résultat (0.3) étant ensuite affecté à d2.

    A la ligne {3}, c'est une division réelle qui est effectuée, le second opérante étant explicitement converti en double, le premier est implicitement converti par le compilateur en double, le résultat (0.3) étant ensuite affecté à d3.

Exemple 2
 
char * pMsg = "Coucou";
cout << "Message : " << pMsg
     << "; Adresse : " << (void *) pMsg << endl;

    Rappelons que l'opérateur << est surchargé pour tous les types de base : si le second paramètre est un pointeur de caractères, l'injecteur interprète le désir de l'utilisateur comme étant d'afficher la chaîne correspondante (au sens des chaînes en C : NTCTS). Pour tout autre type de pointeur, l'injecteur interprète le désir de l'utilisateur comme étant d'afficher l'adresse de l'objet. Pour afficher l'adresse d'une chaîne de caractères (au sens C), il faut donc convertir le type du pointeur en n'importe quel autre type de pointeur (ici (void *)). Le résultat serait équivalent s'il avait été converti en (int *).

Exemple 3
 
#define SIG_ERR ((void (*)(int)) -1)

    La macro SIG_ERR correspond à la valeur numérique -1, mais ne doit pas être considérée comme un objet de type int, mais comme un pointeur de fonction ayant un paramètre int et ne renvoyant pas de résultat. La valeur de la macro est, de plus, complètement parenthésée. Cette macro est couramment utilisée dans les traitements des signaux sous Unix.

Remarque :

    Cette écriture est obsolète en C++ et doit donc être évitée le plus possible. Elle doit cependant être connue, car elle est fréquemment rencontrée, en particulier lors de l'utilisation de fonctions C en C++.

Conversion explicite en "style" C++

    La conversion explicite d'un objet en un autre, si elle est possible, respecte la syntaxe suivante (aussi adoptée en ADA) :
 
type_cible (objet)

    En voici quelques exemples.

Exemple 1
 
double d;
int    i = 3;
int    j = 10;

d = double (i) / double (j);

Exemple 2
 
char * pMsg = "Coucou";
typedef void * p_void_t;

cout << "Message : " << pMsg
     << "; Adresse : " << p_void_t (pMsg) << endl;

Exemple 3
 
typedef (void (*sighandler_t)(int);
const sighandler_t SIG_ERR = sighandler_t (-1);

    En C++, la syntaxe de la conversion nécessite que le type_cible soit un type nommé et non un type anonyme (et composé), d'où l'utilisation de typedef.

Remarques :

  1. Si la syntaxe de conversion du C++ et d'ADA est la même, en C++ elle laisse clairement apparaître qu'il s'agit d'un appel explicite au constructeur du type_cible, avec pour paramètre l'objet à convertir (même si ce constructeur n'existe pas vraiment).
La seconde ligne correspond au remplacement d'une macro de C (présentée plus haut) en une constante C++ de type "pointeur de fonction". Nous avons cependant conservé l'écriture en majuscule, pour rester conforme à la macro souvent utilisée en programmation système.

Surcharge de l'opérateur type() dans une classe

    La surcharge de cet opérateur prend la forme générale :
 
class CX
{
  public :
    operator type () { /* ... */ }

}; // CX

    Elle permet d'utiliser un objet de "type" CX partout où un objet de "type" type est attendu.

Premier exemple :
 
class CRationnel
{
    int m_Num;
    int m_Denom;

  public :
    CRationnel (const int Num, const int Denom)
        : m_Num (Num), m_Denom (Denom) {}

    operator double () { return double (m_Num) / double (m_Denom); }

}; // CRationnel

qui peut être utilisé de la façon suivante :
 
CRationnel R (10, 3);
cout << R << endl;           // Affichage de 3.333333

Autre exemple :

    La bibliothèque standard comporte une classe assez analogue à la classe ios suivante :
 
class ios  // classe hypothétique (en réalité ios_base)
{
  protected :
    int state;           // bits d'état
  public :
    int eof  () const;   // vrai si fin de fichier visible
    int fail () const;   // vrai si une opération a échoué,
    int bad  () const;   // vrai si une erreur s'est produite
    int good () const;   // vrai si aucun bit d'état n'est positionné 
                         //      (précédente opération d'E/S réussie)
    operator bool () { return good(); }

    // ...

}; // ios

    L'opérateur bool() permet d'utiliser un flux partout où un booléen est attendu. De plus, ce booléen est vrai lorsque le flux est valide, c'est-à-dire lorsque la précédente opération effectuée dessus n'a pas échoué et qu'une nouvelle opération peut être effectuée.

    L'utilisation la plus fréquente est la suivante :
 
for ( ; cin >> i; ) { /* ... */ }

    Comme l'injecteur, l'extracteur >> renvoie le flux sur lequel a été effectuée l'opération. Celui-ci n'est pas de type bool, comme l'exige la syntaxe de la deuxième expression de l'instruction for. Avant de diagnostiquer une erreur de compilation, le compilateur cherche parmi les conversions implicites du C++, si l'une d'elles ne peut pas être utilisée. Ce n'est pas le cas ici, aucune conversion implicite ne transforme un flux istream (donc ios) en bool. Il recherche ensuite s'il connaît une opération permettant de passer d'un istream (donc ios) à un bool. Il utilise alors l'opérateur bool().

Conversion et transtypage

    Il ne faut pas confondre les deux opérations : la conversion, telle qu'elle vient d'être développée ici, correspond à la création d'un nouvel objet d'un type dont la représentation interne en machine peut n'avoir aucun rapport avec celle de l'objet à convertir. Par exemple, un int est codé en binaire sur 4 octets, alors qu'un double est codé sur 8 octets avec une mantisse, une caractéristique, et un bit de signe. De même il n'y a aucun rapport entre la représentation interne d'un booléen et celle d'un flux ...

    Au contraire, un transtypage est une demande au compilateur de considérer que le type de l'objet en est en réalité un autre.

    A la lumière de ces indications, l'exemple 2 ci-dessus devrait plutôt être un transtypage, et être écrit ainsi :

Exemple 2
 
char * pMsg = "Coucou";
cout << "Message : " << pMsg
     << "; Adresse : " << reinterpret_cast <void *> (pMsg) << endl;