Kapitel 6. Usability

In diesem Kapitel:

Gebrauchstauglichkeit oder auch einfach Usability ist ein wichtiges Design-Ziel von C++11. Genau dieses Design-Ziel verfolgen die neuen Features Range-basierte For-Schleife, automatische Typableitung, Lambda-Funktionen und die vereinheitlichte Initialisierung, die Sie in diesem Kapitel genauer kennenlernen werden.

Die Range-basierte For-Schleife

Jeder Datentyp, für den begin() und end() so definiert sind, dass er Iteratoren zurückgibt, unterstützt die Range-basierte For-Schleife. Das sind insbesondere alle STL-Container, std::string, die neuen Datentypen std::array und Initialisiererlisten.

auto

In Kombination mit auto lässt sich so äußerst kompakt über einen Bereich (range) iterieren.

for (auto x : {1,2,3,5,8,13,21,34}) std::cout << x << " ";

Werden die Elemente des Bereichs per Referenz angenommen, können sie direkt modifiziert werden. In Kapitel 3, Abschnitt „Die Range-basierte For-Schleife“, sind viele Anwendungsfälle für die Range-basierte For-Schleife zu finden.

Dabei ist ein Ausdruck der Form

for (iterVariable: expression) statement

im Wesentlichen äquivalent zu (Komitee, 2008):

{
  auto&& range= expression;
  for (auto begin= begin(range), end= end(range);
        begin != end; ++begin){
    iterVariable= *begin
    statement
  }
}

Mit der Range-basierten For-Schleife kann man durch einen String iterieren und ihn modifizieren (Listing 6.1).

forLoop.cpp

In Listing 6.1 wird die neue For-Schleife sowohl für die Ausgabe (Zeile 11) als auch für die Modifikation des Strings (Zeilen 15 und 21) verwendet. Dabei gibt der ternäre Ausdruck in Zeile 21 den Groß- oder Kleinbuchstaben des Zeichens c abhängig davon zurück, ob das Prädikat std::isupper(c) zu wahr oder falsch evaluiert.

Die Ausgabe zeigt den Original-String, den String in Großbuchstaben und den String, bei dem alle Groß- zu Kleinbuchstaben und Klein- zu Großbuchstaben werden.

myVec.cpp myVecSolution.cpp

Aufgabe 6-1

Implementieren Sie einen Container, der in einer Range-basierten For-Schleife verwendet werden kann.

Der Datentyp MyVec soll eine einfache Hülle um einen std::vector sein und in einer Range-basierten For-Schleife die Elemente des std::vector ausgeben. Dazu muss er die Methoden begin() und end() anbieten, die Iteratoren zurückgeben.

Als Ausgangsbasis soll Listing 6.2 dienen.

incrementListForLoop.cpp

Aufgabe 6-2

Inkrementieren Sie in einem sequenziellen Container den Wert jedes Elements.

Ihre Liste std::list<int> besitzt 20 Elemente mit den Werten 0. Inkrementieren Sie die Werte der Liste sukzessive um 1, sodass die Liste die Werte von 1 bis 20 enthält. Geben Sie die modifizierten Werte der Liste aus.

compRangeForEach.cpp

Aufgabe 6-3

Vergleichen Sie die Range-basierte For-Schleife mit dem STL-Algorithmus std::for_each.

Iterieren Sie dazu über einen Container der Zahlen von 1 bis 10 und ersetzen Sie jede Zahl durch ihr Quadrat. Lösen Sie die Aufgabe mit der Range-basierten For-Schleife und dem STL-Algorithmus std::for_each. Geben Sie zur Kontrolle die veränderten Container aus.

Das neue Schlüsselwort auto ist in Teil I, eingeführt und häufig angewandt worden. Was noch fehlt, sind die Details.

Direkte und Kopierinitialisierung

Sowohl die direkte als auch die Kopierinitialisierung ist mit auto erlaubt und erzeugt den gleichen Datentyp:

auto myIntDirect(1);
auto myIntCopy= 1;

Deklaration mehrerer Variablen

Solange jede Initialisierung den gleichen Typ ergibt, kann auto verwendet werden, um mehrere Variablen zu definieren.

double d= 5.5;
auto f= 5.0, *pf= &f, *pd= &d;

Der kurze Codeschnipsel erklärt eine Variable f vom Typ double und zwei Zeiger pf und pd auf Variablen vom Typ double. In der Typdefinition mit mehreren Variablen setze ich explizit voraus, dass der Ausdruck von links nach rechts verarbeitet wird.

