List-initialization (since C++11)
Initialisiert ein Objekt aus einer geschweiften Klammer-Initialisierungsliste .
Inhaltsverzeichnis |
Syntax
Direkte Listeninitialisierung
T Objekt
{
arg1, arg2, ...
};
|
(1) | ||||||||
T
{
arg1, arg2, ...
}
|
(2) | ||||||||
new
T
{
arg1, arg2, ...
}
|
(3) | ||||||||
Klasse
{
T Mitglied
{
arg1, arg2, ...
}; };
|
(4) | ||||||||
Klasse
::
Klasse
() :
Mitglied
{
arg1, arg2, ...
} {...
|
(5) | ||||||||
Copy-Listeninitialisierung
T Objekt
= {
arg1, arg2, ...
};
|
(6) | ||||||||
Funktion
({
arg1, arg2, ...
})
|
(7) | ||||||||
return {
arg1, arg2, ...
};
|
(8) | ||||||||
Objekt
[{
arg1, arg2, ...
}]
|
(9) | ||||||||
Objekt
= {
arg1, arg2, ...
}
|
(10) | ||||||||
U
({
arg1, arg2, ...
})
|
(11) | ||||||||
Klasse
{
T Mitglied
= {
arg1, arg2, ...
}; };
|
(12) | ||||||||
Die Listeninitialisierung wird in den folgenden Situationen durchgeführt:
- direkte Listeninitialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt)
- Copy-List-Initialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt, aber nur nicht-explizite Konstruktoren können aufgerufen werden)
operator[]
, wo die Listeninitialisierung den Parameter des überladenen Operators initialisiert
U
in diesem Beispiel ist nicht der Typ, der listeninitialisiert wird;
U
's Konstruktorparameter ist es)
Erklärung
Die Auswirkungen der Listeninitialisierung eines Objekts vom Typ (möglicherweise cv-qualifiziert)
T
sind:
|
(seit C++20) |
-
Wenn
Teine Aggregatklasse ist und die geschweifte Klammer-Initialisierungsliste , die keine designated initializer list enthält, (seit C++20) eine einzelne Initialisierungsklausel desselben oder abgeleiteten Typs (möglicherweise cv-qualifiziert) enthält, wird das Objekt aus dieser Initialisierungsklausel initialisiert (durch Copy-Initialisierung für Copy-List-Initialisierung oder durch Direct-Initialisierung für Direct-List-Initialisierung). -
Andernfalls, wenn
Tein Zeichenarray ist und die geschweifte Klammer-Initialisierungsliste eine einzelne Initialisierungsklausel enthält, die ein Zeichenkettenliteral mit passendem Typ ist, wird das Array wie üblich vom Zeichenkettenliteral initialisiert .
-
Andernfalls, falls
Tein Aggregattyp ist, wird Aggregatinitialisierung durchgeführt.
-
Andernfalls, wenn die geschweifte Klammer-Initialisierungsliste leer ist und
Tein Klassentyp mit einem Standardkonstruktor ist, wird Wertinitialisierung durchgeführt.
-
Andernfalls, falls
Teine Spezialisierung von std::initializer_list ist, wird das Objekt wie unten beschrieben initialisiert.
-
Andernfalls, wenn
Tein Klassentyp ist, werden die Konstruktoren vonTin zwei Phasen betrachtet:
-
- Alle Konstruktoren, die std::initializer_list als einzigen Argument annehmen, oder als erstes Argument, falls die restlichen Argumente Standardwerte haben, werden geprüft und durch overload resolution gegen ein einzelnes Argument vom Typ std::initializer_list abgeglichen.
-
-
Wenn die vorherige Stufe keine Übereinstimmung ergibt, nehmen alle Konstruktoren von
Tan der Überlagerungsauflösung gegenüber der Menge der Argumente teil, die aus den Initialisierer-Klauseln der geschweiften Initialisiererliste besteht, mit der Einschränkung, dass nur nicht-verengende Konvertierungen erlaubt sind. Wenn diese Stufe einen expliziten Konstruktor als beste Übereinstimmung für eine Copy-List-Initialisierung ergibt, schlägt die Kompilierung fehl (beachte, bei einfacher Copy-Initialisierung werden explizite Konstruktoren überhaupt nicht berücksichtigt).
-
Wenn die vorherige Stufe keine Übereinstimmung ergibt, nehmen alle Konstruktoren von
|
(seit C++17) |
-
Andernfalls (wenn
Tkein Klassentyp ist), falls die geschweifte Initialisierungsliste nur einen Initialisierungsabschnitt enthält und entwederTkein Referenztyp ist oder ein Referenztyp, dessen referenzierter Typ derselbe wie oder eine Basisklasse des Typs des Initialisierungsabschnitts ist, wirdTdirekt initialisiert (bei direkter Listeninitialisierung) oder kopierinitialisiert (bei Kopierlisteninitialisierung), außer dass einschränkende Konvertierungen nicht erlaubt sind.
-
Andernfalls, wenn
Tein Referenztyp ist, der nicht mit dem Typ des Initialisierer-Ausdrucks kompatibel ist:
|
(bis C++17) |
|
(seit C++17) |
-
Andernfalls, wenn die geschweifte Klammer-Initialisiererliste keine Initialisierer-Klausel hat,
Twird value-initialized .
Listeninitialisierung von std::initializer_list
Ein Objekt vom Typ std:: initializer_list < E > wird aus einer Initialisierungsliste konstruiert, als ob der Compiler einen materialisierten (seit C++17) Prvalue vom Typ "Array von N const E " erzeugen würde, wobei N die Anzahl der Initialisierungsclauseln in der Initialisierungsliste ist; dies wird als Backing Array der Initialisierungsliste bezeichnet.
Jedes Element des zugrundeliegenden Arrays wird copy-initialisiert mit dem entsprechenden Initialisierer-Ausdruck der Initialisiererliste, und das std:: initializer_list < E > -Objekt wird so konstruiert, dass es auf dieses Array verweist. Ein für den Kopiervorgang ausgewählter Konstruktor oder eine Konvertierungsfunktion muss zugänglich im Kontext der Initialisiererliste sein. Wenn eine einschränkende Konvertierung zur Initialisierung eines der Elemente erforderlich ist, ist das Programm fehlerhaft.
Das zugrundeliegende Array hat die gleiche Lebensdauer wie jedes andere temporäre Objekt , außer dass die Initialisierung eines std::initializer_list -Objekts aus dem zugrundeliegenden Array die Lebensdauer des Arrays genau wie das Binden einer Referenz an ein temporäres Objekt verlängert.
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // Die obige Initialisierung wird in etwa äquivalent zu unten implementiert, // unter der Annahme, dass der Compiler ein initializer_list-Objekt mit einem Paar von // Zeigern konstruieren kann, und mit dem Verständnis, dass `__b` den Aufruf von `f` nicht überlebt. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // Backing-Array f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // Backing-Array f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // Backing-Array q(std::initializer_list<A>(__c, __c + 3)); }
Ob alle zugrundeliegenden Arrays unterschiedlich sind (d. h. in nicht-überlappenden Objekten gespeichert sind), ist nicht spezifiziert:
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // das Ergebnis ist nicht spezifiziert: // die zugrundeliegenden Arrays können // Speicher innerhalb von {1, 2, 3, 4} teilen
Einschränkende Konvertierungen
Die Listeninitialisierung beschränkt die zulässigen impliziten Konvertierungen , indem folgende verboten werden:
- Umwandlung von einem Gleitkommatyp in einen Ganzzahltyp
-
Konvertierung von einem Gleitkommatyp
Tzu einem anderen Gleitkommatyp, dessen floating-point conversion rank weder größer als noch gleich dem vonTist, außer wenn das Konvertierungsergebnis ein constant expression ist und eine der folgenden Bedingungen erfüllt ist:- Der konvertierte Wert ist endlich und die Konvertierung führt nicht zu einem Überlauf.
- Die Werte vor und nach der Konvertierung sind nicht endlich.
- Konvertierung von einem Ganzzahltyp zu einem Gleitkommatyp, außer wenn die Quelle ein konstanter Ausdruck ist, dessen Wert exakt im Zieltyp gespeichert werden kann
-
Konvertierung von einem Integer- oder unbegrenzten Enumerationstyp zu einem Integer-Typ, der nicht alle Werte des Originals darstellen kann, außer wenn
- die Quelle ein Bitfeld ist, dessen Breite w geringer ist als die seines Typs (oder, für einen Enumerationstyp , seines zugrundeliegenden Typs) und der Zieltyp alle Werte eines hypothetischen erweiterten Integer-Typs mit Breite w und mit der gleichen Vorzeichenbehaftung wie der Originaltyp darstellen kann, oder
- die Quelle ein konstanter Ausdruck ist, dessen Wert exakt im Zieltyp gespeichert werden kann
- Umwandlung von einem Zeigertyp oder Zeiger-auf-Member-Typ zu bool
Hinweise
Jeder Initialisierer-Ausdruck ist sequenced before jedem Initialisierer-Ausdruck, der ihm in der geschweiften Initialisiererliste folgt. Dies steht im Gegensatz zu den Argumenten eines function call expression , die unsequenced (until C++17) indeterminately sequenced (since C++17) sind.
Eine in geschweiften Klammern eingeschlossene Initialisierungsliste ist kein Ausdruck und hat daher keinen Typ, z.B. decltype ( { 1 , 2 } ) ist fehlerhaft. Das Fehlen eines Typs bedeutet, dass die Template-Typableitung keinen Typ ableiten kann, der einer in geschweiften Klammern eingeschlossenen Initialisierungsliste entspricht, daher ist bei der Deklaration template < class T > void f ( T ) ; der Ausdruck f ( { 1 , 2 , 3 } ) fehlerhaft. Allerdings kann der Template-Parameter anderweitig abgeleitet werden, wie im Fall von std:: vector < int > v ( std:: istream_iterator < int > ( std:: cin ) , { } ) , wo der Iteratortyp durch das erste Argument abgeleitet wird, aber auch in der zweiten Parameterposition verwendet wird. Eine spezielle Ausnahme wird für Typableitung mit dem Schlüsselwort auto gemacht, das jede in geschweiften Klammern eingeschlossene Initialisierungsliste in der Copy-List-Initialisierung als std::initializer_list ableitet.
Auch weil eine in geschweifte Klammern eingeschlossene Initialisierungsliste keinen Typ hat, gelten spezielle Regeln für Overload Resolution , wenn sie als Argument für einen überladenen Funktionsaufruf verwendet wird.
Aggregate führen Copy-/Move-Initialisierung direkt aus einer geschweiften Initialisierungsliste mit einer einzelnen Initialisierungsklausel desselben Typs durch, aber Nicht-Aggregate berücksichtigen zuerst std::initializer_list Konstruktoren:
struct X {}; // Aggregat struct Q // Nicht-Aggregat { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // Copy-Konstruktor (keine Aggregat-Initialisierung) Q q; Q q2 = Q{q}; // Initializer-List-Konstruktor (kein Copy-Konstruktor) }
Einige Compiler (z.B. gcc 10) betrachten nur die Konvertierung von einem Zeiger oder einem Zeiger-auf-Mitglied zu bool als Einengung im C++20-Modus.
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | Listeninitialisierung und std::initializer_list |
Beispiel
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // Listeninitialisierung eines nicht-statischen Members std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // Listeninitialisierung eines Members im Konstruktor }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // Listeninitialisierung in return-Anweisung } int main() { int n0{}; // Wertinitialisierung (auf Null) int n1{1}; // direkte Listeninitialisierung std::string s1{'a', 'b', 'c', 'd'}; // Initializer-List-Konstruktoraufruf std::string s2{s1, 2, 2}; // regulärer Konstruktoraufruf std::string s3{0x61, 'a'}; // Initializer-List-Konstruktor wird (int, char) vorgezogen int n2 = {1}; // Kopier-Listeninitialisierung double d = double{1.2}; // Listeninitialisierung eines PR-Werts, dann Kopierinitialisierung auto s4 = std::string{"HelloWorld"}; // wie oben, kein temporäres Objekt // erzeugt seit C++17 std::map<int, std::string> m = // verschachtelte Listeninitialisierung { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // Listeninitialisierung im Funktionsaufruf << '\n'; const int (&ar)[2] = {1, 2}; // bindet eine Lvalue-Referenz an ein temporäres Array int&& r1 = {1}; // bindet eine Rvalue-Referenz an einen temporären int // int& r2 = {2}; // Fehler: kann Rvalue nicht an nicht-konstante Lvalue-Referenz binden // int bad{1.0}; // Fehler: einschränkende Konvertierung unsigned char uc1{10}; // okay // unsigned char uc2{-1}; // Fehler: einschränkende Konvertierung Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // hat Effekt von [[maybe_unused]] }
Ausgabe:
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 1288 | C++11 |
Die Listeninitialisierung einer Referenz mit einer geschweiften Initialisiererliste
mit einem einzelnen Initialisierer band die Referenz immer an ein Temporärobjekt |
Bindung an diesen Initialisierer
wenn gültig |
| CWG 1290 | C++11 | Die Lebensdauer des Backing-Arrays war nicht korrekt spezifiziert |
Spezifiziert wie andere
Temporärobjekte |
| CWG 1324 | C++11 |
Initialisierung wurde zuerst für die Initialisierung von
{}
betrachtet
|
Aggregate-Initialisierung
wird zuerst betrachtet |
| CWG 1418 | C++11 | Dem Typ des Backing-Arrays fehlte const | const hinzugefügt |
| CWG 1467 | C++11 |
Gleich-Typ-Initialisierung von Aggregaten und Character-
Arrays war verboten; Initializer-List-Konstruktoren hatten Priorität über Kopierkonstruktoren für Einzelklausel-Listen |
Gleich-Typ-Initialisierung
erlaubt; Einzelklausel- Listen initialisieren direkt |
| CWG 1494 | C++11 |
Bei Listeninitialisierung einer Referenz mit einem Initialisierer
inkompatiblen Typs war unklar, ob das Temporärobjekt direkt-listeninitialisiert oder kopier-listeninitialisiert wird |
Es hängt von der Art
der Initialisierung für die Referenz ab |
| CWG 2137 | C++11 |
Initializer-List-Konstruktoren verloren gegen Kopie-
konstruktoren bei Listeninitialisierung von
X
aus
{X}
|
Nicht-Aggregate betrachten
Initializer-Listen zuerst |
| CWG 2252 | C++17 | Aufzählungen konnten von Nicht-Skalarwerten listeninitialisiert werden | Verboten |
| CWG 2267 | C++11 |
Die Lösung von
CWG Issue 1494
machte klar,
dass Temporärobjekte direkt-listeninitialisiert werden könnten |
Sie werden kopier-listeninitialisiert
bei Listeninitialisierung von Referenzen |
| CWG 2374 | C++17 | Direkt-Listeninitialisierung einer Enum erlaubte zu viele Quelltypen | Eingeschränkt |
| CWG 2627 | C++11 |
Ein schmales Bitfeld eines größeren Integer-Typs kann zu
einem kleineren Integer-Typ promoviert werden, aber es war trotzdem eine Verengungskonvertierung |
Es ist keine
Verengungskonvertierung |
| CWG 2713 | C++20 |
Referenzen auf Aggregate-Klassen konnten nicht
durch Designated-Initializer-Listen initialisiert werden |
Erlaubt |
| CWG 2830 | C++11 | Listeninitialisierung ignorierte nicht die Top-Level-cv-Qualifikation | Ignoriert |
| CWG 2864 | C++11 | Fließkomma-Konvertierungen, die überlaufen, waren keine Verengungen | Sie sind Verengungen |
| P1957R2 | C++11 |
Konvertierung von einem Zeiger/Zeiger-auf-Mitglied
zu bool war keine Verengung |
Als Verengung betrachtet |
| P2752R3 | C++11 | Backing-Arrays mit überlappender Lebensdauer konnten nicht überlappen | Sie können überlappen |