Implicit conversions
Implizite Konvertierungen werden durchgeführt, wenn ein Ausdruck eines Typs
T1
in einem Kontext verwendet wird, der diesen Typ nicht akzeptiert, aber einen anderen Typ
T2
akzeptiert; insbesondere:
-
wenn der Ausdruck als Argument beim Aufruf einer Funktion verwendet wird, die mit
T2als Parameter deklariert ist; -
wenn der Ausdruck als Operand mit einem Operator verwendet wird, der
T2erwartet; -
beim Initialisieren eines neuen Objekts vom Typ
T2, einschließlichreturn-Anweisung in einer Funktion, dieT2zurückgibt; -
wenn der Ausdruck in einer
switch
-Anweisung verwendet wird (
T2ist integraler Typ); -
wenn der Ausdruck in einer
if
-Anweisung oder einer Schleife verwendet wird (
T2ist bool ).
Das Programm ist wohlgeformt (kompiliert) nur wenn es eine eindeutige
implicit conversion sequence
von
T1
zu
T2
gibt.
Wenn es mehrere Überladungen der aufgerufenen Funktion oder des Operators gibt, wird nach dem Aufbau der impliziten Konvertierungssequenz von
T1
zu jedem verfügbaren
T2
durch die Regeln der
Überladungsauflösung
entschieden, welche Überladung kompiliert wird.
Hinweis: In arithmetischen Ausdrücken wird der Zieltyp für die impliziten Konvertierungen der Operanden binärer Operatoren durch einen separaten Regelsatz bestimmt: usual arithmetic conversions .
Inhaltsverzeichnis |
Reihenfolge der Konvertierungen
Die implizite Konvertierungssequenz besteht aus den folgenden Schritten in dieser Reihenfolge:
Bei der Betrachtung des Arguments für einen Konstruktor oder eine benutzerdefinierte Konvertierungsfunktion ist nur eine Standardkonvertierungssequenz zulässig (andernfalls könnten benutzerdefinierte Konvertierungen effektiv verkettet werden). Bei der Konvertierung von einem Nicht-Klassentyp zu einem anderen Nicht-Klassentyp ist nur eine Standardkonvertierungssequenz zulässig.
Eine Standardkonvertierungssequenz besteht aus den folgenden Schritten, in dieser Reihenfolge:
- lvalue-to-rvalue conversion ,
- array-to-pointer conversion , und
- function-to-pointer conversion ;
|
3)
null oder eine
Funktionszeigerkonvertierung
;
|
(since C++17) |
Eine benutzerdefinierte Konvertierung besteht aus null oder einem nicht-expliziten Einzelargument- converting constructor oder nicht-expliziten conversion function Aufruf.
Ein Ausdruck
e
wird als
implizit konvertierbar zu
T2
bezeichnet genau dann, wenn
T2
durch
Copy-Initialisierung
aus
e
initialisiert werden kann, das heißt die Deklaration
T2 t
=
e
;
wohlgeformt ist (kompiliert werden kann), für eine erfundene temporäre Variable
t
. Beachten Sie, dass dies sich von
Direktinitialisierung
(
T2 t
(
e
)
) unterscheidet, bei der zusätzlich explizite Konstruktoren und Konvertierungsfunktionen berücksichtigt werden.
Kontextuelle Konvertierungen
|
In den folgenden Kontexten wird der Typ bool erwartet und die implizite Konvertierung wird durchgeführt, falls die Deklaration bool t ( e ) ; wohlgeformt ist (das heißt, eine explizite Konvertierungsfunktion wie explicit T :: operator bool ( ) const ; wird berücksichtigt). Ein solcher Ausdruck e wird als kontextuell zu bool konvertiert bezeichnet.
|
(seit C++11) |
In den folgenden Kontexten wird ein kontextspezifischer Typ
T
erwartet, und der Ausdruck
e
vom Klassentyp
E
ist nur zulässig, wenn
|
(bis C++14) |
|
(seit C++14) |
Ein solcher Ausdruck
e
wird als
kontextuell implizit konvertiert
zum angegebenen Typ
T
bezeichnet.
Beachten Sie, dass explizite Konvertierungsfunktionen nicht berücksichtigt werden, obwohl sie bei kontextuellen Konvertierungen zu
bool
berücksichtigt werden.
(seit C++11)
-
das Argument des
delete-Ausdrucks
(
Tist ein beliebiger Objektzeigertyp); -
integrale konstante Ausdrücke
, bei denen eine Literalklasse verwendet wird (
Tist ein beliebiger integraler oder unbegrenzter Aufzählungstyp, die ausgewählte benutzerdefinierte Konvertierungsfunktion muss constexpr sein); -
der kontrollierende Ausdruck der
switch-Anweisung (Tist ein beliebiger integraler oder Aufzählungstyp).
#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) {} // Fehler bis C++14 (mehr als eine Konvertierungsfunktion) // OK ab C++14 (beide Funktionen konvertieren zum selben Typ int) switch (i + 0) {} // immer korrekt (implizite Konvertierung) }
Werttransformationen
Werttransformationen sind Konvertierungen, die die Wertkategorie eines Ausdrucks ändern. Sie finden statt, wenn ein Ausdruck als Operand eines Operators erscheint, der einen Ausdruck einer anderen Wertkategorie erwartet:
- Immer wenn ein Glvalue als Operand eines Operators erscheint, der einen Prvalue für diesen Operanden erfordert, werden die Lvalue-zu-Rvalue -, Array-zu-Zeiger - oder Funktion-zu-Zeiger -Standardkonvertierungen angewendet, um den Ausdruck in einen Prvalue umzuwandeln.
|
(since C++17) |
Lvalue-zu-Rvalue-Konvertierung
Ein
Lvalue
(bis C++11)
Ein
Glvalue
(seit C++11)
eines beliebigen Nicht-Funktions-, Nicht-Array-Typs
T
kann implizit konvertiert werden in
einen
Rvalue
(bis C++11)
einen
Prvalue
(seit C++11)
:
-
Wenn
Tkein Klassentyp ist, ist der Typ des rvalue (until C++11) prvalue (since C++11) die cv-unqualifizierte Version vonT. -
Andernfalls ist der Typ des
rvalue
(until C++11)
prvalue
(since C++11)
T.
Wenn eine Lvalue-zu-Rvalue-Konvertierung von einem unvollständigen Typ durch ein Programm erforderlich ist, ist dieses Programm fehlerhaft.
Gegeben das Objekt, auf das die lvalue (until C++11) glvalue (since C++11) referenziert als obj :
|
(bis C++11) | ||||
|
(seit C++11) |
Diese Konvertierung modelliert den Vorgang des Lesens eines Wertes aus einer Speicheradresse in ein CPU-Register.
Array-zu-Zeiger-Konvertierung
Ein
Lvalue
oder
Rvalue
vom Typ "Array von
N
T
" oder "Array unbekannter Größe von
T
" kann implizit in einen
Prvalue
vom Typ "Zeiger auf
T
" konvertiert werden.
Wenn das Array ein Prvalue ist,
erfolgt temporäre Materialisierung
.
(seit C++17)
Der resultierende Zeiger verweist auf das erste Element des Arrays (siehe
Array-zu-Zeiger-Zerfall
für Details).
Function-to-Pointer-Konvertierung
Ein Lvalue vom Funktionstyp kann implizit in einen Prvalue Zeiger auf diese Funktion konvertiert werden. Dies gilt nicht für nicht-statische Memberfunktionen, da Lvalues, die auf nicht-statische Memberfunktionen verweisen, nicht existieren.
Temporäre Materialisierung
Ein
Prvalue
eines beliebigen vollständigen Typs
Wenn
struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue Temporäre Materialisierung tritt in den folgenden Situationen auf:
Beachten Sie, dass temporäre Materialisierung nicht auftritt, wenn ein Objekt aus einem Prvalue desselben Typs initialisiert wird (durch Direktinitialisierung oder Kopierinitialisierung ): Ein solches Objekt wird direkt vom Initialisierer initialisiert. Dies gewährleistet "garantierte Kopierelision". |
(seit C++17) |
Ganzzahlige Höherstufung
prvalues von kleinen Ganzzahltypen (wie char ) und Aufzählungstypen ohne Gültigkeitsbereich können in prvalues von größeren Ganzzahltypen (wie int ) konvertiert werden. Insbesondere akzeptieren arithmetische Operatoren keine Typen kleiner als int als Argumente, und Ganzzahlaufwertungen werden automatisch nach der Lvalue-zu-Rvalue-Konvertierung angewendet, falls zutreffend. Diese Konvertierung bewahrt stets den Wert.
Die folgenden impliziten Konvertierungen in diesem Abschnitt werden als integral promotions klassifiziert.
Beachten Sie, dass für einen gegebenen Quelltyp der Zieltyp der integralen Promotion eindeutig ist, und alle anderen Konvertierungen keine Promotionen sind. Zum Beispiel wählt overload resolution char -> int (Promotion) gegenüber char -> short (Konvertierung).
Höherstufung von ganzzahligen Typen
Ein prvalue vom Typ bool kann in einen prvalue vom Typ int konvertiert werden, wobei false zu 0 und true zu 1 wird.
Für einen Prvalue
val
eines ganzzahligen Typs
T
außer
bool
:
- val kann in einen Prvalue des Typs int konvertiert werden, falls int alle Werte des Bitfelds darstellen kann;
- andernfalls kann val in unsigned int konvertiert werden, falls unsigned int alle Werte des Bitfelds darstellen kann;
- andernfalls kann val gemäß den in Punkt (3) spezifizierten Regeln konvertiert werden.
-
wenn
Tchar8_t , (seit C++20) char16_t , char32_t oder (seit C++11) wchar_t ist, val kann gemäß den in Punkt (3) spezifizierten Regeln konvertiert werden; -
andernfalls, wenn der
Integer-Konversionsrang
von
Tniedriger ist als der Rang von int :
-
-
val
kann in einen Prvalue des Typs
int
konvertiert werden, wenn
int
alle Werte von
Tdarstellen kann; - andernfalls kann val in einen Prvalue des Typs unsigned int konvertiert werden.
-
val
kann in einen Prvalue des Typs
int
konvertiert werden, wenn
int
alle Werte von
T
ist einer der angegebenen Zeichentypen) spezifizierten Fällen kann
val
zu einem PR-Wert des ersten der folgenden Typen konvertiert werden, der alle Werte seines zugrundeliegenden Typs darstellen kann:
-
- int
- unsigned int
- long
- unsigned long
|
(seit C++11) |
Höherstufung von Aufzählungstypen
Ein prvalue eines unbegrenzten enumeration -Typs, dessen zugrundeliegender Typ nicht festgelegt ist, kann in einen prvalue des ersten Typs aus der folgenden Liste konvertiert werden, der ihren gesamten Wertebereich aufnehmen kann:
- int
- unsigned int
- long
- unsigned long
|
(seit C++11) |
|
Ein Prvalue eines unbegrenzten Aufzählungstyps mit festgelegtem zugrundeliegendem Typ kann in seinen zugrundeliegenden Typ konvertiert werden. Wenn der zugrundeliegende Typ außerdem einer Ganzzahlpromotion unterliegt, kann die Konvertierung zum promovierten zugrundeliegenden Typ erfolgen. Für die Zwecke der Überladungsauflösung ist die Konvertierung zum nicht-promovierten zugrundeliegenden Typ besser. |
(seit C++11) |
Gleitkomma-Promotion
Ein prvalue vom Typ float kann in einen prvalue vom Typ double konvertiert werden. Der Wert ändert sich dabei nicht.
Diese Konvertierung wird als floating-point promotion bezeichnet.
Numerische Konvertierungen
Im Gegensatz zu den Promotions können numerische Konvertierungen die Werte ändern, mit möglichem Präzisionsverlust.
Ganzzahlige Konvertierungen
Ein prvalue eines ganzzahligen Typs oder eines unbegrenzten Aufzählungstyps kann in jeden anderen ganzzahligen Typ konvertiert werden. Falls die Konversion unter Ganzzahl-Höherstufungen aufgeführt ist, handelt es sich um eine Höherstufung und nicht um eine Konversion.
-
Wenn der Zieltyp vorzeichenlos ist, ist der resultierende Wert der kleinste vorzeichenlose Wert, der dem Quellwert
modulo
2
n
entspricht, wobei n die Anzahl der Bits zur Darstellung des Zieltyps ist.
-
- Das heißt, abhängig davon, ob der Zieltyp breiter oder schmaler ist, werden vorzeichenbehaftete Ganzzahlen sign-erweitert [1] oder abgeschnitten und vorzeichenlose Ganzzahlen werden null-erweitert oder entsprechend abgeschnitten.
-
Wenn der Zieltyp vorzeichenbehaftet ist, ändert sich der Wert nicht, wenn die Quell-Ganzzahl im Zieltyp dargestellt werden kann. Andernfalls ist das Ergebnis
implementierungsdefiniert
(bis C++20)
der eindeutige Wert des Zieltyps, der gleich dem Quellwert modulo
2
n
ist, wobei n die Anzahl der Bits zur Darstellung des Zieltyps ist (seit C++20) (beachten Sie, dass dies sich von vorzeichenbehaftetem Ganzzahl-Überlauf unterscheidet, der undefiniert ist). - Wenn der Quelltyp bool ist, wird der Wert false in null konvertiert und der Wert true in den Wert eins des Zieltyps (beachten Sie, dass dies bei Zieltyp int eine Ganzzahl-Höherwertung, keine Ganzzahl-Konvertierung ist).
- Wenn der Zieltyp bool ist, handelt es sich um eine boolesche Konvertierung (siehe unten).
- ↑ Dies gilt nur, wenn die Arithmetik im Zweierkomplement erfolgt, was nur für die exact-width integer types erforderlich ist. Beachten Sie jedoch, dass derzeit alle Plattformen mit einem C++-Compiler Zweierkomplement-Arithmetik verwenden.
Gleitkomma-Konvertierungen
|
Ein prvalue eines Gleitkommatyps kann in einen prvalue eines beliebigen anderen Gleitkommatyps konvertiert werden. |
(bis C++23) |
|
Ein prvalue eines Gleitkommatyps kann in einen prvalue eines beliebigen anderen Gleitkommatyps mit einem größeren oder gleichen Gleitkomma-Konvertierungsrang konvertiert werden. Ein prvalue eines standardmäßigen Gleitkommatyps kann in einen prvalue eines beliebigen anderen standardmäßigen Gleitkommatyps konvertiert werden.
|
(seit C++23) |
Wenn die Konvertierung unter Gleitkomma-Promotions aufgeführt ist, handelt es sich um eine Promotion und nicht um eine Konvertierung.
- Wenn der Quellwert exakt im Zieltyp dargestellt werden kann, ändert er sich nicht.
- Wenn der Quellwert zwischen zwei darstellbaren Werten des Zieltyps liegt, ist das Ergebnis einer dieser beiden Werte (implementierungsdefiniert welcher, obwohl bei Unterstützung von IEEE-Arithmetik die Standardrundung to nearest ist).
- Andernfalls ist das Verhalten undefiniert.
Gleitkomma-Ganzzahl-Konvertierungen
Ein prvalue vom Gleitkommatyp kann in einen prvalue eines beliebigen Ganzzahltyps konvertiert werden. Der Bruchteil wird abgeschnitten, das heißt, der Bruchteil wird verworfen.
- Wenn der abgeschnittene Wert nicht in den Zieltyp passt, ist das Verhalten undefiniert (selbst wenn der Zieltyp vorzeichenlos ist, kommt Modulo-Arithmetik nicht zur Anwendung).
- Wenn der Zieltyp bool ist, handelt es sich um eine boolesche Konvertierung (siehe unten ).
Ein prvalue vom Typ Integer oder unbegrenzter Aufzählungstyp kann in einen prvalue eines beliebigen Gleitkommatyps konvertiert werden. Das Ergebnis ist exakt, wenn möglich.
- Wenn der Wert in den Zieltyp passt, aber nicht exakt dargestellt werden kann, ist implementierungsdefiniert, ob der nächsthöhere oder nächstniedrigere darstellbare Wert gewählt wird, obwohl bei Unterstützung von IEEE-Arithmetik die Rundung standardmäßig auf den nächsten Wert erfolgt.
- Wenn der Wert nicht in den Zieltyp passt, ist das Verhalten undefiniert.
- Wenn der Quelltyp bool ist, wird der Wert false in null und der Wert true in eins konvertiert.
Zeigerkonvertierungen
Ein null pointer constant kann in jeden Zeigertyp konvertiert werden, und das Ergebnis ist der Nullzeigerwert dieses Typs. Eine solche Konvertierung (bekannt als null pointer conversion ) ist als einzelne Konvertierung in einen cv-qualifizierten Typ erlaubt, das heißt, sie wird nicht als Kombination numerischer und qualifizierender Konvertierungen betrachtet.
Ein
prvalue
-Zeiger auf einen beliebigen (optional cv-qualifizierten) Objekttyp
T
kann in einen prvalue-Zeiger auf (identisch cv-qualifiziertes)
void
konvertiert werden. Der resultierende Zeiger repräsentiert denselben Speicherort wie der ursprüngliche Zeigerwert.
- Wenn der ursprüngliche Zeiger ein Nullzeigerwert ist, ist das Ergebnis ein Nullzeigerwert des Zieltyps.
Ein Prvalue
ptr
vom Typ "Zeiger auf (möglicherweise cv-qualifiziertes)
Derived
" kann in einen Prvalue vom Typ "Zeiger auf (möglicherweise cv-qualifiziertes)
Base
" konvertiert werden, wobei
Base
eine
Basisklasse
von
Derived
ist und
Derived
ein
vollständiger
Klassentyp ist. Wenn
Base
unzugänglich oder mehrdeutig ist, ist das Programm fehlerhaft.
- Wenn ptr ein Nullzeigerwert ist, ist das Ergebnis ebenfalls ein Nullzeigerwert.
-
Andernfalls, wenn
Baseeine virtuelle Basisklasse vonDerivedist und ptr nicht auf ein Objekt zeigt, dessen Typ ähnlich zuDerivedist und das sich innerhalb seiner Lebensdauer oder innerhalb seiner Konstruktions- oder Destruktionsphase befindet, ist das Verhalten undefiniert. - Andernfalls ist das Ergebnis ein Zeiger auf das Basisklassen-Subobjekt des abgeleiteten Klassenobjekts.
Pointer-zu-Member-Konvertierungen
Ein null pointer constant kann in jeden Zeiger-auf-Mitglied-Typ konvertiert werden, und das Ergebnis ist der Null-Mitglied-Zeiger-Wert dieses Typs. Eine solche Konvertierung (bekannt als Null-Mitglied-Zeiger-Konvertierung ) ist erlaubt, um in einen cv-qualifizierten Typ als einzelne Konvertierung umzuwandeln, das heißt, sie wird nicht als Kombination von numerischen und Qualifizierungskonvertierungen betrachtet.
Ein
prvalue
vom Typ "Zeiger auf Mitglied von
Base
vom Typ (möglicherweise cv-qualifiziert)
T
" kann in einen prvalue vom Typ "Zeiger auf Mitglied von
Derived
vom Typ (identisch cv-qualifiziert)
T
" konvertiert werden, wobei
Base
eine Basisklasse von
Derived
ist und
Derived
ein vollständiger Klassentyp ist. Wenn
Base
eine unzugängliche, mehrdeutige oder virtuelle Basis von
Derived
ist oder eine Basis einer intermediären virtuellen Basis von
Derived
ist, ist das Programm fehlerhaft.
-
Wenn
Deriveddas ursprüngliche Member nicht enthält und keine Basisklasse der Klasse ist, die das ursprüngliche Member enthält, ist das Verhalten undefiniert. -
Andernfalls kann der resultierende Zeiger mit einem
Derived-Objekt dereferenziert werden, und er wird auf das Member innerhalb desBase-Basisunterobjekts diesesDerived-Objekts zugreifen.
Boolesche Konvertierungen
Ein prvalue von integralen, Gleitkomma-, unbegrenzten Aufzählungstypen, Zeiger- und Zeiger-auf-Mitglieder-Typen kann in einen prvalue vom Typ bool konvertiert werden.
Der Wert Null (für Ganzzahlen, Gleitkommazahlen und unbegrenzte Aufzählungen) sowie der Nullzeiger und die Null-Zeiger-auf-Mitglieder-Werte werden zu false . Alle anderen Werte werden zu true .
|
Im Kontext einer Direktinitialisierung kann ein bool -Objekt mit einem Prvalue vom Typ std::nullptr_t initialisiert werden, einschließlich nullptr . Der resultierende Wert ist false . Dies wird jedoch nicht als implizite Konvertierung betrachtet. |
(seit C++11) |
Qualifikationskonvertierungen
Allgemein gesprochen:
-
Ein
prvalue
vom Typ Zeiger auf
cv-qualifizierten
Typ
Tkann in einen prvalue-Zeiger auf einen stärker cv-qualifizierten gleichen TypTkonvertiert werden (mit anderen Worten: Constness und Volatilität können hinzugefügt werden). -
Ein prvalue vom Typ Zeiger auf Mitglied eines cv-qualifizierten Typs
Tin KlasseXkann in einen prvalue-Zeiger auf Mitglied eines stärker cv-qualifizierten TypsTin KlasseXkonvertiert werden.
Die formale Definition von "qualification conversion" wird unten gegeben.
Ähnliche Typen
Informell sind zwei Typen ähnlich wenn, unter Vernachlässigung der Top-Level-cv-Qualifikation:
- sie sind vom gleichen Typ; oder
- sie sind beide Zeiger und die gezeigten Typen sind ähnlich; oder
- sie sind beide Zeiger auf Mitglieder derselben Klasse, und die Typen der gezeigten Mitglieder sind ähnlich; oder
- sie sind beide Arrays und die Array-Elementtypen sind ähnlich.
Zum Beispiel:
- const int * const * und int ** sind ähnlich;
- int ( * ) ( int * ) und int ( * ) ( const int * ) sind nicht ähnlich;
- const int ( * ) ( int * ) und int ( * ) ( int * ) sind nicht ähnlich;
- int ( * ) ( int * const ) und int ( * ) ( int * ) sind ähnlich (sie sind derselbe Typ);
- std:: pair < int , int > und std:: pair < const int , int > sind nicht ähnlich.
Formal wird Typähnlichkeit in Bezug auf Qualifikationszerlegung definiert.
Eine
Qualifikationszerlegung
eines Typs
T
ist eine Sequenz von Komponenten
cv_i
und
P_i
, sodass
T
„
cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U
“ für nicht-negative
n
ist, wobei
-
jedes
cv_iist eine Kombination aus const und volatile , und -
jedes
P_iist
-
- "Zeiger auf",
-
"Zeiger auf Element der Klasse
C_ivom Typ", - "Array von N_i ", oder
- "Array unbekannter Größe von".
Wenn
P_i
ein Array bezeichnet, werden die cv-Qualifizierer
cv_i+1
des Elementtyps auch als die cv-Qualifizierer
cv_i
des Arrays übernommen.
// T ist "Zeiger auf Zeiger auf const int", es hat 3 Qualifikations-Zerlegungen: // n = 0 -> cv_0 ist leer, U ist "Zeiger auf Zeiger auf const int" // n = 1 -> cv_0 ist leer, P_0 ist "Zeiger auf", // cv_1 ist leer, U ist "Zeiger auf const int" // n = 2 -> cv_0 ist leer, P_0 ist "Zeiger auf", // cv_1 ist leer, P_1 ist "Zeiger auf", // cv_2 ist "const", U ist "int" using T = const int**; // Ersetzen von U mit einem der folgenden Typen ergibt eine der Zerlegungen: // U = U0 -> die Zerlegung mit n = 0: U0 // U = U1 -> die Zerlegung mit n = 1: Zeiger auf [U1] // U = U2 -> die Zerlegung mit n = 2: Zeiger auf [Zeiger auf [const U2]] using U2 = int; using U1 = const U2*; using U0 = U1*;
Zwei Typen
T1
und
T2
sind
ähnlich
, wenn für jeden von ihnen eine Qualifikationszerlegung existiert, wobei für die beiden Qualifikationszerlegungen alle folgenden Bedingungen erfüllt sind:
- Sie haben dasselbe n .
-
Die durch
Ubezeichneten Typen sind identisch. -
Die entsprechenden
P_iKomponenten sind identisch oder eine ist "Array von N_i " und die andere ist "Array unbekannter Größe von" (seit C++20) für alle i .
// die Qualifikationszerlegung mit n = 2: // Zeiger auf [flüchtigen Zeiger auf [const int]] using T1 = const int* volatile *; // die Qualifikationszerlegung mit n = 2: // konstanter Zeiger auf [Zeiger auf [int]] using T2 = int** const; // Für die beiden obigen Qualifikationszerlegungen // obwohl cv_0, cv_1 und cv_2 alle unterschiedlich sind, // haben sie dasselbe n, U, P_0 und P_1, // daher sind die Typen T1 und T2 ähnlich.
Kombinieren von cv-Qualifizierern
In der folgenden Beschreibung bezeichnet die längste Qualifikationszerlegung des Typs
Tn
als
Dn
, und ihre Komponenten werden als
cvn_i
und
Pn_i
bezeichnet.
|
Ein prvalue-Ausdruck vom Typ
Der
qualifikationskombinierte Typ
zweier Typen
|
(bis C++20) |
|
Der
qualifikationskombinierte Typ
zweier Typen
Ein prvalue vom Typ
|
(seit C++20) |
// längste Qualifikationszerlegung von T1 (n = 2): // Zeiger auf [Zeiger auf [char]] using T1 = char**; // längste Qualifikationszerlegung von T2 (n = 2): // Zeiger auf [Zeiger auf [const char]] using T2 = const char**; // Bestimmung der cv3_i und T_i Komponenten von D3 (n = 2): // cv3_1 = leer (Vereinigung von leer cv1_1 und leer cv2_1) // cv3_2 = "const" (Vereinigung von leer cv1_2 und "const" cv2_2) // P3_0 = "Zeiger auf" (kein Array unbekannter Größe, verwende P1_0) // P3_1 = "Zeiger auf" (kein Array unbekannter Größe, verwende P1_1) // Alle Komponenten außer cv_2 sind gleich, cv3_2 unterscheidet sich von cv1_2, // daher füge "const" zu cv3_k für jedes k in [1, 2) hinzu: cv3_1 wird "const". // T3 ist "Zeiger auf const Zeiger auf const char", d.h. const char* const *. using T3 = /* der qualifikationskombinierte Typ von T1 und T2 */; int main() { const char c = 'c'; char* pc; T1 ppc = &pc; T2 pcc = ppc; // Fehler: T3 ist nicht identisch mit cv-unqualifiziertem T2, // keine implizite Konvertierung. *pcc = &c; *pc = 'C'; // Wenn die fehlerhafte Zuweisung oben erlaubt wäre, // könnte das const-Objekt "c" modifiziert werden. }
Beachten Sie, dass in der Programmiersprache C const / volatile nur auf der ersten Ebene hinzugefügt werden kann:
char** p = 0; char * const* p1 = p; // OK in C und C++ const char* const * p2 = p; // Fehler in C, OK in C++
Funktionszeiger-Konvertierungen
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 |
(seit C++17) |
Das Safe-Bool-Problem
Bis C++11 stellte das Entwerfen einer Klasse, die in booleschen Kontexten verwendbar sein sollte (z.B. if ( obj ) { ... } ), ein Problem dar: Bei einer benutzerdefinierten Konvertierungsfunktion wie T :: operator bool ( ) const ; erlaubte die implizite Konvertierungssequenz eine zusätzliche Standardkonvertierungssequenz nach diesem Funktionsaufruf, was bedeutet, dass das resultierende bool zu int konvertiert werden konnte, was Code wie obj << 1 ; oder int i = obj ; ermöglichte.
Eine frühe Lösung dafür ist in std::basic_ios zu sehen, das ursprünglich operator void * definiert, sodass Code wie if ( std:: cin ) { ... } kompiliert, weil void * in bool konvertierbar ist, aber int n = std:: cout ; nicht kompiliert, da void * nicht in int konvertierbar ist. Dies erlaubt jedoch weiterhin unsinnigen Code wie delete std:: cout ; zu kompilieren.
Viele Drittanbieterbibliotheken vor C++11 wurden mit einer aufwendigeren Lösung entworfen, bekannt als das Safe Bool Idiom . std::basic_ios erlaubte dieses Idiom ebenfalls über LWG issue 468 , und operator void * wurde ersetzt (siehe Anmerkungen ).
Seit C++11 kann auch die explizite bool-Konvertierung verwendet werden, um das Safe-Bool-Problem zu lösen.
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Veröffentlichtes Verhalten | Korrektes Verhalten |
|---|---|---|---|
| CWG 170 | C++98 |
Das Verhalten von Pointer-to-Member-Konvertierungen war unklar
wenn die abgeleitete Klasse das ursprüngliche Member nicht besitzt |
klargestellt |
| CWG 172 | C++98 | Aufzählungstyp wurde basierend auf seinem zugrunde liegenden Typ heraufgestuft | basierend auf seinem Wertebereich stattdessen |
|
CWG 330
( N4261 ) |
C++98 |
die Konvertierung von
double
*
const
(
*
p
)
[
3
]
zu double const * const ( * p ) [ 3 ] war ungültig |
gültig gemacht |
| CWG 519 | C++98 |
Nullzeigerwerte waren nicht garantiert
bei Konvertierung in einen anderen Zeigertyp erhalten |
immer erhalten |
| CWG 616 | C++98 |
das Verhalten der Lvalue-zu-Rvalue-Konvertierung von
beliebigen nicht initialisierten Objekten und Zeigerobjekten mit ungültigen Werten war stets undefiniert |
unbestimmter
unsigned
char
ist erlaubt; Verwendung ungültiger Zeiger ist implementierungsdefiniert |
| CWG 685 | C++98 |
der zugrundeliegende Typ eines Aufzählungstyps wurde
bei der Ganzzahl-Höherstufung nicht priorisiert, wenn er festgelegt ist |
priorisiert |
| CWG 707 | C++98 |
Ganzzahl-zu-Gleitkomma-Konvertierung
hatte in allen Fällen definiertes Verhalten |
das Verhalten ist undefiniert, wenn
der zu konvertierende Wert außerhalb des Zielbereichs liegt |
| CWG 1423 | C++11 |
std::nullptr_t
war konvertierbar zu
bool
sowohl in Direkt- als auch Kopierinitialisierung |
nur Direktinitialisierung |
| CWG 1773 | C++11 |
Ein Namensausdruck, der in einem potenziell ausgewerteten
Ausdruck erscheint, sodass das benannte Objekt nicht odr-used ist, könnte dennoch während einer Lvalue-zu-Rvalue-Konvertierung ausgewertet werden |
nicht ausgewertet |
| CWG 1781 | C++11 |
std::nullptr_t
zu
bool
wurde als implizite
Konvertierung betrachtet, obwohl sie nur für die Direktinitialisierung gültig ist |
wird nicht mehr als
implizite Konvertierung betrachtet |
| CWG 1787 | C++98 |
Das Verhalten beim Lesen von einem undefinierten
unsigned char , der in einem Register zwischengespeichert war, war undefiniert |
wurde wohldefiniert gemacht |
| CWG 1981 | C++11 | Kontextuelle Konvertierungen betrachteten explizite Konvertierungsfunktionen | nicht berücksichtigt |
| CWG 2140 | C++11 |
Es war unklar, ob Lvalue-zu-Rvalue-Konvertierungen von
std::nullptr_t Lvalues diese Lvalues aus dem Speicher abrufen |
nicht abgerufen |
| CWG 2310 | C++98 |
für abgeleitete-zu-Basis-Zeigerkonvertierungen und
Basis-zu-abgeleitet Zeiger-auf-Mitglied-Konvertierungen, konnte der abgeleitete Klassentyp unvollständig sein |
muss vollständig sein |
| CWG 2484 | C++20 |
char8_t
und
char16_t
hatten unterschiedliche Ganzzahl-
Höherstufungsstrategien, aber sie können beide aufnehmen |
char8_t
sollte auf die gleiche Weise
heraufgestuft werden wie char16_t |
| CWG 2485 | C++98 | Ganzzahlpromotions mit Bitfeldern waren nicht gut spezifiziert | Spezifikation verbessert |
| CWG 2813 | C++23 |
Temporäres Materialisieren würde auftreten, wenn eine explizite
Objekt-Memberfunktion eines Klassen-Prvalue aufgerufen wird |
tritt in diesem Fall
nicht auf |
| CWG 2861 | C++98 |
ein Zeiger auf ein typunzugängliches Objekt könnte
in einen Zeiger auf ein Basisklassen-Subobjekt konvertiert werden |
das Verhalten ist
in diesem Fall undefiniert |
| CWG 2879 | C++17 |
Temporäre Materialisierungskonvertierung wurde auf Prvalue
als Operand eines Operators angewendet, der Glvalue erwartet |
in einigen Fällen nicht angewendet |
| CWG 2899 | C++98 |
Lvalue-zu-Rvalue-Konvertierungen konnten auf Lvalues
angewendet werden, die Objekte mit ungültigen Wertdarstellungen bezeichnen |
das Verhalten ist
in diesem Fall undefiniert |
| CWG 2901 | C++98 |
Das Ergebnis der Lvalue-zu-Rvalue-Konvertierung von einem
unsigned
int
Lvalue, der sich auf ein int Objekt mit dem Wert - 1 bezieht, war unklar |
klargestellt |
Siehe auch
-
const_cast -
static_cast -
dynamic_cast -
reinterpret_cast - Explizite Konvertierung
- Benutzerdefinierte Konvertierung
|
C-Dokumentation
für
Implizite Konvertierungen
|