Warum ist std :: initializer_list keine integrierte Sprache?

95

Warum ist keine std::initializer_listKernsprache eingebaut?

Es scheint mir, dass es ein ziemlich wichtiges Feature von C ++ 11 ist und dennoch kein eigenes reserviertes Schlüsselwort (oder ähnliches) hat.

Stattdessen handelt initializer_listes sich nur um eine Vorlagenklasse aus der Standardbibliothek, die über eine spezielle implizite Zuordnung aus der neuen Syntax der Klammer-Init-Liste verfügt {...} , die vom Compiler verarbeitet wird.

Auf den ersten Blick ist diese Lösung ziemlich hackig .

Werden auf diese Weise neue Ergänzungen zur C ++ - Sprache implementiert: durch implizite Rollen einiger Vorlagenklassen und nicht durch die Kernsprache ?


Bitte beachten Sie diese Beispiele:

   widget<int> w = {1,2,3}; //this is how we want to use a class

Warum wurde eine neue Klasse gewählt:

   widget( std::initializer_list<T> init )

anstatt etwas Ähnliches wie eine dieser Ideen zu verwenden:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. Ein klassisches Array, das Sie wahrscheinlich consthier und da hinzufügen könnten
  2. drei Punkte sind bereits in der Sprache (var-args, jetzt variadische Vorlagen), warum nicht wieder verwendet die Syntax (und macht sie fühlen Built-in )
  3. nur ein vorhandener Container, könnte hinzufügen constund&

Alle von ihnen sind bereits ein Teil der Sprache. Ich habe nur meine 3 ersten Ideen geschrieben, ich bin mir sicher, dass es viele andere Ansätze gibt.

emesx
quelle
26
Das Normungskomitee hasst es , neue Schlüsselwörter hinzuzufügen!
Alex Chamberlain
11
Das verstehe ich, aber es gibt viele Möglichkeiten, die Sprache zu erweitern ( Stichwort war nur ein Beispiel )
emesx
10
std::array<T>ist nicht mehr "Teil der Sprache" als std::initializer_list<T>. Und dies sind bei weitem nicht die einzigen Bibliothekskomponenten, auf die sich die Sprache stützt. See new/ delete, type_infoverschiedene Ausnahmetypen, size_tetc.
bames53
6
@ Elmes: Ich hätte vorgeschlagen const T(*)[N], denn das verhält sich sehr ähnlich wie es std::initializer_listfunktioniert.
Mooing Duck
1
Dies antwortet, warum std::arrayoder ein Array mit statischer Größe weniger wünschenswerte Alternativen sind.
Boycy

Antworten:

48

Es gab bereits Beispiele für "Kern" -Sprachenfunktionen, die im stdNamespace definierte Typen zurückgaben . typeidkehrt zurück std::type_infound (vielleicht einen Punkt strecken) sizeofkehrt zurück std::size_t.

Im ersteren Fall müssen Sie bereits einen Standardheader einfügen, um diese sogenannte "Kernsprache" -Funktion nutzen zu können.

Bei Initialisierungslisten wird zum Generieren des Objekts kein Schlüsselwort benötigt. Die Syntax besteht aus kontextsensitiven geschweiften Klammern. Abgesehen davon ist es das gleiche wie type_info. Persönlich glaube ich nicht, dass das Fehlen eines Schlüsselworts es "hackiger" macht. Vielleicht etwas überraschender, aber denken Sie daran, dass das Ziel darin bestand, die gleiche Syntax für Klammerinitialisierer zuzulassen, die bereits für Aggregate zulässig war.

Ja, Sie können wahrscheinlich in Zukunft mehr von diesem Designprinzip erwarten:

  • Wenn mehr Gelegenheiten auftreten, in denen es möglich ist, neue Funktionen ohne neue Schlüsselwörter einzuführen, wird das Komitee diese übernehmen.
  • Wenn für neue Funktionen komplexe Typen erforderlich sind, werden diese Typen stdnicht als integrierte Typen, sondern als solche eingefügt.

Daher:

  • Wenn eine neue Funktion einen komplexen Typ erfordert und ohne neue Schlüsselwörter eingeführt werden kann, erhalten Sie hier das, was Sie hier haben. Dies ist die "Kernsprache" -Syntax ohne neue Schlüsselwörter, die Bibliothekstypen von verwendet std.

Ich denke, es kommt darauf an, dass es in C ++ keine absolute Trennung zwischen der "Kernsprache" und den Standardbibliotheken gibt. Es sind verschiedene Kapitel im Standard, aber jedes verweist auf das andere, und das war schon immer so.

In C ++ 11 gibt es einen anderen Ansatz: Lambdas führen Objekte ein, deren anonyme Typen vom Compiler generiert wurden. Da sie keine Namen haben, befinden sie sich überhaupt nicht in einem Namespace, schon gar nicht in std. Dies ist jedoch kein geeigneter Ansatz für Initialisierungslisten, da Sie den Typnamen verwenden, wenn Sie den Konstruktor schreiben, der einen akzeptiert.