Automatische Typableitung in Funktions-Templates

Automatische Typableitung mit auto ist in C++ kein neues Feature. Das Ableiten der Parameter von Funktions-Templates ist schon lange im Einsatz und folgt den gleichen Regeln wie auto.

So wie das Argument expression in Zeile 4 den Typ von var in Zeile 2 bestimmt, so bestimmt ihn expression für auto var in Zeile 5.

Der Typ der automatisch abgeleiteten Variablen kann durch eine Referenz und einen Zeiger, aber auch durch die Bezeichner const, volatile und static explizit angegeben werden.

auto.cpp

In Listing 6.4 sind verschiedene Kombinationen des neuen Schlüsselworts auto mit Referenzen und Zeigern, aber auch mit konstanten, volatilen und statischen Datentypen zu sehen. Der resultierende Datentyp folgt im Kommentar.

Implizite Konvertierung

In den Beispielen gelten die Regeln entsprechend der Typableitung in Funktions-Templates. Nur wenn die Variable als Referenz erklärt wird, wird vecRef zur Referenz (Zeilen 15 und 16). Die implizite Typkonvertierung (decay to pointer) von einem Array auf einen Zeiger auf einen Datentyp int (Zeile 19) oder von einer Funktion auf einen Zeiger auf eine Funktion (Zeile 22) kann nur durch eine Referenz (Zeilen 20 und 23) verhindert werden.

autoExplicit.cpp autoExplicitSolution.cpp

Aufgabe 6-4

Die automatische Typableitung mit auto ist das wohl am häufigsten verwendete Feature aus C++11 in diesem Buch.

In dem kleinen Programm in Listing 6.5 wird auto exzessiv eingesetzt.

Schreiben Sie das Programm um, indem alle Verwendungen von auto durch den expliziten Typ ersetzt werden. Stellen Sie durch die Übersetzung des Programms sicher, dass die richtigen Typen zum Einsatz kommen. Beachten Sie dabei die zusätzlich benötigten Header-Dateien.

invokeFunction.cpp

invokeFunctionSolution.cpp

Aufgabe 6-5

Funktionsparameter dürfen nicht als auto deklariert werden.

auto kann fast überall eingesetzt werden, um den Typ aus der Initialisierung automatisch abzuleiten. Zwar ist es in Zeile 16 in Listing 6.5 möglich, einen Funktionsaufruf durch eine Lambda-Funktion zu definieren, aber Listing 6.6 lässt sich nicht übersetzen.

Der aktuelle GCC-Compiler moniert das sofort (Abbildung 6.2).

Wie lässt sich das Programm übersetzen und ausführen?

Neben auto kann der in Teil I, vorgestellte Operator decltype verwendet werden, um den Typ eines Ausdrucks zur Übersetzungszeit zu bestimmen.

decltype

decltype wortreicher als auto

Verwenden wir das Listing 6.4 und ergänzen es um die entsprechenden decltype-Anweisungen, fällt auf den ersten Blick auf, dass decltype deutlich wortreicher ist (Listing 6.7).

decltype.cpp

Auf den zweiten Blick ist das Bild schon deutlich differenzierter. Der Typ, den decltype zurückgibt, ist der deklarierte Typ (declared type). Hieraus leitet sich auch der Name des Operators decltype ab. Daher ist es weder notwendig, Referenzen oder Zeiger bzw. die Bezeichner const, volatile und static wie bei auto explizit zu spezifizieren, noch ist es nötig, den Vektor (Zeile 20), das Array (Zeile 26) und die Funktion (Zeile 3) per Referenz anzunehmen, um eine implizite Konvertierung wie bei auto zu verhindern.

Automatischer Rückgabetyp

Das Alleinstellungsmerkmal von decltype fehlt noch. Durch decltype ist es möglich, den Rückgabewert von Funktions-Templates automatisch bestimmen zu lassen. In C++ wurde dieses Problem gern über Promotion Traits (siehe Anhang E) gelöst.

Bevor wir uns aber einem klassischen Problem der Template-Programmierung widmen, sollten wir uns zunächst die neue, alternative Funktionssyntax anschauen.

Eine Funktion der Form wie in Listing 6.8

lässt sich nun in einer alternativen Syntax (Listing 6.9) definieren:

