Namespaces
Variants

Implicit conversions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Implicit conversions
static_cast
const_cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Les conversions implicites sont effectuées chaque fois qu'une expression d'un certain type T1 est utilisée dans un contexte qui n'accepte pas ce type, mais qui accepte un autre type T2 ; en particulier :

  • lorsque l'expression est utilisée comme argument lors de l'appel d'une fonction déclarée avec T2 comme paramètre ;
  • lorsque l'expression est utilisée comme opérande avec un opérateur qui attend T2 ;
  • lors de l'initialisation d'un nouvel objet de type T2 , y compris l'instruction return dans une fonction retournant T2 ;
  • lorsque l'expression est utilisée dans une instruction switch ( T2 est un type intégral) ;
  • lorsque l'expression est utilisée dans une instruction if ou une boucle ( T2 est bool ).

Le programme est bien formé (se compile) uniquement s'il existe une séquence de conversion implicite non ambiguë de T1 vers T2 .

S'il existe plusieurs surcharges de la fonction ou de l'opérateur appelé, après la construction de la séquence de conversion implicite de T1 vers chaque T2 disponible, les règles de résolution de surcharge déterminent quelle surcharge est compilée.

Note : dans les expressions arithmétiques, le type de destination pour les conversions implicites des opérandes des opérateurs binaires est déterminé par un ensemble distinct de règles : usual arithmetic conversions .

Table des matières

Ordre des conversions

La séquence de conversion implicite se compose des éléments suivants, dans cet ordre :

1) zéro ou une séquence de conversion standard ;
2) zéro ou une conversion définie par l'utilisateur ;
3) zéro ou une séquence de conversion standard (uniquement si une conversion définie par l'utilisateur est utilisée).

Lorsqu'on considère l'argument d'un constructeur ou d'une fonction de conversion définie par l'utilisateur, une seule séquence de conversion standard est autorisée (sinon les conversions définies par l'utilisateur pourraient être effectivement chaînées). Lors de la conversion d'un type non-classe vers un autre type non-classe, seule une séquence de conversion standard est autorisée.

Une séquence de conversion standard se compose des éléments suivants, dans cet ordre :

1) zéro ou une conversion de l'ensemble suivant :
  • conversion lvalue-en-rvalue ,
  • conversion tableau-en-pointeur , et
  • conversion fonction-en-pointeur ;
2) zéro ou une numeric promotion ou numeric conversion ;
3) zéro ou une conversion de pointeur de fonction ;
(depuis C++17)
4) zéro ou une qualification conversion .

Une conversion définie par l'utilisateur consiste en zéro ou un appel de constructeur de conversion non-explicite à un seul argument ou de fonction de conversion non-explicite conversion function .

Une expression e est dite implicitement convertible vers T2 si et seulement si T2 peut être initialisé par copie à partir de e , c'est-à-dire que la déclaration T2 t = e ; est bien formée (peut être compilée), pour un t temporaire inventé. Notez que ceci est différent de l'initialisation directe ( T2 t ( e ) ), où les constructeurs explicites et les fonctions de conversion seraient également considérés.

Conversions contextuelles

Dans les contextes suivants, le type bool est attendu et la conversion implicite est effectuée si la déclaration bool t ( e ) ; est bien formée (c'est-à-dire qu'une fonction de conversion explicite telle que explicit T :: operator bool ( ) const ; est considérée). Une telle expression e est dite contextuellement convertie en bool .

  • l'expression de contrôle de if , while , for ;
  • les opérandes des opérateurs logiques intégrés ! , && et || ;
  • le premier opérande de l'opérateur conditionnel ?: ;
  • le prédicat dans une déclaration static_assert ;
  • l'expression dans un spécificateur noexcept ;
(depuis C++20)
(depuis C++11)

Dans les contextes suivants, un type spécifique au contexte T est attendu, et l'expression e de type classe E n'est autorisée que si