Steve Jessop
quelle
1
Es scheint mir, dass diese Unterteilung aufgrund solcher impliziten Rollen von Typen nicht möglich ist (mailny?) . type_infound size_tsind nette Argumente .. size_tnaja ist nur ein typedef .. also lasst uns das überspringen. Der Unterschied zwischen type_infound initializer_listbesteht darin, dass der erste das Ergebnis eines expliziten Operators und der zweite das Ergebnis einer impliziten Compileraktion ist. Es scheint mir auch, dass initializer_list könnte mit einigen bereits vorhandenen Containern .. oder noch besser ersetzt werden: jeder der Nutzer erklärt als Argument Typ!
Emesx
4
... oder es könnte der einfache Grund sein, dass Sie, wenn Sie einen Konstruktor dafür geschrieben haben vector, arrayeinen Vektor aus einem beliebigen Array des richtigen Typs erstellen können , nicht nur aus einem, das durch die Syntax der Initialisiererliste generiert wird. Ich bin mir nicht sicher, ob es eine schlechte Sache wäre, Container aus irgendwelchen zu konstruieren array, aber es ist nicht die Absicht des Komitees, die neue Syntax einzuführen.
Steve Jessop
2
@Christian: Nein, std::arrayhat nicht einmal Konstruktoren. Der std::arrayFall ist einfach eine Aggregatinitialisierung. Außerdem begrüße ich Sie, sich mir im Chatraum der Lounge <C ++> anzuschließen, da diese Diskussion etwas lang wird.
Xeo
3
@ChristianRau: Xeo bedeutet, dass Elemente kopiert werden, wenn die Initialisierungsliste erstellt wird. Durch das Kopieren einer Initialisierungsliste werden keine enthaltenen Elemente kopiert.
Mooing Duck
2
@Christian List-Initialisierung bedeutet nicht initializer_list. Es kann viele Dinge sein, einschließlich einer guten alten Direktinitialisierung oder einer aggregierten Initialisierung. Keiner von diesen beinhaltet initializer_list (und einige können einfach nicht so funktionieren).
R. Martinho Fernandes
42

Das C ++ Standard Committee scheint es vorzuziehen, keine neuen Schlüsselwörter hinzuzufügen, wahrscheinlich weil dies das Risiko erhöht, vorhandenen Code zu beschädigen (Legacy-Code könnte dieses Schlüsselwort als Namen einer Variablen, einer Klasse oder was auch immer verwenden).

Darüber hinaus scheint mir die Definition std::initializer_listals Vorlagencontainer eine recht elegante Wahl zu sein: Wenn es sich um ein Schlüsselwort handeln würde, wie würden Sie auf den zugrunde liegenden Typ zugreifen? Wie würden Sie es durchlaufen? Sie würden auch eine Reihe neuer Operatoren benötigen, und das würde Sie nur dazu zwingen, sich mehr Namen und mehr Schlüsselwörter zu merken, um die gleichen Dinge zu tun, die Sie mit Standardcontainern tun können.

Wenn Sie einen std::initializer_listwie jeden anderen Container behandeln, haben Sie die Möglichkeit, generischen Code zu schreiben, der mit all diesen Dingen funktioniert.

AKTUALISIEREN:

Warum dann einen neuen Typ einführen, anstatt eine Kombination aus vorhandenen zu verwenden? (aus den Kommentaren)

Zunächst verfügen alle anderen Container über Methoden zum Hinzufügen, Entfernen und Einfügen von Elementen, die für eine vom Compiler generierte Sammlung nicht wünschenswert sind. Die einzige Ausnahme ist std::array<>, dass ein Array im C-Stil mit fester Größe eingeschlossen wird und daher der einzig vernünftige Kandidat bleibt.

Wie Nicol Bolas in den Kommentaren zutreffend hervorhebt , besteht ein weiterer grundlegender Unterschied zwischen std::initializer_listund allen anderen Standardcontainern (einschließlich std::array<>) darin, dass letztere eine Wertesemantik und std::initializer_listeine Referenzsemantik aufweisen . Das Kopieren std::initializer_listvon beispielsweise führt nicht zu einer Kopie der darin enthaltenen Elemente.

Darüber hinaus (erneut mit freundlicher Genehmigung von Nicol Bolas) ermöglicht ein spezieller Container für Listen zur Klammerinitialisierung eine Überlastung der Art und Weise, wie der Benutzer die Initialisierung durchführt.

