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



  • 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.



  • cooky451 schrieb:

    😕 wtf. C++ (bzw. speziell Templates) ist ohne Concepts (und ohne Modules) fundamental kaputt.

    Nein, unsere Art wie wir mit templates librarycode programmieren ist fundamental kaputt. In jedem Code finden sich massenweise asserts um im debug modus den Code so früh wie möglich abrauschen zu lassen, wenn was nicht stimmt. Aber kaum jemand verwendet static_assert für Typlogik. Natürlich kriegt man dann einen 26 Funktionen tiefen instantiation-context vom compiler, genauso wie die backtraces in einem assertfreien Code lang und informationsfrei sind. Es würde aber niemand hingehen und sagen: "C ist fundamental kaputt", sondern "verwende assert".

    Concepts sind im Vergleich dazu die Forderung, wrappertypen zu basteln, die permanent überprüfen, ob alle Objektinvarianten gültig sind.



  • Nein. Typen und Werte sind etwas fundamental anderes. Abgesehen davon, wenn man in C automatische asserts vom Compiler generieren lassen könnte die perfekte Fehlermeldungen ausspucken, alles auf gültige Werte überprüfen und keinen Runtime-Overhead produzieren, (und einem auch noch mitteilen an welcher Stelle das erste mal ein Wert ungültig war) dann wäre das wahrscheinlich nächste Woche von GCC implementiert. (Und eine Woche danach zur Standardisierung vorgeschlagen.)

    Concepts sind wie Compile-Time Interfaces, und ohne die sind Templates einfach kaputt, da kann man noch so viel static_asserten. static_assert dokumentiert kein Interface nach außen, und es kann auch nicht dafür benutzt werden zwischen verschiedenen Implementierungen auszuwählen. Dafür braucht man dann wieder enable_if und andere Tricks. Ja, man kann mit Concepts nicht mehr machen als vorher, die Sprache wird dadurch allerdings endlich ernsthaft benutzbar. (Na ja noch nicht ganz, es fehlen noch Modules.)



  • So, Teil 2 ist da! 🙂



  • Ach, pick_a_number ist genau was ich immer wollte.

    Und swap-Operatoren hört sich ziemlich lustig, dennoch unnötig an.

    Unicode-Support 👍

    Das aber mit Abstand geilste sind generische Lambdas. Bin fast vom Stuhl gefallen.



  • Bezüglich otzes Themengebiet:
    Vielleicht wäre es ja cool, wenn man benannte Invarianten einführen könnte, für benutzerdefinierte Typen und die Methoden mit den Invarianten, die gültig bleiben, taggen könnte. Sowas wie

    void somefunct() const<SizeInvariant>;
    

    Und dann könnte man jedem Referenz- und Pointertyp halt einen Satz von diesen Tags verpassen, bzw. halt um kompatibel zu bleiben, für einen normal-const eben die volle Liste und für nicht-const die leere Liste - wenn man also keine Tags einführt, funktioniert alles wie zuvor.



  • Mal zusammenfassen:

    • Swap Operator: Loest das ADL Problem, sehe aber nicht, warum man dafuer einen extra Operator einfuehren muss.
    • Polymorphic Lambdas: Einfach nur haesslich. Meine Lambda-Lib (angelehnt an Boost.Lambda) ist immer noch schoener.
    • Resumable Functions: Extrem geil. Hoffentlich noch in C++14.
    • quoted Strings: Endlich mal ein nuetzlicher Manipulator.
    • Unicode: Wurde langsam mal Zeit.

    Rest interessiert mich derzeit nicht.



  • Kellerautomat schrieb:

    Swap Operator: Loest das ADL Problem, sehe aber nicht, warum man dafuer einen extra Operator einfuehren muss.

    Das ADL-Problem ist doch schon gelöst?

    std::iter_swap(&a, &b);
    

    quoted Strings: Endlich mal ein nuetzlicher Manipulator

    Macht die IOstreams immer noch nicht benutzbar. Ein getline-Manipulator wäre praktischer.

    Polymorphic Lambdas: Einfach nur haesslich. Meine Lambda-Lib (angelehnt an Boost.Lambda) ist immer noch schoener.

    Ich find das super. Damit sind Lambdas endlich einigermassen angenehm. Jetzt stört eigentlich nur noch das "return". Zeig mal deine Lambda-Lib.

    Unicode: Wurde langsam mal Zeit.

    Ja, das ist mal ein Anfang.



  • adli schrieb:

    Polymorphic Lambdas: Einfach nur haesslich. Meine Lambda-Lib (angelehnt an Boost.Lambda) ist immer noch schoener.

    Ich find das super. Damit sind Lambdas endlich einigermassen angenehm. Jetzt stört eigentlich nur noch das "return". Zeig mal deine Lambda-Lib.

    Die haetten einfach das 'auto' Keyword entfernen sollen. Boost.Lambda kannst du in der Doku nachsehen, ich hab da nur nen Rewrite mit ein paar C++11 Anpassungen gemacht.



  • otze schrieb:

    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ß.

    Fällt mir gerade nicht viel ein, aber ohne dich wäre ich auch nicht auf die Container-Problematik aufmerksam geworden. Grundsätzlich klänge aber Decimads Vorschlag schon mal nach einer sehr guten Idee -- ob wir das in C++ brauchen, ist die andere Frage...

    otze schrieb:

    Es [Range zurückgeben] hilft zudem nicht bei Funktionsparametern, da muss der User den Wrapper bereit stellen, aber in der Signatur kann es Probleme geben(template deduktion).

    Hm, hier bin ich mir nicht sicher, wie du das meinst. Wie stellst du dir Range-Parameter vor? Es gibt ja mehrere Möglichkeiten, so eine Übergabe zu realisieren:

    // 1. Reiner Template-Parameter
    template <typename Range>
    void fn(Range r);
    
    // 2. Concepts (weiss nicht, wie die aktuellste Version aussieht)
    template <RandomAccessRange Range>
    void fn(Range r);
    
    // 3. Templatisierte Typen
    template <typename Itr>
    void fn(iterator_range<Itr> r);
    
    // 4. Type Erasure - kein Template
    void fn(type_erased_range<int> r);
    

    Kellerautomat schrieb:

    Die haetten einfach das 'auto' Keyword entfernen sollen.

    Edit: Stimmt, das haben sie im neuen Paper wieder revidiert. Und ich hab mich beim Lesen des Portland-Papers schon gefreut 😞

    Kellerautomat schrieb:

    Boost.Lambda kannst du in der Doku nachsehen, ich hab da nur nen Rewrite mit ein paar C++11 Anpassungen gemacht.

    Bei Boost.Lambda ist halt recht viel schwarze Magie dahinter. Du kannst zwar nett Ausdrücke basteln, aber nicht ganze Funktionen. Hast du mit deiner Bibliothek nicht sofort extrem verschachtelte Typen und lange Kompilierzeiten?



  • Nexus schrieb:

    Kellerautomat schrieb:

    Die haetten einfach das 'auto' Keyword entfernen sollen.

    Haben sie ja.

    Waere mir neu. Mein aktueller Stand ist:

    [] (auto x) {}
    

    ... was ja mal dermassen haesslich ist.

    Nexus schrieb:

    Kellerautomat schrieb:

    Boost.Lambda kannst du in der Doku nachsehen, ich hab da nur nen Rewrite mit ein paar C++11 Anpassungen gemacht.

    Bei Boost.Lambda ist halt recht viel schwarze Magie dahinter. Du kannst zwar nett Ausdrücke basteln, aber nicht ganze Funktionen. Hast du mit deiner Bibliothek nicht sofort extrem verschachtelte Typen und lange Kompilierzeiten?

    Doch, ich kann damit Funktionen bauen. if/else, try/catch, while/for, new/delete, alles dabei. Das mit den verschachtelten Typen ist zwar richtig, aber ich sehe nicht, warum das ein Problem sein sollte. Man nimmt ja eh auto dafuer, oder uebergibt es einem Funktionstemplate, z.B. einen std Algo. Ueber Kompilierzeiten kann ich nicht klagen.



  • So, nun ist auch Part 3 fertig.

    Diesmal mit concepts lite und transactional memory. Letztere ist wohl noch ziemlich unausgegoren.
    Weitere Highlights finde ich die letzten 3 Papers. N3595 und N3596 sind von Peter Gottschling.



  • "Wording for accessing Tuplefields by type", "Delimited Iterators" - Wer kommt auf solche unnötigen Vorschläge?

    Neue Allokatoren, Concepts Lite, make_unique, Relaxing constraints on constexpr functions : 👍



  • Eines meiner wtf-Hightlights aus den Papers ist:

    The primary implementation hurdle will be the implementation of an allocator that can provide executable memory, whereas traditional allocators do not provide such.

    N3574


Anmelden zum Antworten