Anlehnung an die Mathematik

Wird der Rückgabetyp in der klassischen Funktionsdeklaration zuerst angegeben, folgt er nur nach der Funktionssignatur und wird mit einem -> eingeleitet. Die Syntax mag an die Funktionsdeklaration in der Mathematik oder auch an Haskell erinnern. Dies ist nicht das Entscheidende. Entscheidend ist, dass a und b zu dem späten Zeitpunkt definiert sind und rechts vom -> verwendet werden können.

Automatischer Rückgabetyp einer Funktion

Die Mächtigkeit von auto und decltype zeigt sich erst, wenn beide neuen Features zusammen mit der alternativen Form, Funktionen zu deklarieren, angewandt werden. Denn mit auto und decltype lässt sich der Rückgabetyp einer Funktion automatisch bestimmen (Listing 6.10).

newFunctionSyntax.cpp

trailing return type

Während getValue (Zeile 3 in Listing 6.10) mit der klassischen Template-Syntax auch ausgedrückt werden kann, indem der Template-Parameter T als Rückgabetyp verwendet wird, zeigt das Funktions-Template add (Zeile 8) eine ganz neue Funktionalität. Der Rückgabewerttyp des Funktions-Templates wird durch den Compiler für den Ausdruck first + second (Zeile 9) bestimmt. Das Schlüsselwort auto in der Typdeklaration (Zeilen 4 und 9) leitet die neue Syntax ein, um den Rückgabetyp verzögert zu evaluieren (trailing return type), und ist daher nicht mit der automatischen Typableitung von auto zu verwechseln.

Das ganze Programm kommt völlig ohne die Angabe eines Typs aus. Ein Vergleich des Funktions-Templates add mit der klassischen Umsetzung per Promotion Traits bietet drei nicht zu unterschätzende Vorteile:

Die Ausgabe bringt kein überraschendes Ergebnis.

typeid.cpp

Aufgabe 6-6

Implizite Typumwandlungen des Compilers

Machen Sie sich mit den Regeln der impliziten Typumwandlung des Compilers bei arithmetischen Operationen vertraut. Verwenden Sie dazu die neue Syntax, Funktionen zu deklarieren, und fragen Sie den Rückgabetyp mit dem Schlüsselwort typeid ab. Überprüfen Sie bei jedem Ergebnis, ob es Ihren Erwartungen entspricht.

Ein paar Anregungen:

std::cout << typeid( getType(1,false) ).name() << std::endl;
std::cout << typeid( getType('a',1) ).name() << std::endl;
std::cout << typeid( getType(false,false) ).name() << std::endl;
std::cout << typeid( getType(true,3.14) ).name() << std::endl;
std::cout << typeid( getType(1,4.0) ).name() << std::endl;

Dabei ist getType nach der neuen Funktionssyntax deklariert und soll seine zwei Argumente addieren.

newFunctionSyntaxSolution.cpp

Aufgabe 6-7

Schreiben Sie das generische Funktions-Template add aus Listing 6.10 so um, dass es exakt die Typen ermittelt.

Jetzt bin ich penibel. Das Funktions-Template add bestimmt nicht genau den Rückgabetyp. Wird es mit zwei Rvalues über add(1,2) aufgerufen, bestimmt es den Rückgabetyp für zwei Lvalues. Der Grund ist, dass die Rvalues mit first und second einen Namen erhalten und somit implizit zu Lvalues werden. Das ist in diesem konkreten Fall wohl kein Problem, kann aber dann zu einem werden, wenn Sie die Argumente verwenden, um eine weitere Funktion mit den exakten Argumenten aufzurufen.

Für die Lösung der Aufgaben müssen Sie wohl Kapitel 8, Abschnitt „Perfect Forwarding“ zurate ziehen. Testen Sie anschließend Ihre Lösung, indem Sie das neue Funktions-Template in Listing 6.10 einbauen.

Lambda-Funktionen

Teil I, enthält viele Beispiele für Lambda-Funktionen. Lambda-Funktionen unterstützen die Lokalität der Funktionalität, denn genau dort, wo eine aufrufbare Einheit benötigt wird, kann diese direkt definiert werden.

Dabei lässt sich eine Lambda-Funktion als Funktionsobjekt vorstellen, das an Ort und Stelle implizit definiert und ausgeführt wird.

lambdaFunctionObject.cpp