Andy Prowl
quelle
4
Warum dann einen neuen Typ einführen, anstatt eine Kombination aus vorhandenen zu verwenden?
Emesx
3
@elmes: Eigentlich ist es eher so std::array. Aber std::arrayzuordnet Speicher während std::initializaer_listWraps ein Compiler-Array. Betrachten Sie es als den Unterschied zwischen char s[] = "array";und char *s = "initializer_list";.
Rodrigo
2
Und da es sich um einen normalen Typ handelt, sind Überladung, Vorlagenspezialisierung, Namensdekoration und dergleichen kein Problem.
Rodrigo
2
@rodrigo: std::arrayreserviert keinen Speicher, es ist eine einfache T arr[N];Sache, das gleiche, was unterstützt wird std::initializer_list.
Xeo
6
@Xeo: T arr[N] reserviert Speicher, möglicherweise nicht im dynamischen Heap, sondern anderswo ... So auch std::array. Ein Nicht-Leerzeichen initializer_listkann jedoch nicht vom Benutzer erstellt werden, sodass offensichtlich kein Speicher zugewiesen werden kann.
Rodrigo
6

Das ist nichts Neues. Beruht sich beispielsweise for (i : some_container)auf das Vorhandensein bestimmter Methoden oder eigenständiger Funktionen in der some_containerKlasse. C # verlässt sich noch mehr auf seine .NET-Bibliotheken. Eigentlich denke ich, dass dies eine ziemlich elegante Lösung ist, da Sie Ihre Klassen mit einigen Sprachstrukturen kompatibel machen können, ohne die Sprachspezifikation zu komplizieren.

Spuk
quelle
2
Methoden in der Klasse oder eigenständig beginund endMethoden. Das ist ein bisschen anders IMO.
Emesx
3
Ist es? Wieder haben Sie ein reines Sprachkonstrukt, das auf der spezifischen Konstruktion Ihres Codes beruht. Dies könnte auch durch die Einführung eines neuen Schlüsselworts geschehen sein, zum Beispieliterable class MyClass { };
Spook
Aber Sie können die Methoden platzieren, wo immer Sie wollen, sie implementieren, wie Sie wollen. Es gibt einige Ähnlichkeiten, da stimme ich zu! Diese Frage ist etwa initializer_listwenn
emesx
4

Dies ist in der Tat nichts Neues und wie viele darauf hingewiesen haben, war diese Praxis in C ++ vorhanden und beispielsweise in C #.

Andrei Alexandrescu hat jedoch einen guten Punkt erwähnt: Sie können sich das als Teil des imaginären "Kern" -Namensraums vorstellen, dann ist es sinnvoller.

Also, es ist tatsächlich so etwas wie: core::initializer_list, core::size_t, core::begin(), core::end()und so weiter. Dies ist nur ein unglücklicher Zufall, dass der stdNamespace einige Kernsprachenkonstrukte enthält.

Artem Tokmakov
quelle
2

Es kann nicht nur vollständig in der Standardbibliothek funktionieren. Die Aufnahme in die Standardbibliothek bedeutet nicht, dass der Compiler keine cleveren Streiche spielen kann.

Obwohl dies möglicherweise nicht in allen Fällen möglich ist, kann es durchaus heißen: Dieser Typ ist bekannt oder ein einfacher Typ. Lassen Sie initializer_listuns das ignorieren und haben Sie nur ein Speicherbild von dem, was der initialisierte Wert sein sollte.

Mit anderen Worten int i {5};kann äquivalent zu int i(5);oder int i=5;oder sogar intwrapper iw {5};Wo intwrapperist eine einfache Wrapper-Klasse über einem int, wobei ein trivialer Konstruktor ein nimmtinitializer_list

Paul de Vrieze
quelle
Haben wir reproduzierbare Beispiele für Compiler, die tatsächlich solche "cleveren Streiche" spielen? Es ist ein bisschen vernünftig, als ob , aber ich würde gerne eine Begründung sehen.
underscore_d
Die Idee zur Optimierung von Compilern besteht darin, dass der Compiler den Code in einen beliebigen äquivalenten Code umwandeln kann. Insbesondere C ++ setzt auf die Optimierung für "freie" Abstraktionen. Die Idee, Code aus der Standardbibliothek zu ersetzen, ist weit verbreitet (siehe die in gcc integrierte Liste gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
In der Tat ist Ihre Idee, int i {5}die irgendetwas beinhaltet, std::initializer_listfalsch. intDa kein Konstruktor verwendet wird std::initializer_list, 5wird der nur direkt zum Erstellen verwendet. Das Hauptbeispiel ist also irrelevant; Es ist einfach keine Optimierung durchzuführen. Da std::initializer_listder Compiler darüber hinaus ein "imaginäres" Array erstellt und ersetzt, kann dies meiner Meinung nach die Optimierung begünstigen, aber das ist der "magische" Teil des Compilers. Es ist also unabhängig davon, ob der Optimierer im Allgemeinen etwas Kluges mit dem Hübschen tun kann langweiliges Objekt mit 2 Iteratoren, die resultieren
underscore_d
1

Es ist nicht Teil der Kernsprache, da es vollständig in der Bibliothek implementiert werden kann, nur Zeile operator newund operator delete. Welchen Vorteil hätte es, Compiler komplizierter zu machen?

Pete Becker
quelle