(jusqu'à C++14)
  • il y a exactement un type T parmi les types autorisés tel que E a des fonctions de conversion non-explicites dont les types de retour sont (éventuellement qualifiés cv) T ou référence vers (éventuellement qualifiés cv) T , et
  • e est implicitement convertible en T .
(depuis C++14)

Une telle expression e est dite implicitement convertie contextuellement vers le type spécifié T . Notez que les fonctions de conversion explicites ne sont pas considérées, même si elles sont prises en compte dans les conversions contextuelles vers bool . (depuis C++11)

  • l'argument de l' delete-expression ( T est un type pointeur vers objet quelconque) ;
  • expression constante intégrale , où une classe littérale est utilisée ( T est un type intégral ou énumération non-scopée quelconque, la fonction de conversion définie par l'utilisateur sélectionnée doit être constexpr ) ;
  • l'expression de contrôle de l'instruction switch ( T est un type intégral ou énumération quelconque).
#include <cassert>
template<typename T>
class zero_init
{
    T val;
public:
    zero_init() : val(static_cast<T>(0)) {}
    zero_init(T val) : val(val) {}
    operator T&() { return val; }
    operator T() const { return val; }
};
int main()
{
    zero_init<int> i;
    assert(i == 0);
    i = 7;
    assert(i == 7);
    switch (i) {}     // erreur jusqu'à C++14 (plus d'une fonction de conversion)
                      // OK depuis C++14 (les deux fonctions convertissent vers le même type int)
    switch (i + 0) {} // toujours correct (conversion implicite)
}

Transformations de valeurs

Les transformations de valeur sont des conversions qui modifient la catégorie de valeur d'une expression. Elles se produisent chaque fois qu'une expression apparaît comme opérande d'un opérateur qui attend une expression d'une catégorie de valeur différente :

  • Chaque fois qu'une glvalue apparaît comme opérande d'un opérateur qui nécessite une prvalue pour cet opérande, les conversions standard lvalue-to-rvalue , array-to-pointer , ou function-to-pointer sont appliquées pour convertir l'expression en une prvalue.
  • Sauf indication contraire, chaque fois qu'une prvalue apparaît comme opérande d'un opérateur qui attend une glvalue pour cet opérande, la conversion de matérialisation temporaire est appliquée pour convertir l'expression en xvalue.
(since C++17)

Conversion Lvalue en Rvalue

Une lvalue (jusqu'en C++11) Une glvalue (depuis C++11) de tout type non-fonction, non-tableau T peut être implicitement convertie en une rvalue (jusqu'en C++11) une prvalue (depuis C++11) :

  • Si T n'est pas un type classe, le type de la rvalue (jusqu'en C++11) prvalue (depuis C++11) est la version non qualifiée cv de T .
  • Sinon, le type de la rvalue (jusqu'en C++11) prvalue (depuis C++11) est T .

Si une conversion de lvalue en rvalue à partir d'un type incomplet est requise par un programme, ce programme est non conforme.

Étant donné l'objet auquel la lvalue (until C++11) glvalue (since C++11) fait référence comme obj :

  • Lorsqu'une conversion lvalue-vers-rvalue se produit dans l'opérande de sizeof , la valeur contenue dans obj n'est pas accédée, car cet opérateur n'évalue pas son opérande.
  • Le résultat de la conversion est la valeur contenue dans obj . Si l'un de T et le type de obj est un type entier signé, et l'autre est le type entier non signé correspondant, le résultat est la valeur de type T avec la même représentation de valeur que obj .
(jusqu'à C++11)
  • Lorsqu'une conversion lvalue-vers-rvalue est appliquée à une expression E , la valeur contenue dans obj n'est pas accédée si :
  • Le résultat de la conversion est déterminé comme suit :
  • Si T est (éventuellement qualifié cv) std::nullptr_t , le résultat est une constante de pointeur nul . obj n'est pas accédé par la conversion, donc il n'y a pas d'effet secondaire même si T est qualifié volatile, et la glvalue peut référencer un membre inactif d'une union.
  • Sinon, si T est un type classe :
(jusqu'à C++17)
(depuis C++17)
  • Sinon, si obj contient une valeur de pointeur invalide, le comportement est défini par l'implémentation.
  • Sinon, si les bits dans la représentation de valeur de obj ne sont pas valides pour le type de obj , le comportement est indéfini.
  • Sinon, obj est lu, et (depuis C++20) le résultat est la valeur contenue dans obj . Si l'un de T et le type de obj est un type entier signé, et l'autre est le type entier non signé correspondant, le résultat est la valeur de type T avec la même représentation de valeur que obj .
(depuis C++11)

Cette conversion modélise l'action de lecture d'une valeur depuis un emplacement mémoire vers un registre de processeur.

Conversion tableau-vers-pointeur

Un lvalue ou rvalue de type « tableau de N T » ou « tableau de limite inconnue de T » peut être implicitement converti en un prvalue de type « pointeur vers T ». Si le tableau est un prvalue, une matérialisation temporaire se produit. (depuis C++17) Le pointeur résultant fait référence au premier élément du tableau (voir Dégénérescence tableau-vers-pointeur pour plus de détails).

Conversion fonction-vers-pointeur

Une lvalue de type fonction peut être implicitement convertie en un prvalue pointeur vers cette fonction . Cela ne s'applique pas aux fonctions membres non statiques car les lvalues qui se réfèrent aux fonctions membres non statiques n'existent pas.

Matérialisation temporaire

Une prvalue de tout type complet T peut être convertie en une xvalue du même type T . Cette conversion initialise un objet temporaire de type T à partir de la prvalue en évaluant la prvalue avec l'objet temporaire comme objet résultat, et produit une xvalue désignant l'objet temporaire.

Si T est un type classe ou un tableau de type classe, il doit avoir un destructeur accessible et non supprimé.

struct S { int m; };
int i = S().m; // member access expects glvalue as of C++17;
               // S() prvalue is converted to xvalue

La matérialisation temporaire se produit dans les situations suivantes :

Notez que la matérialisation temporaire ne se produit pas lors de l'initialisation d'un objet à partir d'une prvalue du même type (par initialisation directe ou initialisation par copie ) : cet objet est initialisé directement à partir de l'initialiseur. Cela garantit "l'élision de copie garantie".

(depuis C++17)

Promotion intégrale

prvalues des petits types entiers (tels que char ) et des types énumération non délimités peuvent être convertis en prvalues de types entiers plus grands (tels que int ). En particulier, les opérateurs arithmétiques n'acceptent pas les types plus petits que int comme arguments, et les promotions entières sont automatiquement appliquées après la conversion lvalue-vers-rvalue, si applicable. Cette conversion préserve toujours la valeur.

Les conversions implicites suivantes dans cette section sont classées comme promotions intégrales .

Notez que pour un type source donné, le type de destination de la promotion intégrale est unique, et toutes les autres conversions ne sont pas des promotions. Par exemple, la résolution de surcharge choisit char -> int (promotion) plutôt que char -> short (conversion).

Promotion des types entiers

Une prvalue de type bool peut être convertie en une prvalue de type int , où false devient 0 et true devient 1 .

Pour une prvalue val de type intégral T sauf bool :

1) Si val est le résultat d'une conversion lvalue-vers-rvalue appliquée à un champ de bits ,
  • val peut être converti en une prvalue de type int si int peut représenter toutes les valeurs du champ de bits ;
  • sinon, val peut être converti en unsigned int si unsigned int peut représenter toutes les valeurs du champ de bits ;
  • sinon, val peut être converti selon les règles spécifiées dans le point (3).
2) Sinon ( val n'est pas converti à partir d'un champ de bits),
  • si T est char8_t , (depuis C++20) char16_t , char32_t ou (depuis C++11) wchar_t , val peut être converti selon les règles spécifiées au point (3) ;
  • sinon, si le rang de conversion entier de T est inférieur au rang de int :
  • val peut être converti en une prvalue de type int si int peut représenter toutes les valeurs de T ;
  • sinon, val peut être converti en une prvalue de type unsigned int .
3) Dans les cas spécifiés par le point (1) (un champ de bits converti ne tenant pas dans unsigned int ) ou le point (2) ( T est l'un des types de caractères donnés), val peut être converti en une prvalue du premier des types suivants qui peut représenter toutes les valeurs de son type sous-jacent :
  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • le type sous-jacent de T
(depuis C++11)

Promotion des types énumération

Une valeur prvalue d'un type d'énumération non-scopée enumeration dont le type sous-jacent n'est pas fixe peut être convertie en une valeur prvalue du premier type de la liste suivante capable de contenir toute leur plage de valeurs :

  • int
  • unsigned int
  • long
  • unsigned long
  • son rang de conversion entier est supérieur au rang de long long ,
  • son rang de conversion entier est le plus bas parmi tous les types entiers étendus, et
  • il est signé s'il existe deux types avec le rang de conversion entier le plus bas parmi tous les types entiers étendus.
(depuis C++11)


Une valeur prvalue d'un type énumération non scopée dont le type sous-jacent est fixe peut être convertie en son type sous-jacent. De plus, si le type sous-jacent est également soumis à la promotion entière, vers le type sous-jacent promu. La conversion vers le type sous-jacent non promu est meilleure pour les besoins de la résolution de surcharge .

(since C++11)

Promotion des nombres à virgule flottante

Une prvalue de type float peut être convertie en une prvalue de type double . La valeur ne change pas.

Cette conversion est appelée floating-point promotion .

Conversions numériques

Contrairement aux promotions, les conversions numériques peuvent modifier les valeurs, avec une perte de précision potentielle.

Conversions intégrales

Une prvalue d'un type entier ou d'un type d'énumération non délimitée peut être convertie en tout autre type entier. Si la conversion est listée sous les promotions entières, il s'agit d'une promotion et non d'une conversion.

  • Si le type de destination est non signé, la valeur résultante est la plus petite valeur non signée égale à la valeur source modulo 2 n
    n est le nombre de bits utilisés pour représenter le type de destination.
  • C'est-à-dire que, selon que le type de destination est plus large ou plus étroit, les entiers signés sont étendus par le signe [1] ou tronqués, et les entiers non signés sont étendus par des zéros ou tronqués respectivement.
  • Si le type de destination est signé, la valeur ne change pas si l'entier source peut être représenté dans le type de destination. Sinon, le résultat est défini par l'implémentation (jusqu'à C++20) la valeur unique du type de destination égale à la valeur source modulo 2 n
    n est le nombre de bits utilisés pour représenter le type de destination
    (depuis C++20)
    (notez que ceci est différent du dépassement d'entier signé en arithmétique , qui est indéfini).
  • Si le type source est bool , la valeur false est convertie en zéro et la valeur true est convertie en la valeur un du type de destination (notez que si le type de destination est int , ceci est une promotion d'entier, pas une conversion d'entier).
  • Si le type de destination est bool , ceci est une conversion booléenne (voir ci-dessous).
  1. Ceci s'applique uniquement si l'arithmétique est en complément à deux, ce qui n'est requis que pour les types entiers de largeur exacte . Notez cependant qu'actuellement, toutes les plateformes avec un compilateur C++ utilisent l'arithmétique en complément à deux.

Conversions en virgule flottante

Une prvalue d'un type à virgule flottante peut être convertie en une prvalue de tout autre type à virgule flottante.

(jusqu'en C++23)

Une prvalue d'un type à virgule flottante peut être convertie en une prvalue de tout autre type à virgule flottante ayant un rang de conversion à virgule flottante supérieur ou égal.

Une prvalue d'un type à virgule flottante standard peut être convertie en une prvalue de tout autre type à virgule flottante standard.

static_cast peut être utilisé pour convertir explicitement une prvalue de type à virgule flottante en tout autre type à virgule flottante.

(depuis C++23)

Si la conversion est listée sous les promotions en virgule flottante, il s'agit d'une promotion et non d'une conversion.

  • Si la valeur source peut être représentée exactement dans le type de destination, elle ne change pas.
  • Si la valeur source est comprise entre deux valeurs représentables du type de destination, le résultat est l'une de ces deux valeurs (il est défini par l'implémentation laquelle, bien que si l'arithmétique IEEE est supportée, l'arrondi par défaut est au plus proche ).
  • Sinon, le comportement est indéfini.

Conversions flottant–entier

Une prvalue de type virgule flottante peut être convertie en une prvalue de n'importe quel type entier. La partie fractionnaire est tronquée, c'est-à-dire que la partie fractionnaire est ignorée.

  • Si la valeur tronquée ne peut pas être contenue dans le type de destination, le comportement est indéfini (même lorsque le type de destination est non signé, l'arithmétique modulaire ne s'applique pas).
  • Si le type de destination est bool , il s'agit d'une conversion booléenne (voir ci-dessous ).

Une valeur prvalue de type entier ou énumération non délimitée peut être convertie en une valeur prvalue de tout type à virgule flottante. Le résultat est exact si possible.

  • Si la valeur peut tenir dans le type de destination mais ne peut pas être représentée exactement, il est défini par l'implémentation si la valeur représentable la plus proche supérieure ou inférieure sera sélectionnée, bien que si l'arithmétique IEEE est supportée, l'arrondi par défaut au plus près .
  • Si la valeur ne peut pas tenir dans le type de destination, le comportement est indéfini.
  • Si le type source est bool , la valeur false est convertie en zéro, et la valeur true est convertie en un.

Conversions de pointeurs

Une constante de pointeur nul peut être convertie en n'importe quel type de pointeur, et le résultat est la valeur de pointeur nul de ce type. Une telle conversion (appelée conversion de pointeur nul ) est autorisée pour convertir vers un type qualifié cv comme une conversion unique, c'est-à-dire qu'elle n'est pas considérée comme une combinaison de conversions numériques et de qualifications.

Un prvalue pointeur vers n'importe quel type d'objet (éventuellement qualifié cv) T peut être converti en un prvalue pointeur vers void (identique qualification cv). Le pointeur résultant représente le même emplacement en mémoire que la valeur du pointeur original.

  • Si le pointeur original est une valeur de pointeur nul, le résultat est une valeur de pointeur nul du type de destination.

Une prvalue ptr de type « pointeur vers (éventuellement qualifié-cv) Derived » peut être convertie en une prvalue de type « pointeur vers (éventuellement qualifié-cv) Base », où Base est une classe de base de Derived , et Derived est un type de classe complet . Si Base est inaccessible ou ambiguë, le programme est mal formé.

  • Si ptr est une valeur de pointeur nul, le résultat est également une valeur de pointeur nul.
  • Sinon, si Base est une classe de base virtuelle de Derived et que ptr ne pointe pas vers un objet dont le type est similaire à Derived et qui se trouve dans sa durée de vie ou dans sa période de construction ou de destruction, le comportement est indéfini.
  • Sinon, le résultat est un pointeur vers le sous-objet de classe de base de l'objet de classe dérivée.

Conversions pointeur-vers-membre

Une constante de pointeur nul peut être convertie en n'importe quel type pointeur-vers-membre, et le résultat est la valeur de pointeur de membre nul de ce type. Une telle conversion (appelée conversion de pointeur de membre nul ) est autorisée pour convertir en un type cv-qualifié comme une conversion unique, c'est-à-dire qu'elle n'est pas considérée comme une combinaison de conversions numériques et de qualifications.

Une prvalue de type « pointeur vers membre de Base de type (éventuellement qualifié cv) T » peut être convertie en une prvalue de type « pointeur vers membre de Derived de type (identique qualifié cv) T », où Base est une classe de base de Derived , et Derived est un type de classe complet. Si Base est une base inaccessible, ambiguë ou virtuelle de Derived ou est une base d'une base virtuelle intermédiaire de Derived , le programme est mal formé.

  • Si Derived ne contient pas le membre original et n'est pas une classe de base de la classe contenant le membre original, le comportement est indéfini.
  • Sinon, le pointeur résultant peut être déréférencé avec un objet Derived , et il accédera au membre dans le sous-objet de base Base de cet objet Derived .

Conversions booléennes

Une prvalue de types entiers, à virgule flottante, énumération non délimitée, pointeur et pointeur-vers-membre peut être convertie en une prvalue de type bool .

La valeur zéro (pour les types entiers, à virgule flottante et les énumérations non délimités) ainsi que les valeurs de pointeur nul et de pointeur-vers-membre nul deviennent false . Toutes les autres valeurs deviennent true .

Dans le contexte d'une initialisation directe , un objet bool peut être initialisé à partir d'une prvalue de type std::nullptr_t , y compris nullptr . La valeur résultante est false . Cependant, cela n'est pas considéré comme une conversion implicite.

(depuis C++11)

Conversions de qualification

D'une manière générale :

  • Une prvalue de type pointeur vers un type qualifié-cv T peut être convertie en une prvalue pointeur vers le même type T plus qualifié-cv (en d'autres termes, la constance et la volatilité peuvent être ajoutées).
  • Une prvalue de type pointeur vers membre d'un type qualifié-cv T dans la classe X peut être convertie en une prvalue pointeur vers membre d'un type plus qualifié-cv T dans la classe X .

La définition formelle de « qualification conversion » est donnée ci-dessous .

Types similaires

Informellement, deux types sont similaires si, en ignorant les qualifications cv de niveau supérieur :

  • ils sont du même type ; ou
  • ils sont tous deux des pointeurs, et les types pointés sont similaires ; ou
  • ils sont tous deux des pointeurs vers un membre de la même classe, et les types des membres pointés sont similaires ; ou
  • ils sont tous deux des tableaux et les types d'éléments du tableau sont similaires.

Par exemple :

  • const int * const * et int ** sont similaires ;
  • int ( * ) ( int * ) et int ( * ) ( const int * ) ne sont pas similaires ;
  • const int ( * ) ( int * ) et int ( * ) ( int * ) ne sont pas similaires ;
  • int ( * ) ( int * const ) et int ( * ) ( int * ) sont similaires (ils sont du même type) ;
  • std:: pair < int , int > et std:: pair < const int , int > ne sont pas similaires.

Formellement, la similarité de types est définie en termes de décomposition de qualification.

Une décomposition de qualification d'un type T est une séquence de composants cv_i et P_i telle que T est « cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U » pour un entier non négatif n , où

  • chaque cv_i est un ensemble de const et volatile , et
  • chaque P_i est
  • "pointeur vers",
  • "pointeur vers membre de la classe C_i de type",
  • "tableau de N_i ", ou
  • "tableau de limite inconnue de".

Si P_i désigne un tableau, les qualificateurs cv cv_i+1 sur le type d'élément sont également pris comme les qualificateurs cv cv_i du tableau.

// T est « pointeur vers pointeur vers const int », il a 3 décompositions de qualification :
// n = 0 -> cv_0 est vide, U est « pointeur vers pointeur vers const int »
// n = 1 -> cv_0 est vide, P_0 est « pointeur vers »,
//          cv_1 est vide, U est « pointeur vers const int »
// n = 2 -> cv_0 est vide, P_0 est « pointeur vers »,
//          cv_1 est vide, P_1 est « pointeur vers »,
//          cv_2 est « const », U est « int »
using T = const int**;
// substituer l'un des types suivants à U donne l'une des décompositions :
// U = U0 -> la décomposition avec n = 0 : U0
// U = U1 -> la décomposition avec n = 1 : pointeur vers [U1]
// U = U2 -> la décomposition avec n = 2 : pointeur vers [pointeur vers [const U2]]
using U2 = int;
using U1 = const U2*;
using U0 = U1*;

Deux types T1 et T2 sont similaires s'il existe une décomposition de qualification pour chacun d'eux, où toutes les conditions suivantes sont satisfaites pour les deux décompositions de qualification :

  • Ils ont le même n .
  • Les types désignés par U sont identiques.
  • Les composants correspondants P_i sont identiques ou l'un est « tableau de N_i » et l'autre est « tableau de limite inconnue de » (depuis C++20) pour tous les i .
// la décomposition de qualification avec n = 2 :
// pointeur vers [pointeur volatile vers [const int]]
using T1 = const int* volatile *;
// la décomposition de qualification avec n = 2 :
// pointeur const vers [pointeur vers [int]]
using T2 = int** const;
// Pour les deux décompositions de qualification ci-dessus
// bien que cv_0, cv_1 et cv_2 soient tous différents,
// ils ont le même n, U, P_0 et P_1,
// par conséquent les types T1 et T2 sont similaires.

Combinaison des qualifications cv

Dans la description ci-dessous, la décomposition de qualification la plus longue du type Tn est notée Dn , et ses composantes sont notées cvn_i et Pn_i .

Une expression prvalue de type T1 peut être convertie en type T2 si toutes les conditions suivantes sont satisfaites :

  • T1 et T2 sont similaires.
  • Pour tout i non nul, si const est dans cv1_i , alors const est aussi dans cv2_i , et de même pour volatile .
  • Pour tout i non nul, si cv1_i et cv2_i sont différents, alors const est dans cv2_k pour tout k dans [ 1 , i ) .

Le type combiné de qualifications de deux types T1 et T2 est un type T3 similaire à T1 tel que

  • cv3_0 est vide,
  • pour tout i non nul, cv3_i est l'union de cv1_i et cv2_i , et
  • si cv3_i est différent de cv1_i ou c2_i , alors const est ajouté à cv3_k pour tout k dans [ 1 , i ) .
(jusqu'en C++20)

Le type combiné de qualifications de deux types T1 et T2 est un type T3 similaire à T1 , où D3 satisfait toutes les conditions suivantes :

  • cv3_0 est vide.
  • Pour tout i non nul, cv3_i est l'union de cv1_i et cv2_i .
  • Si P1_i ou P2_i est « tableau de taille inconnue de », P3_i est « tableau de taille inconnue de », sinon c'est P1_i .
  • Si cv3_i est différent de cv1_i ou cv2_i , ou P3_i est différent de P1_i ou P2_i , alors const est ajouté à cv3_k pour tout k dans [ 1 , i ) .

Une prvalue de type T1 peut être convertie en type T2 si le type combiné de qualifications de T1 et T2 est T2 sans qualificatifs cv.

(depuis C++20)
// décomposition de qualification la plus longue de T1 (n = 2) :
// pointeur vers [pointeur vers [char]]
using T1 = char**;
// décomposition de qualification la plus longue de T2 (n = 2) :
// pointeur vers [pointeur vers [const char]]
using T2 = const char**;
// Détermination des composants cv3_i et T_i de D3 (n = 2) :
// cv3_1 = vide (union de cv1_1 vide et cv2_1 vide)
// cv3_2 = "const" (union de cv1_2 vide et "const" cv2_2)
// P3_0 = "pointeur vers" (aucun tableau de taille inconnue, utiliser P1_0)
// P3_1 = "pointeur vers" (aucun tableau de taille inconnue, utiliser P1_1)
// Tous les composants sauf cv_2 sont identiques, cv3_2 est différent de cv1_2,
// donc ajouter "const" à cv3_k pour chaque k dans [1, 2) : cv3_1 devient "const".
// T3 est "pointeur vers pointeur const vers const char", c'est-à-dire const char* const *.
using T3 = /* le type combiné par qualification de T1 et T2 */;
int main()
{
    const char c = 'c';
    char* pc;
    T1 ppc = &pc;
    T2 pcc = ppc; // Erreur : T3 n'est pas identique à T2 sans qualification cv,
                  //        pas de conversion implicite.
    *pcc = &c;
    *pc = 'C';    // Si l'assignation erronée ci-dessus est autorisée,
                  // l'objet const "c" pourrait être modifié.
}

Notez que dans le langage de programmation C, const / volatile ne peut être ajouté qu'au premier niveau :

char** p = 0;
char * const* p1 = p;       // OK en C et C++
const char* const * p2 = p; // erreur en C, OK en C++

Conversions de pointeurs de fonction

  • Une prvalue de type pointeur vers fonction non-lançant d'exception peut être convertie en une prvalue de type pointeur vers fonction potentiellement lançant d'exception.
  • Une prvalue de type pointeur vers fonction membre non-lançant d'exception peut être convertie en une prvalue de type pointeur vers fonction membre potentiellement lançant d'exception.
void (*p)();
void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function
struct S
{
    typedef void (*p)();
    operator p();
};
void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function
(depuis C++17)

Le problème du booléen sécurisé

Jusqu'à C++11, concevoir une classe qui devait être utilisable dans des contextes booléens (par exemple if ( obj ) { ... } ) posait un problème : étant donné une fonction de conversion définie par l'utilisateur, telle que T :: operator bool ( ) const ; , la séquence de conversion implicite autorisait une séquence de conversion standard supplémentaire après cet appel de fonction, ce qui signifie que le bool résultant pouvait être converti en int , permettant du code tel que obj << 1 ; ou int i = obj ; .

Une solution précoce pour cela peut être observée dans std::basic_ios , qui définit initialement operator void * , de sorte que le code tel que if ( std:: cin ) { ... } compile car void * est convertible en bool , mais int n = std:: cout ; ne compile pas car void * n'est pas convertible en int . Cela permet toujours à du code absurde tel que delete std:: cout ; de compiler.

De nombreuses bibliothèques tierces pré-C++11 étaient conçues avec une solution plus élaborée, connue sous le nom d' idiome Safe Bool . std::basic_ios permettait également cet idiome via LWG issue 468 , et operator void * a été remplacé (voir les notes ).

Depuis C++11, la conversion explicite vers bool peut également être utilisée pour résoudre le problème du safe bool.

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement corrigé
CWG 170 C++98 le comportement des conversions pointeur-vers-membre était ambigu
si la classe dérivée ne possède pas le membre d'origine
clarifié
CWG 172 C++98 le type énumération était promu en fonction de son type sous-jacent en fonction de sa plage de valeurs à la place
CWG 330
( N4261 )
C++98 la conversion de double * const ( * p ) [ 3 ]
vers double const * const ( * p ) [ 3 ] était invalide
rendue valide
CWG 519 C++98 les valeurs de pointeur nul n'étaient pas garanties d'être
préservées lors de la conversion vers un autre type de pointeur
toujours préservées
CWG 616 C++98 le comportement de la conversion lvalue en rvalue de
tout objet non initialisé et des objets pointeur
avec des valeurs invalides était toujours indéfini
indéterminé unsigned char
est autorisé ; l'utilisation de pointeurs invalides
est définie par l'implémentation
CWG 685 C++98 le type sous-jacent d'un type énumération n'était
pas priorisé dans la promotion intégrale s'il est fixe
priorisé
CWG 707 C++98 la conversion d'entier en virgule flottante
avait un comportement défini dans tous les cas
le comportement est indéfini si
la valeur convertie est
hors de la plage de destination
CWG 1423 C++11 std::nullptr_t était convertible en bool
dans l'initialisation directe et par copie
initialisation directe uniquement
CWG 1773 C++11 une expression de nom qui apparaît dans une expression potentiellement évaluée
telle que l'objet nommé n'est pas odr-utilisé pourrait
quand même être évaluée lors d'une conversion lvalue-vers-rvalue
non évalué
CWG 1781 C++11 std::nullptr_t vers bool était considéré comme une conversion
implicite même s'il n'est valide que pour l'initialisation directe
n'est plus considéré
comme une conversion implicite
CWG 1787 C++98 le comportement de la lecture d'un
unsigned char indéterminé mis en cache dans un registre était indéfini
rendu bien défini
CWG 1981 C++11 conversions contextuelles considérées comme des fonctions de conversion explicites non considéré
CWG 2140 C++11 il n'était pas clair si les conversions lvalue-vers-rvalue à partir
std::nullptr_t lvalues récupèrent ces lvalues en mémoire
non récupérées
CWG 2310 C++98 pour les conversions pointeur de dérivé vers base et
les conversions pointeur-vers-membre de base vers dérivé,
le type de classe dérivée pouvait être incomplet
doit être complet
CWG 2484 C++20 char8_t et char16_t avaient des stratégies de promotion
entière différentes, mais ils peuvent les accommoder toutes deux
char8_t devrait être promu
de la même manière que char16_t
CWG 2485 C++98 les promotions entières impliquant des champs de bits n'étaient pas spécifiées correctement a amélioré la spécification
CWG 2813 C++23 la matérialisation temporaire se produirait lorsqu'une fonction membre
à objet explicite d'une prvalue de classe est invoquée
ne se produira pas
dans ce cas
CWG 2861 C++98 un pointeur vers un objet inaccessible par le type pouvait être
converti en un pointeur vers un sous-objet de classe de base
le comportement est
indéfini dans ce cas
CWG 2879 C++17 la conversion de matérialisation temporaire était appliquée sur une prvalue
comme opérande d'un opérateur qui attend une glvalue
non appliquée dans certains cas
CWG 2899 C++98 les conversions lvalue-en-rvalue pouvaient être appliquées aux lvalues
désignant des objets avec des représentations de valeur invalides
le comportement est
indéfini dans ce cas
CWG 2901 C++98 le résultat de la conversion lvalue-vers-rvalue d'une unsigned int
lvalue se référant à un objet int avec la valeur - 1 était ambigu
clarifié

Voir aussi

Documentation C pour Conversions implicites