auto placeholder für Variablendefinitionen - initializer lists


  • Mod

    Viele regen sich seit jeher darüber auf, dass bei auto als placeholder bei einer Variablendefinition initializer lists automatisch als initializer_list deduziert werden:

    auto i = {1, 2, 3}; // Der Typ von i ist std::initializer_list<int>
    

    Zum einen weil es inkonsequent zur Deduzierung von Template-Argumenten ist - eine initializer list als Funktionsargument ist ein non-deduced context, demenstprechend wird kein Template-Argument für ein Funktionsargument wie {1, 2} deduziert.

    Das hat jedoch einen guten Grund: Wenn zwei Funktionsparameter mit demselben Template-Parameter im Typ deklariert werden, kann ein Funktionsargument eine initializer list sein, weil das Template-Argument ausschließlich vom jeweils anderen Argument deduziert werden darf. Das verkürzt die Schreibweise deutlich:

    copy( istream_iterator<int>{cin}, {}, back_inserter(my_vec) );
    // Statt
    copy( istream_iterator<int>{cin}, istream_iterator<int>{}, back_inserter(my_vec) );
    

    (Wären initializer lists also kein non-deduced context, und es gäbe dieselbe Regelung wie bei auto , dann würde es bei der ersten Variante einfach einen Fehler geben, da man keinen Elementtyp für die initializer_list von der leeren Liste deduzieren könnte)
    Daher ist das für mich nicht wirklich überzeugend.

    Zum anderen regt man sich darüber auf weil man keinen Grund für diese Regelung finden kann und sich folglich fragt, was wohl die Überlegung hinter diesem Feature gewesen sein mag. Schließlich scheint es reichlich überflüssig und nur ein weiterer Stolperstein zu sein.

    Ich glaube den Grund gefunden zu haben: range-based for. Genau genommen folgendes Idiom:

    for( auto i : {...} )
        // Tu verschiedenes für verschiedene Werte von i
    

    Finde ich extrem sexy und habe es erst gerade wieder verwendet. Es funktioniert, weil eine Referenz auf die Range deklariert wird, und unsere Spezialregel greift:

    §6.5.4/1 schrieb:

    auto && __range = range-init;
    

    Für new auto wurde die Regel übrigens nicht eingeführt - dort ist eine initializer list als initializer nicht erlaubt.

    Aber das muss Meyers ja schon gekannt haben, als er neulich im C++-Talk darauf hinwies, dass er denjenigen der für die Aufnahme dieser Regel verantwortlich ist gerne mal fragen würde, wofür diese Regelung existiert.

    Also: Warum ist dieses Idiom kein Argument für diese Spezialregelung? Weil man einfach hätte sagen können "Hey, für range-base for sind initializer lists erlaubt"?
    Das schmeckt zumindest mir nicht so, weil man auch an anderen Stellen wo bspw. ein Makro eine Range erwartet, so etwas ausnutzen könnte. Also ein Grund könnte es sicherlich gewesen sein.
    Oder nicht? Und wo könnte man das noch anwenden?



  • Nach meiner Ansicht sind initializer_lists ohnehin voellig falsch erdacht.
    Sie sind zwar ein nettes syntaktisches Feature, aber da die Objekte nicht aenderbar sind, kann man ja auch nicht moven.
    Ich haette { ... } automatisch den Typ std::array gegeben, dann haette man sie wenn noetig falls selber als const-reference uebergeben und andernfalls halt auch moven koennen.



  • @Arcoth: Was würde auto als Typ deduzieren, wenn nicht std::initializer_list<T> ? Oder ist die Frage, warum es nicht immer zu Compilerfehlern wie bei Funktionstemplates führt? Falls ja, hast du die Frage doch schon beantwortet, oder? Bei auto führt das Erlauben zu keinen Konflikten und erlaubt gleichzeitig einige sinnvolle Anwendungsfälle.

    @Marthog: Das Problem ist, dass es bisher bereits die {}-Syntax gab (Aggregat-/Array-Initialisierung), und man std::initializer_list nachträglich irgendwie einbauen musste. Zudem musste man es mit der "uniform" initialization sinnvoll kombinieren, welche an sich schon sehr fragwürdig ist... Wäre interessant zu schauen, wie sowas von Beginn an umgesetzt würde. Kennt jemand eine statisch typisierte Sprache, die ein ähnliches Konzept etwas anders umsetzt?


  • Mod

    Marthog schrieb:

    Sie sind zwar ein nettes syntaktisches Feature, aber da die Objekte nicht aenderbar sind, kann man ja auch nicht moven.

    Ja, aber die initializer_list speichert die Elemente nicht selbst, daher kannst du sie kostenlos hin und her kopieren. 😉

    Nexus schrieb:

    @Arcoth: Was würde auto als Typ deduzieren, wenn nicht std::initializer_list<T> ?

    Nein, dass der einzig sinnvolle Typ std::initializer_list ist, ist klar. Etwas anderes wie das von Marthog vorgeschlagene ( std::array ) ist mMn. nicht sinnvoll.

    Oder ist die Frage, warum es nicht immer zu Compilerfehlern wie bei Funktionstemplates führt?

    Exakt. Viele (auch in SO Chats) haben diskutiert, wozu die Regel überhaupt eingeführt wurde, und ich habe stets im Hinterkopf behalten, nach Gründen zu suchen.

    Falls ja, hast du die Frage doch schon beantwortet, oder? Bei auto führt das Erlauben zu keinen Konflikten und erlaubt gleichzeitig einige sinnvolle Anwendungsfälle.

    Ja, aber warum sieht Meyers das anders? Der ist doch nicht dumm und wird sicher darüber nachgedacht haben?
    In seinem Bash-Talk sagt er an dieser Stelle explizit, dass er diese Entscheidung nicht nachvollziehen kann. Das hat mich tatsächlich geärgert, weil hier offensichtlich ein Mainstream-C++-Experte einfach nicht genug auf so ein Thema eingegangen ist, bzw. mögliche Gründe nicht einmal nebenbei angesprochen hat, obwohl er offenbar jahrelang erfolglos versucht hat, den Grund für diese Spezialregelung (u.a. vom Komitee) zu erhalten.



  • Meyers ist auch nur ein Mensch. Vielleicht hat er was übersehen (in seinem Effective C++ waren ja einzelne Ratschläge auch nachträglich revidiert worden) oder hält die Gründe einfach nicht für wichtig. Du hast ja gezeigt, dass es solche gibt.

    Bei diesem Talk ging es sowieso mehr darum, Inkonsistenzen aufzuzeigen, statt diese im Detail nachzuvollziehen (was dem Publikum wahrscheinlich entgegenkam). Ich könnte mir vorstellen, dass das "I have tried for years" durchaus überspitzt ist, und er C++ hier schlechter darstellen will als es ist, bzw. keine Lust hat, die Einzelheiten in aller Länge durchzukauen.

    Frag ihn doch, warum er eigentlich Jahre braucht um nichts herauszufinden, während du die Antwort innert kürzester Zeit kanntest 😃


  • Mod

    Nexus schrieb:

    Frag ihn doch, warum er eigentlich Jahre braucht um nichts herauszufinden, während du die Antwort innert kürzester Zeit kanntest 😃

    Das stimmt so nicht. Sicherlich ist meine Begründung einfach nicht überzeugend, bzw. falsch. Aber habe ihn trotzdem mal kontaktiert.

    Habe auch die Begründung darauf erweitert, dass man i.A. Initializer für Verweise auf Ranges/Kopien von Ranges nicht für initializer lists "spezialisieren" will. Wenn ich ein syntaktisches Konstrukt habe das eine Range darstellt, dann will ich - egal ob es eine braced-init-list oder ein lvalue das auf einen vector verweist ist - folgendes schreiben können, vielleicht in einem weiteren Standard oder in einem Makro:

    auto&& ref = ...;
    auto copy = ...;
    

Anmelden zum Antworten