List-Initialisierung (seit C++11)
Initialisiert ein Objekt aus einer in geschweiften Klammern eingeschlossenen Initialisierungsliste.
Inhalt |
[bearbeiten] Syntax
[bearbeiten] Direkte List-Initialisierung
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) | ||||||||
[bearbeiten] Kopier-List-Initialisierung
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) | ||||||||
List-Initialisierung findet in folgenden Situationen statt
- direkte List-Initialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt)
- Kopier-List-Initialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt, aber nur nicht-explizite Konstruktoren dürfen aufgerufen werden)
operator[], wobei die List-Initialisierung den Parameter des überladenen Operators initialisiertU in diesem Beispiel ist nicht der Typ, der durch List-Initialisierung initialisiert wird; es ist der Parameter des Konstruktors von U)[bearbeiten] Erklärung
Die Effekte der List-Initialisierung eines Objekts vom Typ (möglicherweise cv-qualifiziert) T sind:
|
(seit C++20) |
- Wenn
Teine Aggregatklasse ist und die in geschweiften Klammern eingeschlossene Initialisierungsliste, die keine designierte Initialisierungsliste enthält,(seit C++20) einen einzigen Initialisierer der gleichen oder abgeleiteten Typs (möglicherweise cv-qualifiziert) hat, wird das Objekt aus diesem Initialisierer initialisiert (durch Kopier-Initialisierung für Kopier-List-Initialisierung oder durch direkte Initialisierung für direkte List-Initialisierung). - Andernfalls, wenn
Tein Zeichen-Array ist und die in geschweiften Klammern eingeschlossene Initialisierungsliste einen einzigen Initialisierer hat, der ein String-Literal des entsprechenden Typs ist, wird das Array wie üblich aus dem String-Literal initialisiert.
- Andernfalls, wenn
Tein Aggregattyp ist, wird Aggregat-Initialisierung durchgeführt.
- Andernfalls, wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste leer ist und
Tein Klassentyp mit einem Standardkonstruktor ist, wird Wert-Initialisierung durchgeführt.
- Andernfalls, wenn
Teine Spezialisierung von std::initializer_list ist, wird das Objekt wie nachstehend beschrieben initialisiert.
- Andernfalls, wenn
Tein Klassentyp ist, werden die Konstruktoren vonTin zwei Phasen betrachtet:
- Alle Konstruktoren, die std::initializer_list als einziges Argument oder als erstes Argument haben, wenn die restlichen Argumente Standardwerte haben, werden untersucht und durch Überladungsauflösung gegen ein einzelnes Argument vom Typ std::initializer_list abgeglichen.
- Wenn die vorherige Phase keine Übereinstimmung ergibt, nehmen alle Konstruktoren von
Tan der Überladungsauflösung gegen die Menge von Argumenten teil, die aus den Initialisierer-Klauseln der in geschweiften Klammern eingeschlossenen Initialisierungsliste bestehen, mit der Einschränkung, dass nur nicht-verengende Konvertierungen zulässig sind. Wenn diese Phase einen expliziten Konstruktor als beste Übereinstimmung für eine Kopier-List-Initialisierung ergibt, schlägt die Kompilierung fehl (Hinweis: Bei einfacher Kopier-Initialisierung werden explizite Konstruktoren überhaupt nicht berücksichtigt).
- Wenn die vorherige Phase keine Übereinstimmung ergibt, nehmen alle Konstruktoren von
|
(seit C++17) |
- Andernfalls (wenn
Tkein Klassentyp ist), wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste nur eine einzige Initialisierer-Klausel hat und entwederTkein Referenztyp ist oder ein Referenztyp ist, dessen referenzierter Typ gleich dem Typ der Initialisierer-Klausel ist oder eine Basisklasse davon ist, wirdTdirekt initialisiert (bei direkter List-Initialisierung) oder kopiert initialisiert (bei Kopier-List-Initialisierung), mit der Ausnahme, dass verengende Konvertierungen nicht zulässig sind.
- Andernfalls, wenn
Tein Referenztyp ist, der nicht mit dem Typ der Initialisierer-Klausel kompatibel ist:
|
(bis C++17) |
|
(seit C++17) |
- Andernfalls, wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste keine Initialisierer-Klausel hat, wird
Twert-initialisiert.
[bearbeiten] List-Initialisierung von std::initializer_list
Ein Objekt vom Typ std::initializer_list<E> wird aus einer Initialisierungsliste konstruiert, als ob der Compiler und materialisiert(seit C++17) einen prvalue vom Typ „Array von N const E“ generiert hätte, wobei N die Anzahl der Initialisierer-Klauseln in der Initialisierungsliste ist; dies wird als *Unterstützungs-Array* der Initialisierungsliste bezeichnet.
Jedes Element des Unterstützungs-Arrays wird mit der entsprechenden Initialisierer-Klausel der Initialisierungsliste kopiert initialisiert, und das Objekt std::initializer_list<E> wird so konstruiert, dass es auf dieses Array verweist. Ein für die Kopie ausgewählter Konstruktor oder Konvertierungsfunktion muss im Kontext der Initialisierungsliste zugänglich sein. Wenn eine verengende Konvertierung zur Initialisierung eines der Elemente erforderlich ist, ist das Programm ill-formed.
Das Unterstützungs-Array hat die gleiche Lebensdauer wie jedes andere temporäre Objekt, außer dass die Initialisierung eines std::initializer_list-Objekts aus dem Unterstützungs-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}}); } // The initialization above will be implemented in a way roughly equivalent to below, // assuming that the compiler can construct an initializer_list object with a pair of // pointers, and with the understanding that `__b` does not outlive the call to `f`. 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 Unterstützungs-Arrays eindeutig 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}); // the result is unspecified: // the back arrays can share // storage within {1, 2, 3, 4}
[bearbeiten] Verengende Konvertierungen
List-Initialisierung schränkt die zulässigen impliziten Konvertierungen ein, indem sie Folgendes verbietet:
- Konvertierung von einem Fließkommatyp in einen ganzzahligen Typ
- Konvertierung von einem Fließkommatyp
Tin einen anderen Fließkommatyp, dessen Fließkomma-Konvertierungsrang weder größer noch gleich dem vonTist, es sei denn, das Konvertierungsergebnis ist ein Konstantausdruck und eine der folgenden Bedingungen ist erfüllt:- 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 ganzzahligen Typ in einen Fließkommatyp, es sei denn, die Quelle ist ein Konstantausdruck, dessen Wert exakt im Zieltyp gespeichert werden kann
- Konvertierung von einem ganzzahligen Typ oder einem unbereinigten Aufzählungstyp in einen ganzzahligen Typ, der nicht alle Werte des Originals darstellen kann, es sei denn:
- die Quelle ist ein Bitfeld, dessen Breite w kleiner ist als die seines Typs (oder bei einem Aufzählungstyp dessen zugrundeliegender Typ) und der Zieltyp kann alle Werte eines hypothetischen erweiterten ganzzahligen Typs mit Breite w und mit derselben Vorzeichenbehaftung wie der ursprüngliche Typ darstellen, oder
- die Quelle ist ein Konstantausdruck, dessen Wert exakt im Zieltyp gespeichert werden kann
- Konvertierung von einem Zeigertyp oder einem Zeiger-auf-Mitglied-Typ in bool
[bearbeiten] Hinweise
Jede Initialisierer-Klausel wird sequenziert vor jeder Initialisierer-Klausel, die ihr in der in geschweiften Klammern eingeschlossenen Initialisierungsliste folgt. Dies steht im Gegensatz zu den Argumenten eines Funktionsaufrufausdrucks, die unsequenziert(bis C++17)unbestimmt sequenziert(seit C++17) sind.
Eine in geschweifte Klammern eingeschlossene Initialisierungsliste ist kein Ausdruck und hat daher keinen Typ. Beispiel: decltype({1, 2}) ist ill-formed. Da kein Typ vorhanden ist, kann die Template-Typableitung keinen Typ ableiten, der einer in geschweifte Klammern eingeschlossenen Initialisierungsliste entspricht. Daher ist für die Deklaration template<class T> void f(T); der Ausdruck f({1, 2, 3}) ill-formed. Der Template-Parameter kann jedoch anderweitig abgeleitet werden, wie im Fall von std::vector<int> v(std::istream_iterator<int>(std::cin), {}), wo der Iterator-Typ durch das erste Argument abgeleitet wird, aber auch an der zweiten Parameterposition verwendet wird. Eine Ausnahme bildet die Typableitung unter Verwendung des Schlüsselworts auto, die jede in geschweifte Klammern eingeschlossene Initialisierungsliste bei der Kopier-List-Initialisierung als std::initializer_list ableitet.
Ebenso, da eine in geschweifte Klammern eingeschlossene Initialisierungsliste keinen Typ hat, gelten spezielle Regeln für die Überladungsauflösung, wenn sie als Argument für einen überladenen Funktionsaufruf verwendet wird.
Aggregate kopieren/verschieben direkt aus einer in geschweiften Klammern eingeschlossenen Initialisierungsliste mit einem einzigen Initialisierer des gleichen Typs, aber Nicht-Aggregate betrachten zuerst std::initializer_list-Konstruktoren.
struct X {}; // aggregate struct Q // non-aggregate { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // copy-constructor (not aggregate initialization) Q q; Q q2 = Q{q}; // initializer-list constructor (not copy constructor) }
Einige Compiler (z. B. gcc 10) betrachten die Konvertierung von einem Zeiger oder Zeiger-auf-Mitglied in bool im C++20-Modus nur als verengend.
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_initializer_lists |
200806L |
(C++11) | List-Initialisierung und std::initializer_list |
[bearbeiten] Beispiel
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // list-initialization of a non-static member std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // list-initialization of a member in constructor }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // list-initialization in return statement } int main() { int n0{}; // value-initialization (to zero) int n1{1}; // direct-list-initialization std::string s1{'a', 'b', 'c', 'd'}; // initializer-list constructor call std::string s2{s1, 2, 2}; // regular constructor call std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char) int n2 = {1}; // copy-list-initialization double d = double{1.2}; // list-initialization of a prvalue, then copy-init auto s4 = std::string{"HelloWorld"}; // same as above, no temporary // created since C++17 std::map<int, std::string> m = // nested list-initialization { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // list-initialization in function call << '\n'; const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array int&& r1 = {1}; // binds an rvalue reference to a temporary int // int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref // int bad{1.0}; // error: narrowing conversion unsigned char uc1{10}; // okay // unsigned char uc2{-1}; // error: narrowing conversion 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); // has effect of [[maybe_unused]] }
Ausgabe
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
[bearbeiten] Defect reports
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 | Das List-Initialisieren einer Referenz mit einer in geschweiften Klammern eingeschlossenen Initialisierungsliste eines einzelnen Initialisierers band die Referenz immer an ein temporäres Objekt |
an diesen Initialisierer binden, falls gültig. |
| CWG 1290 | C++11 | Die Lebensdauer des Unterstützungs-Arrays war nicht korrekt spezifiziert. | gleiche Lebensdauer wie andere temporäre Objekte |
| CWG 1324 | C++11 | die Initialisierung wurde zuerst für die Initialisierung aus {} betrachtet |
Aggregatinitialisierung zuerst betrachtet |
| CWG 1418 | C++11 | Der Typ des Unterstützungs-Arrays fehlte const | const hinzugefügt |
| CWG 1467 | C++11 | Die gleichzeitige Initialisierung von Aggregaten und Zeichen- Arrays war verboten; Initializer-Listen-Konstruktoren hatten Priorität vor Kopierkonstruktoren für einzeilige Listen |
gleichzeitige Initialisierung erlaubt; einzeilige Listen werden direkt initialisiert |
| CWG 1494 | C++11 | Bei der List-Initialisierung einer Referenz mit einer Initialisierer-Klausel eines inkompatiblen Typs war es unbestimmt, ob das temporäre Objekt direkt-list-initialisiert oder kopiert-list-initialisiert wurde |
es hängt vom Initialisierungsart für die Referenz ab |
| CWG 2137 | C++11 | Initializer-Listen-Konstruktoren verloren gegen Kopier- Konstruktoren beim List-Initialisieren von X aus {X} |
Nicht-Aggregate betrachten Initializer-Listen zuerst |
| CWG 2252 | C++17 | Aufzählungen konnten von nicht-skalaren Werten per List-Initialisierung initialisiert werden | verboten |
| CWG 2267 | C++11 | Die Auflösung von CWG-Problem 1494 machte deutlich dass temporäre Objekte direkt-list-initialisiert werden konnten |
sie werden kopiert-list-initialisiert bei List-Initialisierung von Referenzen |
| CWG 2374 | C++17 | Die direkte List-Initialisierung einer Aufzählung erlaubte zu viele Quelltypen | eingeschränkt |
| CWG 2627 | C++11 | Ein schmales Bitfeld eines größeren ganzzahligen Typs kann zu einem kleineren ganzzahligen Typ befördert werden, aber es war trotzdem eine verengende Konvertierung |
es ist keine verengende Konvertierung |
| CWG 2713 | C++20 | Referenzen auf Aggregatklassen konnten nicht mit designierten Initialisierungslisten initialisiert werden |
erlaubt |
| CWG 2830 | C++11 | List-Initialisierung ignorierte nicht die oberste cv-Qualifizierung | ignoriert |
| CWG 2864 | C++11 | Fließkomma-Konvertierungen, die überliefen, waren nicht verengend | sie sind verengend |
| P1957R2 | C++11 | Konvertierung von einem Zeiger/Zeiger-auf-Mitglied zu bool war nicht verengend |
wird als verengend betrachtet |
| P2752R3 | C++11 | Unterstützungs-Arrays mit überlappender Lebensdauer konnten sich nicht überlappen | sie dürfen sich überlappen |