Der Aufruf der Lambda-Funktion wie auch der des Funktionsobjekts erzielen das gleiche Ergebnis – 105 (Abbildung 6.4).

Die Lambda-Funktion in Listing 6.12 [&sumLambda,inc](int v){sumLambda += v+inc;} (Zeile 27) ist über ihren Funktionskörper am einfachsten zu verstehen. In diesem wird die lokale Summationsvariable sumLambda (Zeile 23) per Referenz adressiert, da der Wert der Summation nach der Schleife zur Verfügung stehen soll. Erreicht wird das dadurch, dass die Bindung mit einer Referenz &sumLambda in dem Bindungsbereich [] deklariert wird. Hingegen ist es für das lokale inc (Zeile 5) ausreichend, kopiert zu werden. Das Argument v der Lambda-Funktion nimmt die Werte des Vektors an.

Lambda-Funktion versus Funktionsobjekte

Ein scharfer Blick auf das Funktionsobjekt und die Lambda-Funktion zeigt die Parallelität auf. Der Funktionskörper der Lambda-Funktion findet sich im überladenen Klammeroperator (Zeile 13) wieder. Sein Argument v entspricht dem Argument der Lambda-Funktion. Die Summationsvariable sumFunctionObject wird per Referenz im Objekt gebunden, inc hingegen kopiert.

Bindung an den lokalen Bereich: [ ]

Das Referenzieren des lokalen Bereichs im Bindungsbereich [] ist beliebig feingranular spezifizierbar. Neben dem Verhalten »Kopiere oder referenziere alle im Funktionskörper verwendeten Variablen des lokalen Bereichs« lassen sich explizite Ausnahmen festlegen. Dies ist am einfachsten anhand eines kleinen Beispiels erklärt. Für dieses Beispiel sollen die drei Variablen a, b und c deklariert sein und für den Bindungsbereich verschiedene Möglichkeiten durchgespielt werden. Für die drei Variablen ist explizit angegeben, ob sie im Funktionsblock nicht (Ø), per Referenz (&) oder per Kopie (=) zur Verfügung stehen.

Lambda-Funktionen können als Funktionen angesehen werden, die ihren Aufrufkontext konservieren.

Closure und Funktionsabschluss

closure.cpp

Das konstruierte Listing 6.14 verdeutlicht, wie Lambda-Funktionen ihren Aufrufkontext binden können. Ziel des Programms ist es, den Vektor über natürliche Zahlen mit verschiedenen Trennzeichen auszugeben. Beim Definieren der Lambda-Funktionen in den Zeilen 14, 17, 20 und 23 wird der String seperator gebunden. Dies ist der Grund dafür, dass jede Iteration über den Vektor mithilfe dieser Lambda-Funktionen ein anderes Trennzeichen anwendet (Zeilen 26, 29, 32 und 35) und ausgibt.

dangling reference

Bindet die Lambda-Funktion die Variablen ihres Aufrufkontexts per Referenz, muss sichergestellt sein, dass die Lambda-Funktion ihre verwendeten Variablen überlebt. Genau das ist in Listing 6.15 nicht der Fall.

danglingReference.cpp

Die Funktion makeLambda in Listing 6.15 erzeugt eine einfache Lambda-Funktion und gibt diese zurück. Die Lambda-Funktion benötigt kein Argument und soll einen std::string zurückgeben. Genau so ist der Rückgabewert durch das Funktionsobjekt std::function<std::string()> definiert. In Zeile 13 wird die Lambda-Funktion an bad zugewiesen und in der nächsten Zeile ausgeführt. Das Programm besitzt undefiniertes Verhalten. Führt dies mit dem GCC 4.6 (Abbildung 6.6) zu einem Speicherzugriffsfehler, so wird mit dem GCC 4.7 (Abbildung 6.7) der String "very bad" gar nicht ausgegeben.

Was ist der Grund? In der Lambda-Funktion wird in Listing 6.15 der String val per Referenz gebunden. Ist die Funktion ausgeführt, endet der Lebenszyklus der Variablen. Die Lambda-Funktion referenziert bei ihrer Ausführung eine Variable, die nicht mehr existiert. Es entsteht eine klassische dangling reference.

Klassenmethode

