9

In C++ 11, so scheint es, wie es ein std::map<std::string, int> wie folgt zu initialisieren legal ist:Welche Sprachregeln erlauben C++ 11, daraus abzuleiten, dass es sich um eine initialiser_list von Paaren handelt?

std::map<std::string, int> myMap = { 
    { "One", 1 }, 
    { "Two", 2 }, 
    { "Three", 3 } 
}; 

Intuitiv dies sinnvoll ist - die in geschweiften Klammern stehenden Initialisierer eine Liste von Paaren von Strings ist, und std::map<std::string, int>::value_type ist std::pair<std::string, int> (möglicherweise mit einigen const Qualifikationen.

Allerdings bin ich mir nicht sicher, ich verstehe, wie die Typisierung hier funktioniert. Wenn wir die Variablendeklaration hier eliminieren und nur die geschweifte Initialisierer haben, würde der Compiler nicht wissen, dass es schaute auf eine std::initializer_list<std::pair<std::string, int>>, weil es nicht wissen würde, dass die verspannten Paare re präsentiert std::pair s. Daher scheint es, als ob der Compiler den Vorgang des Zuweisens eines Typen an den geschweiften Initialisierer irgendwie verzögert, bis es genügend Typeninformation von dem std::map-Konstruktor hat, um zu erkennen, dass die geschachtelten geschweiften Klammern für Paare sind. Ich erinnere mich nicht, dass so etwas in C++ 03 passiert ist; Nach meinem besten Wissen hing der Typus eines Ausdrucks niemals von seinem Kontext ab.

Welche Sprachregeln erlauben die korrekte Kompilierung dieses Codes und die Festlegung des für die Initialisierungsliste zu verwendenden Typs durch den Compiler? Ich hoffe auf Antworten mit spezifischen Verweisen auf die C++ 11-Spezifikation, da es wirklich interessant ist, dass das funktioniert!

Danke!

+0

In Klammern eingeschlossene Listen in C++ 03 waren bereits sehr spezifisch für den Initialisierungskontext. Es gibt hier keinen signifikanten Unterschied zu einem verschachtelten C++ 03-Aggregat (vielleicht 'std :: pair myArray []'), das mit geschweiften Klammern initialisiert wurde, außer dass der Typ nicht direkt benannt ist die verfügbaren Konstruktoren. –

+0

BTW, implizite Konvertierungsoperatoren (Member 'operator T()') sind die andere Stelle, an der der Kontext den Typ eines Ausdrucks bestimmt (und für die Überladungsauflösung verwendet wird). –

+2

'braced-init-list' ist kein Ausdruck und hat keinen Typ. Es gibt keinen Abzug, nur eine Überladungsauflösung unter den Initialisierungslisten-Konstruktoren (von denen es nur einen gibt) – Cubbi

Antwort

9

Im Ausdruck

std::map<std::string, int> myMap = { 
    { "One", 1 }, 
    { "Two", 2 }, 
    { "Three", 3 } 
}; 

auf der rechten Seite haben Sie eine verspannt-init-Liste wobei jedes Element ist auch eine verspannt-init-Liste. Das erste, was passiert, ist, dass der Initialisiererlistenkonstruktor von std::map berücksichtigt wird.

map(initializer_list<value_type>, 
    const Compare& = Compare(), 
    const Allocator& = Allocator()); 

map<K, V>::value_type ist ein typedef für pair<const K, V>, in diesem Fall pair<const string, int>. Die inneren stained-init-Listen können erfolgreich in map::value_type konvertiert werden, da std::pair über einen Konstruktor verfügt, der Verweise auf seine beiden konstituierenden Typen verwendet, und std::string über einen impliziten Konvertierungskonstruktor verfügt, der char const * akzeptiert.

So ist der Initialisiererlistenkonstruktor von std::map lebensfähig, und die Konstruktion kann von den verschachtelten-inst-Listen erfolgen.

Der relevante standardese in vorliegt §13.3.1.7/1 [over.match.list]

Wenn Objekte von nicht-aggregierte Klassentyp T sind Liste initialisiert (8.5.4), Die Überladungsauflösung wählt den Konstruktor in zwei Phasen aus: - Anfänglich sind die Kandidatenfunktionen die Initialisierungslistenkonstruktoren (8.5.4) der Klasse T und die Argumentliste besteht aus der Initialisierungsliste als einzelnes Argument.
- Wenn kein ausführbarer Initialisierungslistenkonstruktor gefunden wird, wird die Überladungsauflösung erneut ausgeführt, wobei die Kandidatenfunktionen alle Konstruktoren der Klasse T sind und die Argumentliste aus den Elementen der Initialisierungsliste besteht.

Die erste Kugel ist, was der initializer_list Konstruktor map Ursachen für die äußere verspannt-init-Liste ausgewählt werden, während die zweite Kugel führt zur Auswahl des richtigen pair Konstruktor für die innere verspannt-Init Listen.

+0

Wunderbar, danke! – templatetypedef

2

Dies ist Listeninitialisierung. Die Regeln werden gefunden in §8.5.4 [dcl.init.list]/p3 des Standard:

List-Initialisierung eines Objekts oder einer Referenz des Typs T als folgt definiert ist:

  • Wenn die Initialisierungsliste keine Elemente enthält und T ein Klassentyp mit einem Standardkonstruktor ist, wird das Objekt initialisiert.
  • Andernfalls, wenn T ein Aggregat ist, wird Aggregat Initialisierung durchgeführt (8.5.1). [Beispiel weggelassen]
  • Andernfalls, wenn T eine Spezialisierung von std::initializer_list<E> ist, ein initializer_list Objekt, wie unten beschrieben aufgebaut ist, und verwendet, um das Objekt zu initialisieren, nach den Regeln für Initialisierung eines Objekt aus einer Klasse von der gleichen Art (8.5).
  • Andernfalls, wenn T ein Klassentyp ist, werden Konstruktoren berücksichtigt. Die anwendbaren Konstruktoren werden aufgelistet und die beste wird durch Überladungsauflösung gewählt (13.3, 13.3.1.7). Wenn eine konvertierende Konvertierung (siehe unten) erforderlich ist, um eines der Argumente zu konvertieren, ist das Programm schlecht formatiert.
  • [Beispiel und den Rest der Regeln weggelassen]

Beachten Sie, dass Auflösung überlasten std::initializer_list Konstrukteuren in diesen Fällen (§13.3.1.7 [over.match.list]) bevorzugen.

Somit, wenn der Compiler eine verspannt Liste verwendet sieht einen Gegenstand aus einem nicht-aggregierte, nicht std::initializer_list Klassentyp zu initialisieren, wird sie die Überladungsauflösung führt den entsprechenden Konstruktor auszuwählen, lieber den Konstruktor initializer_list wenn ein tragfähiger existiert (wie es für std::map tut).