Namensräume
Varianten
Aktionen

List-Initialisierung (seit C++11)

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Initialisiert ein Objekt aus einer in geschweiften Klammern eingeschlossenen Initialisierungsliste.

Inhalt

[bearbeiten] Syntax

[bearbeiten] Direkte List-Initialisierung

T Objekt { arg1, arg2, ... };

T Objekt{.des1 = arg1 , .des2 { arg2 } ... };

(seit C++20)
(1)
T { arg1, arg2, ... }

T {.des1 = arg1 , .des2 { arg2 } ... }

(seit C++20)
(2)
new T { arg1, arg2, ... }

new T {.des1 = arg1 , .des2 { arg2 } ... }

(seit C++20)
(3)
Klasse { T Mitglied { arg1, arg2, ... }; };

Klasse { T Mitglied {.des1 = arg1 , .des2 { arg2 } ... }; };

(seit C++20)
(4)
Klasse::Klasse() : Mitglied { arg1, arg2, ... } {...

Klasse::Klasse() : Mitglied {.des1 = arg1 , .des2 { arg2 } ...} {...

(seit C++20)
(5)

[bearbeiten] Kopier-List-Initialisierung

T Objekt = { arg1, arg2, ... };

T Objekt = {.des1 = arg1 , .des2 { arg2 } ... };

(seit C++20)
(6)
Funktion ({ arg1, arg2, ... })

Funktion ({.des1 = arg1 , .des2 { arg2 } ... })

(seit C++20)
(7)
return { arg1, arg2, ... };

return {.des1 = arg1 , .des2 { arg2 } ... };

(seit C++20)
(8)
Objekt [{ arg1, arg2, ... }]

Objekt [{.des1 = arg1 , .des2 { arg2 } ... }]

(seit C++20)
(9)
Objekt = { arg1, arg2, ... }

Objekt = {.des1 = arg1 , .des2 { arg2 } ... }

(seit C++20)
(10)
U ({ arg1, arg2, ... })

U ({.des1 = arg1 , .des2 { arg2 } ... })

(seit C++20)
(11)
Klasse { T Mitglied = { arg1, arg2, ... }; };

Klasse { T Mitglied = {.des1 = arg1 , .des2 { arg2 } ... }; };

(seit C++20)
(12)

List-Initialisierung findet in folgenden Situationen statt

  • direkte List-Initialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt)
1) Initialisierung einer benannten Variablen mit einer in geschweiften Klammern eingeschlossenen Initialisierungsliste
2) Initialisierung eines unbenannten temporären Objekts mit einer in geschweiften Klammern eingeschlossenen Initialisierungsliste
3) Initialisierung eines Objekts mit dynamischer Speicherdauer mit einem new-Ausdruck, wobei der Initialisierer eine in geschweifte Klammern eingeschlossene Initialisierungsliste ist
4) in einem nicht-statischen Datenmember-Initialisierer, der das Gleichheitszeichen nicht verwendet
5) in einer Member-Initialisierungsliste eines Konstruktors, wenn eine in geschweifte Klammern eingeschlossene Initialisierungsliste verwendet wird
  • Kopier-List-Initialisierung (sowohl explizite als auch nicht-explizite Konstruktoren werden berücksichtigt, aber nur nicht-explizite Konstruktoren dürfen aufgerufen werden)
6) Initialisierung einer benannten Variablen mit einer in geschweiften Klammern eingeschlossenen Initialisierungsliste nach einem Gleichheitszeichen
7) in einem Funktionsaufrufausdruck, wobei eine in geschweifte Klammern eingeschlossene Initialisierungsliste als Argument verwendet wird und die List-Initialisierung den Funktionsparameter initialisiert
8) in einer return-Anweisung mit einer in geschweifte Klammern eingeschlossenen Initialisierungsliste als Rückgabeausdruck und die List-Initialisierung das zurückgegebene Objekt initialisiert
9) in einem Indexausdruck mit einem benutzerdefinierten operator[], wobei die List-Initialisierung den Parameter des überladenen Operators initialisiert
10) in einem Zuweisungsausdruck, wobei die List-Initialisierung den Parameter des überladenen Operators initialisiert
11) Funktionaler Cast-Ausdruck oder andere Konstruktoraufrufe, bei denen eine in geschweifte Klammern eingeschlossene Initialisierungsliste anstelle eines Konstruktorarguments verwendet wird. Die Kopier-List-Initialisierung initialisiert den Parameter des Konstruktors (Hinweis: Der Typ U in diesem Beispiel ist nicht der Typ, der durch List-Initialisierung initialisiert wird; es ist der Parameter des Konstruktors von U)
12) in einem nicht-statischen Datenmember-Initialisierer, der das Gleichheitszeichen verwendet

