Mehrstellige Initialisierungen in C++
Direktinitialisierung
Bei einer Direktinitialisierung können in den Klammern auch mehrere kommagetrennte Ausdrücke angegeben werden. Im folgenden Programmbeispiel wird als Typ des Objektes »s« ausdrücklich die Klasse »::std::pair< int, int >« angegeben, und es werden die beiden Ausdrücke »1« und »2« in den runden Klammern der Direktinitialisierung angegeben.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */int main()
{ ::std::pair< int, int > p( 1, 2 );
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }transcript
1
2
Bei einer Direktinitialisierung eines Objekts mit einer Klasse als Typ können in den Klammern also auch mehrere kommagetrennte Ausdrücke angegeben werden. Wir nennen diese Ausdrücke, wie bei einem Aufruf, Argumente.
Bei einer Direktinitialisierung mit mehreren Argumenten wird in der Klasse des Objekts nach einem passenden Konstruktor gesucht, ähnlich wie bei der Überladungsauflösung einer Funktion (siehe Lektion 10.4.).
Die Schablone »::std::pair« enthält einen passenden parametrisierten Konstruktor »constexpr pair( const T1 x, const T2 y )«, mit dem ein Paar durch zwei Werte initialisiert werden kann.
- Dokumentation (vereinfacht)
// defined in header <utility>
namespace std
{ template< class T1, class T2 >
struct pair
{ T1 first;
T2 second;
pair( pair );
pair( const T1 x, const T2 y ); }; }
Direkt-Listeninitialisierung
Eine Liste kommagetrennter Ausdrücke in geschweiften Klammern wird Initialisierungsliste genannt (auch wenn die geschweiften Klammern nur einen oder gar keinen Ausdruck enthalten).
Wenn der Initialisierer eine Initialisierungsliste ist, spricht man von einer Listeninitialisierung.
Das folgende Programm enthält eine Listeninitialisierung, die gleichzeitig auch eine Direktinitialisierung ist und deshalb auch als Direkt-Listeninitialisierung bezeichnet wird.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */int main()
{ ::std::pair< int, int >p{ 1, 2 };
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }transcript
1
2
Die geschweiften Klammern einer Direkt-Listeninitialisierung gelten darin nicht als ein Ausdruck, sondern als ein Initialisierer.
Bei der Direkt-Listeninitialisierung wird im obenstehenden Programm ebenfalls der Konstruktor »pair( const T1 x, const T2 y )« herangezogen, weil es sich bei einer Direkt-Listeninitialisierung ja auch um eine Direktinitialisierung handelt. (»T1« und »T2« sind durch den Datentyp »::std::pair< int, int >« beide als »int« festgelegt worden.
Kopier-Listeninitialisierung
Wenn mit mehrere kommagetrennte Ausdrücke initialisiert werden soll, um eine Konstruktor zu verwenden, der mehrere Parameter hat, so kann eine Kopierinitialisierung (etwas in der Art wie »::std::pair< int, int >p = 1, 2«) nicht direkt dafür verwendet werden. Es hilft auch nicht, die rechte Seite in runde Klammern einzuschließen (etwas in der Art wie »::std::pair< int, int >p =( 1, 2 )«).
Eine Initialisierungsliste kann jedoch auch rechts vom Gleichheitszeichen verwendet werden. In diesem Fall handelt es sich um eine Kopier-Listeninitialisierung.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */int main()
{ ::std::pair< int, int > p ={ 1, 2 };
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }Protokoll
1
2
Die Kopier-Listeninitialisierung ähnelt eher der weiter oben vorgestellten Direkt-Listeninitialisierung als einer traditionellen Kopierinitialisierung (also einer Kopierinitialisierung, die keine Listeninitialisierung ist).
Bei der traditionellen Kopierinitialisierung wird die rechte Seite zunächst in den Typ der linken Seite gewandelt (wofür eventuell schon ein Konstruktor verwendet wird). Dann wird das Ergebnis mit Hilfe eines Kopierkonstruktors in die linke Seite kopiert. Falls eine Klasse keinen Kopierkonstruktor besitzt, kann eine traditionalle Kopierinitialisierung also nicht durchgeführt werden.
Bei der Kopier-Listeninitialisierung wird wie bei einer Direkt-Listeninitialisierung ein passender Konstuktor verwendet.
Der Kopierkonstruktor
Im folgenden Programm wird die Kopier-Listeninitialisierung »/* :C */« also ohne Fehlermeldung übersetzt, weil sie keinen Kopierkonstruktor benötigt. Die traditionalle Kopierinitialisierung »/* :D */« führt jedoch zu einer Fehlermeldung, weil der Typ »::std::atomic< int >« keinen Kopierkonstruktor hat.
main.cpp
#include <atomic> /* ::std::atomic */
#include <initializer_list>int main()
{ { ::std::atomic< int >a( 0 ); /* :A */ }
{ ::std::atomic< int >a{ 0 }; /* :B */ }
{ ::std::atomic< int >a = { 0 }; /* :C */ }
{ ::std::atomic< int >a = 0; /* :D */ }}Protokoll
error: use of deleted function 'std::atomic<int>::atomic(const std::atomic<int>&)'
{ ::std::atomic<int> a = 0; }}
^
Einengung
Außerdem verbietet die Kopier-Listeninitialisierung wie die Direkt-Listeninitialisierung die Einengung eines Wertes.
main.cpp
#include <atomic> /* ::std::atomic */
#include <initializer_list>int main()
{ { int a = 0.0; /* :A */ }
{ int a{ 0.0 }; /* :B */ }
{ int a ={ 0.0 }; /* :C */ } }Protokoll
main.cpp:6:16: error: narrowing conversion of '0.0' from 'double' to 'int' inside { }
{ int a{ 0.0 }; /* :B */ }
^main.cpp:7:19: error: narrowing conversion of '0.0' from 'double' to 'int' inside { }
{ int a = { 0.0 }; /* :C */ } }
^
Ausdrücke
Eine Initialisierungsliste kann im allgemeinen nicht als Operand verwendet werden. Entsprechend führt das folgende Programm, in dem eine Initialisierungsliste als Operand eines unären Klammeroperators verwendet wird, zu einer Fehlermeldung.
main.cpp
#include <initializer_list>
int main(){ int a =( { 0.0 } ); }
Protokoll
error: ISO C++ forbids braced-groups within expressions
int main(){ int a =( { 0.0 } ); }
^
Zusammenfassung einiger Begriffe
Eine Initialisierung in einer der beiden Schreibweisen
T x ( L );
T x { L };
(…) wird Direktinitialisierung genannt. (Hierbei steht „T “ für einen Typ, „x “ für einen Namen und „L “ für eine kommagetrennte Ausdruckliste)
Die zweite Form ist zusätzlich auch eine Direkt-Listeninitialisierung.
Eine Initialisierung in einer der beiden Schreibweisen
T x = A ;
T x ={ L };
(…) wird Kopierinitialisierung genannt. (Hierbei „A “ für einen Ausdruck)
Die zweite Form ist zusätzlich auch eine Kopier-Listeninitialisierung.
Quellen
Im Standard wird Listeninitialisierung unter “List-initialization [dcl.init.list] ” behandelt (Stand 2018).
Typherleitung für Schablonenargumente
Seit C++17 gibt es eine automatische Typ-Herleitung für Initialisierungen die es erlaubt, die explizite Typangabe in spitzen Klammern wegzulassen, welche dann automatisch hergeleitet wird.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */int main()
{ ::std::pair p{ 1, 2 };
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }- Protokoll
1
2
Sequenzierung
Die einzelnen Einträge einer Initialisierungsliste werden in der gegebenen Reihenfolge ausgewertet.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */static int f(){ ::std::cout << "Hallo, "; return 0; }
static int g(){ ::std::cout << "Welt!" << '\n'; return 0; }int main()
{ ::std::pair< int, int > p{ f(), g() }; }Protokoll
Hallo, Welt!
Übungsfragen
? Initialisierungsarten erkennen
Ordnen Sie Beispiele und Initialisierungsarten einander zu!
- Beispiele
- »::std::string const b = a;«
- »::std::pair< int, int >p{ 1, 2 };«
- »::std::pair< int, int >p( 1, 2 );«
- Initialisierungsarten
- Kopierinitialisierung
- Direkt-Initialisierung
- Listen-Initialisierung
? Konstruktoren zuordnen
Ordnen Sie Beispiele und Konstruktoren einander zu!
- Beispiele
- »::std::string const b = a;«
- »::std::pair< int, int > p{ 1, 2 };«
- »::std::pair< int, int > p( 1, 2 );«
- Proklamation der Klasse »::std::pair« (vereinfacht)
// defined in header <utility>
namespace std
{ template< class T1, class T2 >
struct pair
{ T1 first;
T2 second;
pair( pair ); /* <-- Anton */
pair( const T1 x, const T2 y ); }; /* <-- Berta */ }- Proklamation der Klasse »::std::string« (vereinfacht)
namespace std
{ using string = basic_string< char >;template< class charT >
class basic_string
{ basic_string( basic_string const str ); /* <-- Caesar */
basic_string( charT const * ); /* <-- Ida */
charT const * c_str() const;
charT const * data() const; }}
Übungsaufgaben
/ Übungsaufgabe 0
Schreiben Sie eine Funktion »print_pair«, die ein Objekt vom Typ »::std::pair<int,int>« als Argument entgegennimmt und ausgibt. Rufen Sie diese Funktion dann von »main« aus mit einem ::std::pair<int,int>-Argument auf, um dieses Argument auszugeben. (Die Lösung dieser Aufgabe kann aufbewahrt werden, da es später noch einmal eine ähnliche Aufgabe geben wird.)
Zusatzanforderung 01 Schreiben Sie statt einer Funktion eine Funktionsschablone, welche ein beliebiges »::std::pair<T1,T2>« als Argument entgegennimmt. (Dabei kann »T1« und »T2« dann jeweils einer der Typen »int«, »double« oder »::std::string« sein.)
/ Übungsaufgabe 1 ⃗
Schreiben Sie eine Funktion »create_pair«, die zwei int-Zahlen als Argumente entgegennimmt und ein »::std::pair<int,int>« als Ergebnis zurueckgibt, das die beiden als Argumente uebergebenen int-Werte in der gegebenen Reihenfolge enthält. Dann sollte diese Funktion in »main« aufgerufen werden, um eine Paar mit zwei int-Werten zu erhalten, und es sollten dann die beiden Komponenten jenes Paares ausgegeben werden (in der main-Funktion).
Zusatzanforderung 11 Schreiben Sie statt einer Funktion eine Funktionsschablone, welche Argumente verschiedener Typen als Argumente entgegennimmt und ein passendes Paar zurückgibt. (Da der Dozent diese Aufgabe selber noch nicht gelöst hat, ist es nicht ganz sicher, ob das geht. Vermutlich ist es aber möglich.)
Zitate *
- C++ 2017 8.6.4p1 List-initialization [dcl.init.list] (leicht überarbeitet)
- List-initialization is initialization of an object or reference from a braced-init-list.
- Such an initializer is called an initializer list.
- The comma-separated initializer-clauses of the list are called the elements of the initializer list.
- An initializer list may be empty.
- List-initialization can occur in direct-initialization or copy-initialization contexts.
- List-initialization in a direct-initialization context is called direct-list-initialization.
- List-initialization in a copy-initialization context is called copy-list-initialization.
- Einsatzmöglichkeiten (C++ 2017 8.6.4p1 note) (überarbeitet)
- List-initialization can be used
- (1.1) — as the initializer in a variable definition (8.6)
- »int a = {1};«
- »std::complex<double> z{1,2};«
- »std::map<std::string,int> anim = { {"bear",4}, {"cassowary",2}, {"tiger",7} };«
- (1.2) — as the initializer in a new-expression (5.3.4)
- »new std::vector<std::string>{"once", "upon", "a", "time"}; /* 4 string elements */«
- (1.3) — in a return statement (6.6.3)
- »return { "Norah" }; /* return list of one element */«
- (1.4) — as a for-range-initializer (6.5)
- (1.5) — as a function argument (5.2.2)
- (1.6) — as a subscript (5.2.1)
- (1.7) — as an argument to a constructor invocation (8.6, 5.2.3)
- »x = double{1}; /* explicitly construct a double */«
- (1.8) — as an initializer for a non-static data member (9.2)
- »int* e {}; /* initialization to zero / null pointer */«
- (1.9) — in a mem-initializer (12.6.2)
- (1.10) — on the right-hand side of an assignment (5.18)
- 8.6.4p3 [dcl.init.list]
- Abschnitt 3 von 8.6.4 beschreibt ausführlich die Semantik aller möglichen Fälle von Listeninitialisierung. Man kann sie also bei Bedarf dort nachlesen.
- 8.6.4p4 [dcl.init.list]
- Abschnitt 4 von 8.6.4 beschreibt die Reihenfolge der Auswertung der Einträge einer Initialisierungsliste.
- 8.6.4p5 [dcl.init.list]
- Abschnitte 5 und 6 von 8.6.4 beschreiben die Konstruktion eines Initialisierungslistenobjekts aus einer Initialisierungsliste.
- 8.6.4p4 [dcl.init.list]
- Abschnitt 7 von 8.6.4 beschreibt einengende Wandlungen.