Will eine Lambda-Funktion, die in einer Methode implementiert ist, auf die Klassenelemente zugreifen, muss sie einen Standardbindungsmodus [&] oder [=] oder this angeben. Damit kann die Lambda-Funktion in ihrem Funktionskörper auf alle Elemente der Klasse per Referenz ([&]), per Copy ([=]) oder auf die Elemente des Objekts (this) zugreifen, unabhängig davon, ob diese privat, protected oder public definiert sind (Listing 6.16).

classMember.cpp

In Listing 6.16 kann sowohl das private Element a als auch die private Methode get10() in den drei Lambda-Funktionen (Zeilen 13, 14 und 15) verwendet werden. Damit die Lambda-Funktion direkt aufgerufen werden kann, kommt auf jedem der drei Funktionsobjekte der ()-Operator zum Einsatz.

Es fehlt noch das Ergebnis des Programmlaufs:

Rückgabewert: →

Besteht der Funktionskörper einer Lambda-Funktion nur aus einem return Ausdruck; oder gibt die Lambda-Funktion keinen Wert zurück, kann auf die Angabe des Rückgabetyps verzichtet werden. Die meisten Lambda-Funktionen, die bisher verwendet wurden, machten von dieser Option Gebrauch.

Verlangt eine Lambda-Funktion die explizite Angabe des Rückgabetyps, muss dieser in der alternativen Funktionssyntax angegeben werden.

Lambda-Funktionen in C++11 sind relativ mächtig, verglichen mit Lambda-Funktionen in anderen Programmiersprachen wie Python. Sie können aus mehreren Ausdrücken und sogar Statements bestehen.

Komplexere Lambda-Funktionen verlangen ein geschultes Auge. In Listing 6.17 wird eine Lambda-Funktion erklärt, die als Ergebnis die zwei Argumente addiert und zurückgibt. Die Lambda-Funktion erhält mithilfe von auto den Namen addLambda und verhält sich wie die Funktion addFunction:

danglingReference.cpp

Aufgabe 6-8

Korrigieren Sie das Programm danglineReference.cpp in Listing 6.15.

Wie müsste danglingReference.cpp in Listing 6.15 implementiert werden, damit das Programm wohldefiniert ist? Hierzu bieten sich einige Variationen an:

  • Geben Sie den Rückgabewert per Copy zurück.

  • Verwenden Sie eine globale Variable für den Rückgabewert.

  • Erweitern Sie makeLambda um einen Eingabeparameter std::string.

  • Erweitern Sie die von makeLambda erzeugte Lambda-Funktion um einen Eingabeparameter std::string.

createClosure.cpp

Aufgabe 6-9

Lassen Sie sich ein Closure von einer Klasse zurückgeben.

Die Klasse kann ganz einfach strukturiert sein.

Nun soll die Klasse eine Methode erhalten, die auf Anfrage einen Closure zurückgibt. Wird der Closure ausgeführt, gibt er den Wert der Variablen name zurück. Die entscheidende Zeile 6 fehlt in Listing 6.19. Ausgeführt, soll das Programm die Ausgabe aus Abbildung 6.9 besitzen.

Aufgabe 6-10

Implementieren Sie ein Funktionsobjekt.

In folgendem Listing ist eine einfache Lambda-Funktion definiert.

const std::string hello("lambda");
auto myLambda= [hello](const std::string& a)
               {return hello + " " + a; };
std::cout << myLambda("function") << std::endl;

Ausgeführt, gibt die Lambda-Funktion lambda function aus. Implementieren Sie ein Funktionsobjekt mit der gleichen Funktionalität, das sich wie eine Lambda-Funktion anfühlt.

const std::string helloFunc("function");
auto myFunctionObject= MyFunctionObject(helloFunc);
std::cout << myFunctionObject("object") << std::endl;

Vereinheitlichte Initialisierung überall

{}-Initialisiererlisten können in C++11 universell eingesetzt werden. Die Initialisierung mit {}-Listen verdrängt nicht die Initialisierung der Daten in C++98, sondern ergänzt sie. In Teil I, war dieses Feature schon häufig im Einsatz, da das Initialisieren von Containern damit sehr praktisch ist. Ein paar weitere Beispiele, die nicht alle Anwendungsfälle abdecken können, sollen dieses mächtige Feature noch besser veranschaulichen.

uniformInitialization.cpp

