std::mem_movable



  • Zu GorbGorbs Beitrag wollte ich auch gerade was schreiben, pumuckl hat aber schon alles gesagt 🙂

    Wie sieht es aber mit dem typename aus, warum genau ist das bei Vererbungs- und Initialisierungsliste verboten? Ich würde hier übrigens auch von Inkonsistenz sprechen, dazu muss kein Widerspruch vorhanden sein.



  • GorbGorb schrieb:

    Zuerst mal wäre da die Erbsünde c zu nennen, Präprozessor, Ellipsen, etc.

    Mit beidem kann man tolle Sachen machen. Den Präprozessor, um Schreibarbeit zu sparen, Ellipsen für TMP.



  • Ich denke man sollte inkonsistent hier schon als unintuitiv zu den sonstigen Regeln sehen, dass der Standard selbst nicht zweideutig ist, ist glaube ich jedem hier klar.

    @virtual -> Hmja, man sollte virtual wohl eher zur Pflicht machen, anstatt es zu verbieten.
    @Don't pay
    1. @RTTI -> Hm.. man zahlt dafür wenn man es nicht nutzt? Das ist mir neu.
    2. @exceptions -> Ohne try/catch hast du keine Nachteile. Und try/catch zu nutzen ohne etwas zu werfen ist irgendwie.. suboptimal.
    @die Standardbibliothek ist mit der Sprache verwurstelt.. -> 😕
    @template -> Na ja. Das würde ich in der Tat als etwas inkonsistent ansehen, aber es ist einfach zu hilfreich um es abzuschaffen.



  • pumuckl schrieb:

    bmario_ schrieb:

    Auch wenn einem die Regeln klar sind, warum man an den verschiedenen Stellen typename braucht bzw. es nicht zulässig ist, so ist es dennoch inkonsistent.

    Unintuitiv, aber nicht inkonsistent. Inkonsistenz würde bedeuten, dass der Standard sich selbst widerspricht.

    Inkonsistent in dem Sinne, dass an vielen Stellen in C++ implizit Festlegungen gelten, aber es kein Fehler ist, diese dennoch explizit aufzuschreiben.
    Beispiele dafür:
    - in der Klassendefinition definierte Funktionen sind implizit inline, aber es ist kein Fehler, ein inline explizit hinzuschreiben.
    - definiert man einen der großen Drei nicht, wird vom Kompiler implizit einer generiert, aber es ist kein Fehler, einen semantisch äquivalenten explizit zu definieren.
    - beim Ableiten einer Klasse wird ohne Schlüsselwort implizit angenommen, dass die Ableitung public ist, aber es ist kein Fehler dies explizit hinzuschreiben
    - geerbte virtuelle Funktionen sind implizit virtuell, aber es ist kein Fehler, dies explizit hinzuschreiben.

    vs.
    - An Stellen, wo Typen erwartet werden, werden geschachtelte abhängige Namen implizit als Typen angenommen, ABER es ist ein Fehler, dies explizit hinzuschreiben
    - Voll qualifizierte geschachtelte Namen werden implizit als Typen erkannt, aber es ist ein Fehler, dies explizit hinzuschreiben.

    Das würde ich schon inkonsistent nennen, aber man kann natürlich jetzt darüber diskutieren, was man als inkonsistent ansieht 🤡



  • cooky451 schrieb:

    @typename -> Da wo kein typename steht wird ja auch kein Typ genutzt.

    Äh, doch? In den allermeisten Fällen nutzt du Typen ohne typename .

    cooky451 schrieb:

    @virtual -> Hmja, man sollte virtual wohl eher zur Pflicht machen, anstatt es zu verbieten.

    Im neuen Standard gibts override . Aber wäre natürlich schön gewesen, hätte man das von Anfang an richtig gehabt. Und new gibts ja leider nicht...



  • Nexus schrieb:

    Wie sieht es aber mit dem typename aus, warum genau ist das bei Vererbungs- und Initialisierungsliste verboten? Ich würde hier übrigens auch von Inkonsistenz sprechen, dazu muss kein Widerspruch vorhanden sein.

    Ich kann leider nicht mit einem Auszug aus dem Standard dienen, aber Scott Meyers wird das sicher nicht aus Spass in Effective C++ so beschreiben. Er erwähnt aber auch, dass das verschiedene Compiler unterschiedlich sehen mit dem Typename.



  • Nexus schrieb:

    cooky451 schrieb:

    @typename -> Da wo kein typename steht wird ja auch kein Typ genutzt.

    Äh, doch? In den allermeisten Fällen nutzt du Typen ohne typename .

    Ja, ja. Ich hab's doch sofort weggemacht, viel zu schnelle Antworten hier. 😉
    Das ist wohl auch das Einzige, was wirklich komisch ist. Warum die das wohl so festgelegt haben?



  • pumuckl schrieb:

    Unintuitiv, aber nicht inkonsistent. Inkonsistenz würde bedeuten, dass der Standard sich selbst widerspricht.

    pumuckl schrieb:

    Ich gehe sogar soweit zu behaupten dass so ein Feature nicht konsistent in die Sprache eingeführt werden könnte.

    Ich bin davon ausgegangen, dass du "inkonsist" im Sinne von "sich intuitiv nachvollziehbar verhalten" meintest. Dass ein Standard so ein feature wie std::mem_movable beinhalten und trotzdem formal widerspruchsfrei sein kann halte ich für sehr wahrscheinlich, da ja nur bisher undefined behaviour zu defined behaviour würde (ob es den Aufwand wert ist und Sinn ergibt ist natürlich fraglich).

    pumuckl schrieb:

    don't pay what you don't use wird verletzt:
    - rtti
    - exceptions (war es nicht so dass die compiler dann nicht so gut optimieren
    können? wobei exceptions ein Programm auch schneller machen können, hier gibt es wohl keinen Königsweg)

    Wo wird da was verletzt? Für Klassen ohne virtuelle Funktionen gibts auch kein RTTI. Wenn man keine Exceptions benutzt, verlangt der Standard auch keinen Overhead dafür. (Was die Compiler draus machen ist was anderes)

    Der Standard ist nicht als Beschreibung einer nur hypothetischen Sprache entworfen worden. Ein Ziel von c++ ist es, keinen unnötigen overhead im Vergleich zu c zu verursachen. Das ist bei den heutigen compilern in den von mir genannten Punkten nicht gegeben. Vorgabe und Umsetzung widersprechen sich hier (bei exceptions finde ich das aber gut so wie es ist).

    pumuckl schrieb:

    [*] die Standardbibliothek ist mit der Sprache verwurstelt: typeid, dynamic_cast und sizeof

    Mal abgesehen davon dass weder dynamic_cast noch sizeof irgendwas mit der Standardbibliothek zu tun haben: Was macht den Standard in sich inkonsistent, wenn zwei seiner Features (typeid-Operator und type_info Klasse) voneinander abhängig sind? Beide sind Teil des Standards, genauso wie Klassen und Memberfunktionen - letztere würden ohne erstere auch keinen Sinn machen.

    Welchen Typ hat die von dynamic_cast geworfene exception und wo ist der definiert? Was ist der Rückgabetyp von sizeof?
    Die standard library ist doch deshalb eine Bibliothek, weil man sie austauschen und etwas anderes stattdessen verwenden kann. Für einen sinnvollen Einsatz von typeid (und eingeschränkt auch dynamic_cast und sizeof) kann man aber nicht auf sie verzichten.

    pumuckl schrieb:

    Da bist du einem Irrtum aufgesessen. Derived sieht nur ::A, nicht Base::A.

    error: 'class Base::A' is private
    

    Im name lookup verdeckt Base::A ::A. Private Member, also die Implementierung von Base, beeinflussen, wie sich Derived verhält. Man kann eine Klasse nicht als black box schreiben, da alle privaten member sich in dieser Form bemerkbar machen. Das widerspricht der Vorgabe von c++ als OOP-Sprache.

    pumuckl schrieb:

    Und? Wo ist die Inkonsistenz? Dass du mit den Regeln auf dem Kriegsfuß stehst und Probleme damit hast, bedeutet doch nicht, dass der Standard inkonsistent ist.

    Der gleiche token steht für 2 völlig unterschiedliche Dinge. Das ist nicht gerade intuitiv.

    cooky451 schrieb:

    1. @RTTI -> Hm.. man zahlt dafür wenn man es nicht nutzt? Das ist mir neu.

    Was ist, wenn du ein Objekt an eine dll übergibst, die dann typeid aufruft, um den Namen des Typs zu bekommen? Dein Programm muss, selbst wenn dein compiler so schlau ist und das ganze Programm analysiert, irgendwo speichern, wie der Typ hieß. Das muss für jeden Typ passieren, der in nicht einsehbarem code verwendet wird.

    2. @exceptions -> Ohne try/catch hast du keine Nachteile. Und try/catch zu nutzen ohne etwas zu werfen ist irgendwie.. suboptimal.

    Dazu kann ich nichts Näheres sagen, habe aber gelesen, dass compiler bestimmte Optimierungen bei Aktivierung von exceptions nicht machen können.

    Es geht mir ja gar nicht darum, c++ schlecht zu reden, die Sprache ist halt schon 30-40 Jahre alt, dafür hat sie sich ganz gut gehalten. Ich kenne keine Sprache, die so eine Bandbreite von Bitfrickelei bis starke Abstraktion bietet.


  • Mod

    bmario_ schrieb:

    Nexus schrieb:

    Wie sieht es aber mit dem typename aus, warum genau ist das bei Vererbungs- und Initialisierungsliste verboten? Ich würde hier übrigens auch von Inkonsistenz sprechen, dazu muss kein Widerspruch vorhanden sein.

    Ich kann leider nicht mit einem Auszug aus dem Standard dienen, aber Scott Meyers wird das sicher nicht aus Spass in Effective C++ so beschreiben. Er erwähnt aber auch, dass das verschiedene Compiler unterschiedlich sehen mit dem Typename.

    C++11 schrieb:

    14.6 Name resolution [temp.res]
    :::
    5 A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword. [ Note: The typename keyword is not permitted by the syntax of these constructs. —end note ]

    C++03 schrieb:

    14.6 Name resolution [temp.res]
    :::
    5 The keyword typename shall only be used in template declarations and definitions, including in the return type of a function template or member function template, in the return type for the definition of a member function of a class template or of a class nested within a class template, and in the type-specifier for the definition of a static member of a class template or of a class nested within a class template. The keyword typename shall be applied only to qualified names, but those names need not be dependent. The keyword typename shall be used only in contexts in which dependent names can be used. This includes template declarations and definitions but excludes explicit specialization declarations and explicit instantiation declarations. The keyword typename is not permitted in a base-specifier or in a mem-initializer; in these contexts a qualified-id that depends on a template-parameter (14.6.2) is implicitly assumed to be a type name.


  • Mod

    GorbGorb schrieb:

    pumuckl schrieb:

    Da bist du einem Irrtum aufgesessen. Derived sieht nur ::A, nicht Base::A.

    error: 'class Base::A' is private
    

    Im name lookup verdeckt Base::A ::A. Private Member, also die Implementierung von Base, beeinflussen, wie sich Derived verhält. Man kann eine Klasse nicht als black box schreiben, da alle privaten member sich in dieser Form bemerkbar machen. Das widerspricht der Vorgabe von c++ als OOP-Sprache.

    Darüber kann man streiten, die Frage hier war aber die nach Inkonsistenz. Und die ist hier nicht erkennbar - im Gegenteil: einer der grundsätzlichen Vorgaben ist die, dass die Bedeutung eines Konstrukts nur von den an der jeweiligen Stelle bereits erfolgten Deklarationen abhängen sollte, nicht aber davon, wo genau das Konstrukt steht.

    class A
    {
    };
    class Base
    {
        class A
        {
        };
        friend class Derived;
    };
    class Derived
        : public Base
    {
        A a;
    };
    

    Jetzt soll dann plötzlich Base::A gemeint sein, dagegen ::A falls Derived kein friend ist? Das halte ich für überraschend.



  • camper schrieb:

    einer der grundsätzlichen Vorgaben ist die, dass die Bedeutung eines Konstrukts nur von den an der jeweiligen Stelle bereits erfolgten Deklarationen abhängen sollte, nicht aber davon, wo genau das Konstrukt steht.

    Das musst du mir noch ein bisschen ausführen:
    Es sollte doch einen Unterschied machen, ob ich A a; in einer Klasse oder auf namespace scope schreibe?

    class A
    {
    };
    class Base
    {
        class A
        {
        };
        friend class Derived;
    };
    class Derived
        : public Base
    {
        A a;
    };
    

    Jetzt soll dann plötzlich Base::A gemeint sein, dagegen ::A falls Derived kein friend ist? Das halte ich für überraschend.

    Ich würde einfach sagen, dass es ambiguous ist und damit ein Fehler. Natürlich macht das dann Probleme, wenn man sich bei

    class A
    {
    };
    class B
    {
    public:
        class A
        {
        };
        A a;
    };
    

    darauf verlässt, dass a den Typ B::A hat und nicht ::A. Aber auch das hätte man ja als Fehler werten können.


  • Mod

    GorbGorb schrieb:

    Ich würde einfach sagen, dass es ambiguous ist und damit ein Fehler. Natürlich macht das dann Probleme, wenn man sich bei

    class A
    {
    };
    class B
    {
    public:
        class A
        {
        };
        A a;
    };
    

    darauf verlässt, dass a den Typ B::A hat und nicht ::A. Aber auch das hätte man ja als Fehler werten können.

    Ich kann schon den fluchenden Maintainer sehen, der zusätzlich Arbeit bekommt, weil Bibliothek A einen neuen Bezeichner eingeführt hat der zufällig mit einem Klassenmember der Bibliothek B kollidiert und außerdem zufällig der Header für A zuerest eingebunden wird.



  • Genauso kann ein maintainer fluchen weil eine Klasse, von der er ableitet, einen neuen privaten member bekommt, der halt zufällig genauso heißt wie eine Klasse aus dem namespace der Klasse, die ableitet.


  • Mod

    GorbGorb schrieb:

    Genauso kann ein maintainer fluchen weil eine Klasse, von der er ableitet, einen neuen privaten member bekommt, der halt zufällig genauso heißt wie eine Klasse aus dem namespace der Klasse, die ableitet.

    In diesem Szenario besthet allerdings ein fundamentaler Unterschied: deine abgeleitete Klasse hängt bereits sowohl von ihrer Basisklasse als auch diesem namespace-Member ab. Es kann nicht überraschen, dass Änderungen an Teilen, zu denen Abhängigkeiten bestehen, potentiell zu Kollisionen führen.

    In meinem Szenario hingegen besteht überhaupt keine Abhängigkeit von diesem Bezeichner aus A; die Kollision ist dabei völlig zufällig und kann nicht von vornherein verhindert werden.



  • GorbGorb schrieb:

    Ich bin davon ausgegangen, dass du "inkonsist" im Sinne von "sich intuitiv nachvollziehbar verhalten" meintest. Dass ein Standard so ein feature wie std::mem_movable beinhalten und trotzdem formal widerspruchsfrei sein kann halte ich für sehr wahrscheinlich, da ja nur bisher undefined behaviour zu defined behaviour würde (ob es den Aufwand wert ist und Sinn ergibt ist natürlich fraglich).

    Genau das halte ich für unwahrscheinlich. Es würde nicht reichen, mem_movable einzuführen und ein paar kleiner Regelungen anzupassen. Stattdessen einige Dinge komplett rausgeworfen oder umgeschrieben werden, die aktuell wichtige "Verhaltensfeatures" der Sprache sind.



  • camper schrieb:

    GorbGorb schrieb:

    Genauso kann ein maintainer fluchen weil eine Klasse, von der er ableitet, einen neuen privaten member bekommt, der halt zufällig genauso heißt wie eine Klasse aus dem namespace der Klasse, die ableitet.

    In diesem Szenario besthet allerdings ein fundamentaler Unterschied: deine abgeleitete Klasse hängt bereits sowohl von ihrer Basisklasse als auch diesem namespace-Member ab. Es kann nicht überraschen, dass Änderungen an Teilen, zu denen Abhängigkeiten bestehen, potentiell zu Kollisionen führen.

    In meinem Szenario hingegen besteht überhaupt keine Abhängigkeit von diesem Bezeichner aus A; die Kollision ist dabei völlig zufällig und kann nicht von vornherein verhindert werden.

    Ich kann genauso argumentieren, dass eine Abhängigkeit von A s namespace besteht (sonst würde er ja per using namespace eingebunden), mit einer Namenskollision bei einer Änderung an diesem namespace muss also gerechnet werden (das ist ja der Grund, warum man using namespace sehr vorsichtig benutzen sollte).

    @pumuckl: An welche features hast du da gedacht?



  • GorbGorb schrieb:

    @pumuckl: An welche features hast du da gedacht?

    Vor allem an die Dinge, die der Standar als object lifetime und ibjekt identity bezeichnet. Wenn es mem_movable für etwas anderes als PODs gibt, dann werden diese Konzepte über den Haufen geworfen. Für PODs gibts dagegen afaik schon ein Trait in der Art, also wäre ein mem_movalbe dafür hinfällig.


  • Mod

    GorbGorb schrieb:

    Ich kann genauso argumentieren, dass eine Abhängigkeit von A s namespace besteht (sonst würde er ja per using namespace eingebunden), mit einer Namenskollision bei einer Änderung an diesem namespace muss also gerechnet werden (das ist ja der Grund, warum man using namespace sehr vorsichtig benutzen sollte).

    Verstehe ich nicht.

    Wobei ich kein besonderes Interesse an dieser Diskussion habe, weil ich nicht erkennen kann, wo sie hinführen soll. Vererbung beschränkt sich in C++ nicht nur auf das Interface, dass damit auch als private deklarierte Namen sichtbar werden, ist nur konsequent. Etwas anderes würde Sonderregeln erfordern, also gerade zur angeblichen Inkonsistenz beitragen.


Anmelden zum Antworten