C++ Standardisierung: Ein erster Blick auf die Papers & C++14



  • Ich bezog mich auf die Proposals auf phlox81s Seite. Deshalb "bisher"

    "Kleine Änderungen, die in einzelnen Spezialfällen minimale Vorteile bringen" war eine Reaktion auf Vorschläge wie struct const oder const| .



  • Nexus schrieb:

    Ich bezog mich auf die Proposals auf phlox81s Seite. Deshalb "bisher"

    "Kleine Änderungen, die in einzelnen Spezialfällen minimale Vorteile bringen" war eine Reaktion auf Vorschläge wie struct const oder const| .

    Alle Proposals findest du hier: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/#mailing2013-03

    Ja, von type| bin ich auch nicht sehr begeistert, man muss aber immer sehen, dass das ja nur Vorschläge sind.
    Denke dass sowas nicht durch Committee kommt. Generell wird es alles schwer haben, was veränderungen an der Sprache vornehmen will.
    Mit Ausnahmen der Proposals welche sich auf C++11 features beziehen, und diese verbessern oder erweitern.



  • Oh, okay. Sorry Nexus.



  • Danke für den Link, phlox81. Ich werde wahrscheinlich aber deinen Blog verfolgen, da gibts eine schöne Zusammenfassung. Vielen Dank auch dafür! 🙂



  • Nexus schrieb:

    ]Das ist auch sowas. Statt die richtigen Probleme anzugehen (Modulsystem, Concepts), werden tausend kleine Änderungen vorgeschlagen

    Ich glaube weder an das Modulsystem, noch an Concepts. Wahrscheinlich liegt es daran. Der Berg an Komplexität die diese beiden Änderungen mit sich bringen ist nicht abzusehen, eben weil das so riesige Änderungen sind. Es ist nur klar: die Komplexität der Concepts war so groß, dass Stroustrup angst davor hatte. Und wenn wir jetzt concepts-light kriegen, löst das am Ende vielleicht keins der Probleme.

    Und ich sehe auch nicht, wie ein Konzept meinen Vorschlag ersetzen wollte. Das Konzept kann nur auf Ebene der Funktionssignatur funktionieren. Es schützt aber nicht davor, das eine zweite Funktionssignatur eingeführt wird, die nun die Einschränkungen des Konzepts ignoriert. Ich kann mir also nicht sicher sein das, nach sort(vector) noch meine iteratoren auf vector gültig sind. wenn ich aber sagen würde: die struktur von vector ist konstant, dann weiß ich, das sort(vector) niemals meine iteratoren invalidiert, weil der compiler das abfangen würde.

    Ebenso würde ein Konzept nicht ermöglichen, das ein container oder eine Klasse intern ausnutzen könnte, das der Aufrufer nicht beabsichtigt, die Struktur zu verändern. Ich kann also nicht eine Referenz auf meinen container zurückgeben, damit der Bbenutzer die Werte ändern kann, weil ich keinen Weg habe zu garantieren, dass der Benutzer neue Werte hinzufügt oder anderweitig die Struktur verändert.

    Hier zum Beispiel ein Anwendungsfall der sich mit Konzepts nicht darstellen ließe:

    struct const std::vector<double> vec(10,0);
    struct const std::vector<double> vec1(10,1);
    struct const std::vector<double> vec2(20,1);
    vec=vec1;//okay, selbe struktur, allerdings copy anstatt swap um iteratoren valide zu halten.
    vec=vec2; //Laufzeitfehler: inkompatible Größen von vec und vec2
    


  • Ehm.. wtf? Laufzeitfehler? Nicht dass ich das Concepts-Light Proposal auswendig kennen würde, aber das Beispiel ergibt für mich überhaupt keinen Sinn. Auf welche Sektion im Proposal bezieht sich das? Und deine Abneigung gegenüber Modules hast du auch nicht weiter erklärt.



  • cooky451 schrieb:

    Ehm.. wtf? Laufzeitfehler?

    Es geht um Verhalten, das ich gerne hätte. Das concept verhalten wäre, dass du keinen operator= für ranges verwenden könntest. Denn wenn die Ranges auch assignable wären, dann gibt dir

    template<Range RangeType>
    sort(RangeType& range);
    

    nur sehr schwache Garantien - in der tat schwächere als

    template<class Iterator>
    sort(Iterator begin, Iterator end);
    

    Ichs ehe halt nicht, wie man das mit Konzepts umsetzen kann.



  • Wie jetzt, also das was du willst ist, dass eine Zuweisung auf ein konstantes Objekt kompiliert? 😮 Sorry, aber das kann ich nicht nachvollziehen. Edit: Eine range per Referenz zu nehmen sieht für mich auch nicht sinnvoll aus.



  • otze schrieb:

    Hier zum Beispiel ein Anwendungsfall der sich mit Konzepts nicht darstellen ließe:

    struct const std::vector<double> vec(10,0);
    struct const std::vector<double> vec1(10,1);
    struct const std::vector<double> vec2(20,1);
    vec=vec1;//okay, selbe struktur, allerdings copy anstatt swap um iteratoren valide zu halten.
    vec=vec2; //Laufzeitfehler: inkompatible Größen von vec und vec2
    

    Laufzeitfehler?!? Soll dann ne Exception fliegen?
    Wenn sollte dass zur Compiletime abgefangen werden.
    Und was du hier als container brauchst, wäre ja DynArray, und nicht vector.



  • Ich wollte nicht sagen, dass man genau den Anwendungsfall mit Concepts umsetzen könnte. Dazu kenne ich diese ehrlich gesagt zu wenig. Aber ich fürchte, dass du dir struct const als kleine Änderung vorstellst und über die Tragweite nicht im Klaren bist. CV-Qualifizierer sind so stark in die Sprache verankert, da müsstest du an etlichen Stellen anpassen.

    Und dass ein Laufzeitfehler auftritt, macht das Feature noch weniger vorteilhaft. Könnte man das Gleiche nicht mit Ranges erreichen?

    std::vector<double> vec(10,0);
    std::vector<double> vec1(10,1);
    std::vector<double> vec2(20,1);
    
    auto r = make_range(vec);
    auto r1 = make_range(vec1);
    auto r2 = make_range(vec2);
    
    exhaustive_copy(r, r1);
    exhaustive_copy(r, r2); // BAM
    
    template <typename RaRange1, typename RaRange2>
    void exhaustive_copy(RaRange1 src, RaRange2 dest)
    {
        assert(src.size() == dest.size()); // <-- oder Exception
        for (; !src.empty(); src.pop_front(), dest.pop_front())
            dest.front() = src.front();
    }
    


  • Auch so, "struct const" soll was neues sein. Was das alles mit Concepts und Modules zu tun hat, verstehe ich allerdings immer noch nicht...



  • cooky451 schrieb:

    Auch so, "struct const" soll was neues sein. Was das alles mit Concepts und Modules zu tun hat, verstehe ich allerdings immer noch nicht...

    mir wurde gessagt, dass man das mit Concepts modellieren könnte und concepts und modules the holy grail sind. Was sie nicht sind.

    @Nexus Mir ist durchaus sehr bewusst, wie groß diese Änderung wäre. Ja, ich habe mehr als 20 Minuten darüber nachgedacht.

    Klar kann man das immer irgendwie anders implementieren. Genauso wie man auch ohne concepts und modules gut klar kommt.
    Der Punkt ist: deine Version hat mehr Aufwand im Code und lagert mehr Verantwortung auf den Nutzer ab. Ich versuche das zu minimieren.

    Zum Laufzeitfehler:
    Ich wüsste nicht, wie man das Verhalten mit irgendeinem Sprachfeature in irgendeiner Implementation nicht zur Laufzeit abfangen sollte. Immerhin ist erst bekannt ob die Invariante "size()" des Vektors verletzt wird, wenn die Größe des anderen Vektors bekannt ist. Der Punkt ist aber: der Code sagt kürzer aus, was er macht und wir beschäftigen uns weniger mit dem "wie" ( exhaustive_copy oder op=) als mit dem "was", weil wir das direkt zentral im vector implementieren können:

    struct vector{
          vector operator=(vector other){//copy& swap idiom als normalfall
              swap(*this,other);
          }
          vector operator=(vector const& other)struct const{//strukturerhalted
              assert(size() == other.size()); // <-- oder Exception
              std::copy(other.begin(),other.end(),begin());
          }
    };
    

    natürlich ist da smit dem laufzeitfehler hier ein Spezialfall des Algorithmus. In anderen Fällen kann der Compiler natürlich genau entscheiden, ob und was geht:

    struct const vector<double> vec(20);
    vec.resize(10);//Compilefehler.
    

    Das ist natürlich etwas suboptimal, weil vec.resize(20) die invariante nicht verletzt, naja, aber auch das wäre lösbar:

    template<class T>
    void resize(T& vec, std::size_t size){
        vec.resize(size);
    }
    template<class T>
    void resize(T struct const& vec, std::size_t size){
        assert(vec.size() == size); // <-- oder Exception
    }
    


  • otze schrieb:

    mir wurde gessagt, dass man das mit Concepts modellieren könnte und concepts und modules the holy grail sind. Was sie nicht sind.

    Doch, das sind sie. Oder sagen wir mal so, ohne sie ist C++ quasi tot. Und was "structural constness" jetzt genau mit Concepts zu tun hat, verstehe ich immer noch nicht. Der Anwendungsbereich scheint sich ja relativ groß zu überschneiden, wenn das als Alternative vorgeschlagen wird, was ich einfach nicht sehe. (Concepts haben generell keine Auswirkung auf erzeugten Code?) Hast du die Idee irgendwo mal ausführlicher erklärt?



  • Hier ist das aktuelle paper zu concepts lite, wie es wohl aktuell aussieht, die wahrscheinlichste Variante.

    So nett die Idee mit Concepts ist, das Problem bleibt, das sie eben auch implementiert werden muss.
    Und genau da ist das Problem mit allen Neuerungen an C++ als Sprache: es dauert recht lange bis man solche features wirklich plattformunabhängig einsetzen kann.

    Für C++14 wird es wahrscheinlich oder sagen wir hoffentlich, keine größeren Änderungen an der Sprache geben, der Focus liegt auf der Library und C++11 zu verbessern. Evtl. kommt eine erste Version von Parallelism/Threading/tasks hinzu.



  • Jop, das ist wohl das Hauptproblem. Wobei ich das eher bei Modules sehe, und da kann man ja hoffen, dass die ihre ganze Zeit da rein stecken. 😉 Was Concepts angeht, zumindest die Lite Version, die sind ja gerade so designed, dass der Compiler kaum Änderungen braucht.



  • cooky451 schrieb:

    Hast du die Idee irgendwo mal ausführlicher erklärt?

    ich will das als orthogonales Konzept drin haben, und habe die Concepts selbst gar nicht ins Spiel gebracht. Allerdings muss ich mich dagegen verteidigen, dass Nexus seit dem ersten Äussern der Idee damit argumentiert, dass Concepts das als Spezialfall abdecken. Ich sehe das nicht so und habe deswegen Gegenbeispiele gebracht.

    Für mich geht es darum, das die binäre Entscheidung const vs non-const nicht flexibel genug ist. Meistens habe ich so Graufälle, bei denen der User bestimmte Aspekte meiner Objekte problemlos ändern kann, ohne dass dies Einfluss auf die Validität oder Invarianten des Objektes haben kann. Ein klassisches Beispiel ist die Länge eines arrays: ich habe häufig den Fall, dass ich zwar die Werte von arrays ändern will, aber die Größe nicht ändern darf. Oder ich muss mich darauf verlassen, dass eine bestimmte Funktion nicht dazu führt, dass der vector seinen speicher neu alloziert (zum Beispiel, weil ich momentan Zeiger/Iteratoren auf Objekte in Verwendung habe)

    Ich mache häufiger die Erfahrung, dass ich das Interface unnötig restriktiv gestalten muss, weil ich nicht garantieren kann, dass der User nicht durch einen Programmfehler die Invarianten verletzt - und selbst wenn ich dies detektiere kann es unter Umständen schwierig sein, den Fehler zu finden. Anstatt also einen vector zurückzugeben, schreibe ich Methoden, die Referenzen oder iteratoren auf die Objekte erzeugen - auch wenn ich nur sicher stellen will, dass der User nur keine Elemente löscht oder neu hinzu fügt.

    Aber ich entdecke das Problem auch anderer Stelle. std::set hat ein unnötig restriktives Interface, weil es nicht garantieren kann, dass Zugriffe auf die Elemente die Reihenfolge nicht ändern. Das Design ist verständlich, auch wenn es manchmal nervt.

    Und hier kommt struct const ins Spiel. Ich definiere damit einen Zwischenlevel zwischen const und non-const das strukturerhaltende Änderungen erlaubt. Für einen container wäre dies, das man die Größe nicht ändern kann, für ein element von std::set das operator< invariant unter den struct-const operationen ist.

    Natürlich ist dies ein Nachteil: struct const kann je nach Objektkategorie unterschiedliches bedeuten, es ist eben ein semantischer Unterschied. Wenn ich in einem vector erlaube, dass ich die Objekte ändern kann, dann bedeutet das, das sich das Ergebnis von operator< ändert. Andererseits würde ich nur selten einen vektor in ein set werfen wollen und für diese Sonderfälle kann man sich noch immer einen wrapper basteln, der die Semantik gerade rückt. Und ähnliche Phänomene erleben wir bereits mit const, auch wenn hier zugegebenermaßen die Problemfälle klarer sind.

    Die Regeln sind wie folgt: ein const objekt kann nur const-methoden aufrufen, ein struct const objekt const und struct-const methoden und ein unqualifiziertes Objekt alle Methoden. struct-const zeiger und referenzen wandeln sich automatisch in const um und unqualifiziert nach struct-const und const.

    Die Änderungen sind abwärts kompatibel mit Ausnahme wenn eine methode von const auf struct-const geändert wird. Aber alter Code sollte ohne Änderung laufen.

    //edit Für Container wäre struct-const das entgegengesetzte Konzept zu R-value references: anstatt die Datenstruktur von einem anderen Container zu übernehmen (ohne sie zu kopieren und die alte Struktur zu zerstören) werden hier Transformationen angestrebt, die Datenstruktur erhaltend sind. Dies kann in manchen Fällen zu schnelleren Algorithmen führen "ich muss nicht prüfen, dass der Nutzer die Datenstruktur ändert" aber auch in manchen Fällen die algorithmen etwas komplexer gestalten: "ich muss die Struktur erhalten und deswegen kopieren anstatt zu swappen"



  • otze schrieb:

    ich will das als orthogonales Konzept drin haben, und habe die Concepts selbst gar nicht ins Spiel gebracht.

    Ach so, und ich dachte schon es wäre eine Begründung für

    otze schrieb:

    Ich glaube weder an das Modulsystem, noch an Concepts.



  • otze schrieb:

    Allerdings muss ich mich dagegen verteidigen, dass Nexus seit dem ersten Äussern der Idee damit argumentiert, dass Concepts das als Spezialfall abdecken.

    Das hast du falsch verstanden, was ich dir mit "Ich wollte nicht sagen, dass man genau den Anwendungsfall mit Concepts umsetzen könnte" auch schon zu sagen versucht habe 😉

    Ich hab dein Feature unabhängig von Concepts betrachtet, und finde die zusätzliche Komplexität für den meiner Ansicht nach geringen Nutzen nicht gerechtfertigt.

    otze schrieb:

    Für mich geht es darum, das die binäre Entscheidung const vs non-const nicht flexibel genug ist. Meistens habe ich so Graufälle, bei denen der User bestimmte Aspekte meiner Objekte problemlos ändern kann, ohne dass dies Einfluss auf die Validität oder Invarianten des Objektes haben kann.

    Das stimmt. Aber struct const deckt von diesen Beispielen nur genau einen Teil ab. Für Nicht-Container ist das Feature nutzlos (oder es wird soweit verwässert, dass man plötzlich komplett andere Dinge als "strukturell konstant" zu definieren beginnt). Daher sollte man sich wenn schon überlegen, ob sich sowas nicht allgemeiner lösen lassen würde.

    otze schrieb:

    Anstatt also einen vector zurückzugeben, schreibe ich Methoden, die Referenzen oder iteratoren auf die Objekte erzeugen - auch wenn ich nur sicher stellen will, dass der User nur keine Elemente löscht oder neu hinzu fügt.

    Das wäre doch ein klassischer Fall, wo man eine Range zurückgeben könnte.

    otze schrieb:

    Aber ich entdecke das Problem auch anderer Stelle. std::set hat ein unnötig restriktives Interface, weil es nicht garantieren kann, dass Zugriffe auf die Elemente die Reihenfolge nicht ändern. Das Design ist verständlich, auch wenn es manchmal nervt.

    Ja, manchmal ist das nervig. Oft entstehen solche Fälle aber durch unausgereiftes Design: Der Schlüsseltyp ist nicht nur Schlüssel, sondern enthält noch diverse andere Daten. Idealerweise sollte er aber nur für den Vergleich benutzt werden, und der Rest würde im Wert-Typ einer std::map (statt std::set ) gespeichert werden.



  • Nexus schrieb:

    Ich hab dein Feature unabhängig von Concepts betrachtet, und finde die zusätzliche Komplexität für den meiner Ansicht nach geringen Nutzen nicht gerechtfertigt.

    Ja, aber hier unterscheiden wir uns. ich empfinde concepts als vom Nutzen nicht gerechtfertigt. struct const kostet im Vergleich zu dem enormen Aufwand für Concepts(und den Aufwand den die konsequente Verwendung für die User bedeutet) nur sehr wenig.

    Für Nicht-Container ist das Feature nutzlos (oder es wird soweit verwässert, dass man plötzlich komplett andere Dinge als "strukturell konstant" zu definieren beginnt). Daher sollte man sich wenn schon überlegen, ob sich sowas nicht allgemeiner lösen lassen würde.

    Gut, dann versuchen wir mal zu abstrahieren: welche bedeutenden Sonderfälle kennst du noch? ich sah und sehe das Problem bislang nur für Container in relevantem Ausmaß.

    Das wäre doch ein klassischer Fall, wo man eine Range zurückgeben könnte.

    bloated aber den Code in einem größeren Ausmaß, insbesondere, wenn es um komplexere Objekte geht für die man dann den kompletten wrapper implementieren muss. Es hilft zudem nicht bei Funktionsparametern, da muss der User den Wrapper bereit stellen, aber in der Signatur kann es Probleme geben(template deduktion).



  • otze schrieb:

    Ja, aber hier unterscheiden wir uns. ich empfinde concepts als vom Nutzen nicht gerechtfertigt. struct const kostet im Vergleich zu dem enormen Aufwand für Concepts(und den Aufwand den die konsequente Verwendung für die User bedeutet) nur sehr wenig.

    😕 wtf. C++ (bzw. speziell Templates) ist ohne Concepts (und ohne Modules) fundamental kaputt. Das Fehlen von Ersterem führt dazu dass man Compilerfehler die vom "User" verursacht wurden aus irgendwelchem Library-Code bekommt, und das Fehlen von Modules führt dazu dass man generischen Code für jede .cpp Datei die ihn benutzt noch mal kompilieren muss. Zudem sind Concepts-Lite in der Implementierung für Compiler nahezu gratis.


Anmelden zum Antworten