01 #include <unordered_map>
02 #include <string>
03 #include <vector>
04
05 struct MyStruct{
06     int x;
07     double y;
08 };
09
10 class MyClass{
11 public:
12     int x;
13     double y;
14 };
15
16 struct Telephone{
17   std::string name;
18   int number;
19 };
20
21 Telephone getTelephone(){
22   // Telephone("Rainer Grimm",12345) created
23   return {"Rainer Grimm",12345};
24 }
25
26 struct MyArray {
27     public:
28         MyArray(): data {1, 2, 3, 4, 5} {}
29     private:
30         const int data[5];
31 };
32
33 void getVector(const std::vector<int>& v){
34   // some code
35 }
36
37 int main(){
38
39   // built-in datatypes and strings
40   bool b{true};
41   bool b2= true;
42   int i{2011};
43   int i2= {2011};
44   std::string s{"string"};
45   std::string s2= {"string"};
46
47   // struct and class
48   MyStruct basic{5,3.2};
49   MyStruct basic2= {5,3.2};
50   MyClass alsoClass{5,3.2};
51   MyClass alsoClass2= {5,3.2};
52
53   // C-Array
54   // dynamic array initialization
55   const float * pData = new const float[4] { 1.5, 4, 3.5,
     4.5 };
56
57   // STL-Container
58   // a vector of 1 element
59   std::vector<int>oneElement{1};
60   std::vector<int>oneElement2= {1};
61
62   std::unordered_map<std::string,int> um {
     {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},
     {"Hamming",1968} };
63   std::unordered_map<std::string,int> um2=  {
     {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},
     {"Hamming",1968} };
64   // special cases
65   // brace initialization for a std::vector
66   getVector({ oneElement[0],5, 10, 20, 30 });
67
68   // methode
69   std::vector<int> v {};
70   v.insert(v.end(), { 99, 88, -1, 15 });
71
72   // getTelephone returns a initializer list
73   Telephone tel(getTelephone());
74 }
Listing 6.20 Vereinheitlichte Initialisierung

In Listing 6.20 sind viele verschiedene Variationen der vereinheitlichten Initialisierung dargestellt. In der Regel wird sowohl die direkte als auch die Kopierinitialisierung unterstützt. Diese trifft auf einfache Datentypen (Zeilen 40 bis 45), auf Strukturen und Klassen (Zeilen 48 bis 51), aber auch auf STL-Container (Zeilen 57 bis 63) zu. Sehr interessant ist die Initialisierung des Arguments der Funktion getVector über eine Initialisiererliste. Der std::vector erhält mit C++11 eine weitere Methode insert (Zeile 70), die direkt mit einer Initialisiererliste angesprochen werden kann. In der Funktion getTelephone in Zeile 23 wird implizit ein Objekt Telephone("Rainer Grimm",12345) erzeugt. Damit ist es möglich, als Rückgabewert eine Initialisiererliste zu verwenden.

initializerList.cpp

Aufgabe 6-11

Initialisieren Sie verschiedene Container mit Initialisiererlisten.

Initialisieren Sie ein std::array, ein std::vector, ein std::set und ein std::unordered_multiset durch die {-10,5,1,4,5}-Initialisiererliste und geben Sie die Elemente aus.

Beachten Sie die feinen Unterschiede:

  • Wie wird die Länge der Container angegeben?

  • Werden mehrfach vorkommende Elemente respektiert?

  • Sind die Elemente im Container sortiert?

Aufgabe 6-12

Unterscheiden Sie zwischen direkter und Kopierinitialisierung mit Initialisiererlisten.

In Listing 6.20 behaupte ich, dass in der Regel die direkte wie auch die Kopierinitialisierung mit Initialisiererlisten unterstützt werden.

Aufgabe 6-13

Entdecken Sie den feinen Unterschied zwischen der Initialisierung mit den runden () und den geschweiften {} Klammern.

Einen feinen Unterschied gibt es zwischen der Initialisierung mit runden () und der mit geschweiften {} Klammern. Beim bekannten Initialisieren mit den runden Klammern findet gegebenenfalls eine implizite Verengung (narrowing) des Datentyps statt. Bei der Initialisierung mit den eckigen Klammern ist das nicht der Fall.

Machen Sie sich den feinen Unterschied an ein paar Beispielen aus dem Entwurf N3242 von Pete Becker (Becker, 2011) des kommenden C++11 klar.

Die Zeilen 4, 5 und 9 stellen intelligente Ausnahmen der Regel vor. Passt der Quelltyp ohne Informationsverlust in den Zieltyp, findet das implizite Verengen des Datentyps auch mit der {}-Initialisiererliste statt.