std::initializer_list rant



  • Nachdem ich mich nun intensiv mit den neuen C++11/14 Sprachmitteln und dergleichen auseinandergesetzt habe muss ich sagen, dass std::initializer_list der wahrscheinlich größte Blödsinn ist, der es in den Standard geschafft hat. Inwiefern ergibt das Folgende Sinn?

    int a{5};
    std::vector<int> b{5, 5}; // ups, .size() == 2
    unbekannte_klasse_aus_3rd_party_library c{5}; // Darf ich hier list initialization verwenden oder nicht? Kann ich ohne die Doku nicht wissen...
    auto d{5}; // ups, d ist KEIN int!
    auto e{a}; // ups, e ist IMMER NOCH KEIN int!
    

    Anstatt dass man nun überall list initialization verwenden kann, um die Kontext-Sensibilität von () zu umgehen, muss man nun je nach Fall entscheiden, welche Art der Initialisierung korrekt (nicht nur geeignet / ungeeignet, nein, sondern gar korrekt oder inkorrekt!) ist. Die durch list initialization versprochene Vereinfachung hat - in Kombination mit std::initializer_list - alles um ein Vielfaches komplizierter gemacht.

    Zudem sei gesagt, dass ein Feature wie std::initializer_list niemals nötig war. Man kann gleiches Verhalten (mit leicht anderer Syntax) auch ansonsten implementieren mit Proxies, die den Komma-Operator überladen oder einfach mit variadic templates.

    Kann mich jemand davon überzeugen, dass std::initializer_list nicht einfach nur ein katastrophaler Designfehler war? Hat C++1z vor, da irgendwie Abhilfe zu leisten?



  • Kann ein Mod bitte das Code-Tag fixen? Habe mich vertan...



  • neckbeard420 schrieb:

    std::vector<int> b{5, 5}; // ups, .size() == 2
    

    Das ist in der Tat etwas ungünstig, aber irgendwie muss man diese Mehrdeutigkeit schliesslich auflösen. In solchen Ausnahmefällen ein b(5, 5) finde ich nicht übermässig schlimm.

    neckbeard420 schrieb:

    unbekannte_klasse_aus_3rd_party_library c{5}; // Darf ich hier list initialization verwenden oder nicht? Kann ich ohne die Doku nicht wissen...
    

    Wenn du so wenig Dokumentation hast, dann frage ich mich ob du überhaupt in der lage bist, die Klasse zu initalisieren, wenn sie nicht zufällig einen Default Constructor hat. Wer herausfinden kann, dass eine Klasse einen Konstruktor mit einem int -Argument hat, der kann auch herausfinden, dass sie einen vom Typ initializer_list<int> hat.

    neckbeard420 schrieb:

    auto d{5}; // ups, d ist KEIN int!
    auto e{a}; // ups, e ist IMMER NOCH KEIN int!
    

    Wenn man einen int möchte, dann sollte man auto auch mit einem solchen füttern und nicht mit initializer_list<int> 😃

    neckbeard420 schrieb:

    Man kann gleiches Verhalten (mit leicht anderer Syntax) auch ansonsten implementieren mit Proxies, die den Komma-Operator überladen oder einfach mit variadic templates.

    Wenn DAS die Alternativen sind, dann nehme ich diese kleine Unnanehmlichkeit der initializer_list allein schon wegen der Code-Lesbarkeit/Verständlichkeit gerne in kauf 😉

    neckbeard420 schrieb:

    Kann mich jemand davon überzeugen, dass std::initializer_list nicht einfach nur ein katastrophaler Designfehler war? Hat C++1z vor, da irgendwie Abhilfe zu leisten?

    Zumindest erstmal ein Argument: So was hier finde ich schon sehr elegant, ich kann mir kaum vorstellen dass man das mit älteren Sprachmitteln so schön hinbekommt:

    std::map<std::string, std::vector<int>> m = 
    {
    	{ "eins", { 1 } },
    	{ "zwei", { 1, 2 } },
    	{ "drei", { 1, 2, 3 } }
    };
    

    Für meinen Geschmack hat dein "Rant" etwas zu wenig Fleisch, auch wenn 1,24 deiner Argumente durchaus valide sind.
    Finnegan


  • Mod

    Finnegan schrieb:

    neckbeard420 schrieb:

    Man kann gleiches Verhalten (mit leicht anderer Syntax) auch ansonsten implementieren mit Proxies, die den Komma-Operator überladen oder einfach mit variadic templates.

    Wenn DAS die Alternativen sind, dann nehme ich diese kleine Unnanehmlichkeit der initializer_list allein schon wegen der Code-Lesbarkeit/Verständlichkeit gerne in kauf 😉

    Welche Code-Lesbarkeit/Verständlichkeit soll das sein? Kommaüberladung ist böse; Variadic-Templates allerdings sind wesentlich mächtiger als initialize_list - und erfordern nicht, dass an der Initialisierungs-Semantik herumgepfuscht werden muss. Im Wesentlichen sehe ich zwei wichtige Kritikpunkte:
    1. Variadic Templatekonstruktoren und initializer_list-Konstruktoren können nicht koexistieren. Das schränkt schon mal die Wiederverwendbarkeit ein. Und weil bestehender Code nicht kaputtgemacht werden soll, sehe ich nicht, dass diese Problematik in kommenden Standards beseitigt wird.
    2. initializer_list unterstützt keine Movesemantik.

    neckbeard420 schrieb:

    int a{5};
    auto d{5}; // ups, d ist KEIN int!
    auto e{a}; // ups, e ist IMMER NOCH KEIN int!
    

    Habe sowieso noch nie verstanden, wieso niemand mehr = verwenden will. Mit Lesbarkeit hat es jedenfalls nichts zu tun...



  • Finnegan schrieb:

    Das ist in der Tat etwas ungünstig, aber irgendwie muss man diese Mehrdeutigkeit schliesslich auflösen. In solchen Ausnahmefällen ein b(5, 5) finde ich nicht übermässig schlimm.

    Siehe:

    neckbeard420 schrieb:

    Anstatt dass man nun überall list initialization verwenden kann, um die Kontext-Sensibilität von () zu umgehen, muss man nun je nach Fall entscheiden, welche Art der Initialisierung korrekt (nicht nur geeignet / ungeeignet, nein, sondern gar korrekt oder inkorrekt!) ist. Die durch list initialization versprochene Vereinfachung hat - in Kombination mit std::initializer_list - alles um ein Vielfaches komplizierter gemacht.

    Ein Konzept, das eine bestimmte Sache (Konstuktion von Objekten) vereinfachen sollte, hat es nur noch komplizierter gemacht.

    Finnegan schrieb:

    Wenn du so wenig Dokumentation hast, dann frage ich mich ob du überhaupt in der lage bist, die Klasse zu initalisieren, wenn sie nicht zufällig einen Default Constructor hat. Wer herausfinden kann, dass eine Klasse einen Konstruktor mit einem int -Argument hat, der kann auch herausfinden, dass sie einen vom Typ initializer_list<int> hat.

    Wenn ich weiß, dass der Konstruktor einer Klasse z.B. zwei ints annimmt, so wie bei std::vector<int> , dann kann ich daraus dennoch nicht schlie­ßen, ob ich T{0,0} schreiben kann oder ob nur T(0,0) das korrekte Verhalten produziert. Um das feststellen zu können, muss ich ALLE Konstruktoren der besagten Klasse kennen.

    neckbeard420 schrieb:

    Wenn man einen int möchte, dann sollte man auto auch mit einem solchen füttern und nicht mit initializer_list<int> 😃

    Finde ich gut von dir, dass du eingestehst, dass du überhaupt keine Ahnung von dem Thema hast. Im Snippet haben weder {5} noch {a} den Typ std::initializer_list<int> . Der Sinnlosigkeit des gezeigten Snippets bin ich mir durchaus bewusst, da es auch eher exemplarisch gemeint war. Anders sieht es hingegen aus, wenn man einen wesentlich verboseren Typ zur Hand nimmt und eine Kopie eines Objekts dessen erstellen will:

    std::map<std::string, std::vector<int>> m;
    auto b(m); // ok
    auto b{m}; // ups...
    

    Finnegan schrieb:

    Zumindest erstmal ein Argument: So was hier finde ich schon sehr elegant, ich kann mir kaum vorstellen dass man das mit älteren Sprachmitteln so schön hinbekommt:

    std::map<std::string, std::vector<int>> m = 
    {
    	{ "eins", { 1 } },
    	{ "zwei", { 1, 2 } },
    	{ "drei", { 1, 2, 3 } }
    };
    

    Schön? Ja. Nützlich? Wohl eher nicht. Wie oft kommen hardgecodete Container vor? Recht selten (z.B. LUTs, die profitieren aber nicht von std::initializer_list ). Und wie oft kommen hardgecodete Non-Contiguous-Container vor? Nie. Selbst in deinem (unsinnigen) Beispiel würde man die Vektoren generieren statt sie auszuschreiben. Außerdem weiß ich nicht, was es an den von mir genannten Alternativen auszusetzen gibt. Ist das hier etwa unleserlich?

    Eigen::Matrix3f m;
    m << 1, 2, 3,
         4, 5, 6,
         7, 8, 9;
    


  • camper schrieb:

    neckbeard420 schrieb:

    int a{5};
    auto d{5}; // ups, d ist KEIN int!
    auto e{a}; // ups, e ist IMMER NOCH KEIN int!
    

    Habe sowieso noch nie verstanden, wieso niemand mehr = verwenden will. Mit Lesbarkeit hat es jedenfalls nichts zu tun...

    So wie ich das verstanden habe, wollte man mit der list-initialization von C++11 die Initialisierung von Objekten vereinheitlichen, was ich eine gute Idee finde (korrigier mich, wenn das nicht stimmt). Bleibt da bloß, dass man dank std::initializer_list teilweise immer noch auf die beiden anderen Alternativen zurückfallen muss. Ohne sie könnte man überall und für alles list-initialization einsetzen. Relevant: https://xkcd.com/927/


Anmelden zum Antworten