[bearbeiten] Erklärung

Die Effekte der List-Initialisierung eines Objekts vom Typ (möglicherweise cv-qualifiziert) T sind:

  • Wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste eine designierte Initialisierungsliste enthält und T kein Referenztyp ist, muss T eine Aggregatklasse sein. Die geordneten Bezeichner in den Designatoren der designierten Initialisierungsliste müssen eine Teilsequenz der geordneten Bezeichner der direkten nicht-statischen Datenmember von T bilden. Aggregat-Initialisierung wird durchgeführt.
(seit C++20)
  • Wenn T eine 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 T ein 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 die in geschweiften Klammern eingeschlossene Initialisierungsliste leer ist und T ein Klassentyp mit einem Standardkonstruktor ist, wird Wert-Initialisierung durchgeführt.
  • Andernfalls, wenn T ein Klassentyp ist, werden die Konstruktoren von T in zwei Phasen betrachtet:
  • Wenn die vorherige Phase keine Übereinstimmung ergibt, nehmen alle Konstruktoren von T an 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).
  • Andernfalls, wenn T ein Aufzählungstyp ist, der mit einem festen zugrundeliegenden Typ U versehen ist, die in geschweiften Klammern eingeschlossene Initialisierungsliste nur einen Initialisierer v hat und alle folgenden Bedingungen erfüllt sind, dann wird die Aufzählung mit dem Ergebnis der Konvertierung von v nach U initialisiert:
    • Die Initialisierung ist eine direkte List-Initialisierung.
    • v ist vom Skalar-Typ.
    • v ist implizit nach U konvertierbar.
    • Die Konvertierung von v nach U ist nicht verengend.
(seit C++17)
  • Andernfalls (wenn T kein Klassentyp ist), wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste nur eine einzige Initialisierer-Klausel hat und entweder T kein Referenztyp ist oder ein Referenztyp ist, dessen referenzierter Typ gleich dem Typ der Initialisierer-Klausel ist oder eine Basisklasse davon ist, wird T direkt initialisiert (bei direkter List-Initialisierung) oder kopiert initialisiert (bei Kopier-List-Initialisierung), mit der Ausnahme, dass verengende Konvertierungen nicht zulässig sind.
  • Andernfalls, wenn T ein Referenztyp ist, der nicht mit dem Typ der Initialisierer-Klausel kompatibel ist:
  • Ein prvalue-Temporärobjekt vom Typ, auf den T verweist, wird kopiert initialisiert, und die Referenz wird an dieses temporäre Objekt gebunden (dies schlägt fehl, wenn die Referenz eine nicht-const Lvalue-Referenz ist).
(bis C++17)
  • Ein prvalue wird generiert. Der prvalue initialisiert sein Ergebnisobjekt durch Kopier-List-Initialisierung. Der prvalue wird dann verwendet, um die Referenz direkt zu initialisieren (dies schlägt fehl, wenn die Referenz eine nicht-const Lvalue-Referenz ist). Der Typ des temporären Objekts ist der Typ, auf den T verweist, es sei denn, T ist „Referenz auf ein Array unbekannter Größe von U“, in diesem Fall ist der Typ des temporären Objekts der Typ von x in der Deklaration U x[] H, wobei H die Initialisierungsliste ist(seit C++20).
(seit C++17)
  • Andernfalls, wenn die in geschweiften Klammern eingeschlossene Initialisierungsliste keine Initialisierer-Klausel hat, wird T wert-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 T in einen anderen Fließkommatyp, dessen Fließkomma-Konvertierungsrang weder größer noch gleich dem von T ist, 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

[bearbeiten] Siehe auch