Polyadische Initialisierungen in C++ (Polyadische Initialisierungen in C++), Lektion, Seite 723672
https://www.purl.org/stefan_ram/pub/polyadische_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

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.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram723672 stefan_ram:723672 Polyadische Initialisierungen in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723672, slrprddef723672, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/polyadische_c++