C++20 Tutorial/Howto: Concepts



  • Nach dem "Conceps" in der Überschrift, hab ich nicht viel erwartet 😉 , aber mir hat es auch ganz gut gefallen.



  • Vielen Dank für das Tutorial. Es sollte hier im Forum einen abgetrennten Bereich geben indem Mitglieder so etwas veröffentlichen können. Auch mit Anhängen etc.. Kenne das auch aus anderen Foren wo es gut funktioniert.



  • @Jockelx sagte in C++20 Tutorial/Howto: Conceps:

    Nach dem "Conceps" in der Überschrift, hab ich nicht viel erwartet 😉 , aber mir hat es auch ganz gut gefallen.

    Ich werde es noch etwas aufbohren. Ich muss nur vorher immer testen, ob meine Codefragmente auch tun. Concepts in Verbindung mit modernem C++ kann recht "perverse" Auswüchse annehmen, zum Beispiel wenn ich noch variadic templates und parameter unpacking mit reinnehme. ☺



  • 👍



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Conceps:

    Bei den Beispielen hier verwende ich eine Menge der neuen C++ Features, wie exception-correctnes mit den noexcept Modifiern,

    Ich bin mir nicht sicher was du mit "exception-correctnes" meinst. Klingt aber gefährlich. noexcept ist ein Versprechen, und wenn du es brichst, dann wird terminate aufgerufen. Autsch.
    Daher ist es mMn. am besten noexcept einfach nicht anzugeben, so lange man keinen guten Grund dafür hat.

    Gründe:

    • noexcept in eigenem Code sollte eine "red flag" sein. Wenn man überall noexcept stehen hat, weil man haufenweise noexcept(false) schreibt, desensibilisiert einen das bloss, und man übersieht falsche/unpassende noexcept Angaben leicht.
    • Je öfter man einen noexcept Specifier schreibt, desto mehr Möglichkeiten hat man dabei einen Fehler zu machen. Und da dir der Compiler hier keine Diagnostic gibt, bleibt der dann unerkannt.
    • Gerade in Templates ist noexcept sehr schwer korrekt hinzubekommen.

    Beispiel:

    template <Number T>
    constexpr T multiplyAdd(const T &a, const T &b, const T &c) noexcept
    {
        return a * b + c;
    }
    

    Das noexcept scheint OK. Ist es aber nicht mehr, sobald T z.B. eine BigNum Klasse ist deren Operatoren z.B. Speicher anfordern und daher bad_alloc werfen können.

    Man kann noexcept bewusst/gezielt in einem Programm so einsetzen, dass man etwas mit noexcept markiert, was in bestimmten Fällen Exceptions werfen kann. z.B. wenn eine Operation nur Exceptions wirft in Fällen die man nicht supporten muss, und der Code deutlich einfacher wird wenn man davon ausgehen kann dass es eben nie passiert. Dann schreibt man noexcept dran, und in dem Fall dass so ein "muss-nicht-supported-werden" Szenario eintritt, wird kontrolliert terminate aufgerufen statt dass das Programm z.B. ein falsches Ergebnis ausspuckt.
    Kann man machen. Dann sollte man sich aber sehr genau im Klaren darüber sein wie noexcept funktioniert und gut überlegt haben ob das auch wirklich Sinn macht.

    Anfängern beizubringen möglichst überall noexcept dranzuschreiben ist mMn. auf jeden Fall ein schwerer Fehler.



  • Weiteres Beispiel:

    template <Tuple <1,10>T>
    constexpr void printAll([[maybe_unused]] T &tuple) noexcept
    {
        [&]<size_t ...elements>(std::index_sequence<elements ...>)
        {
            (std::cout << elements << std::endl, ...);
        }
        (std::make_index_sequence<std::tuple_size_v<T>>{});
    }
    

    AFAIK kann man auf std::cout wie auf jedem anderen Stream Exceptions aktivieren. In dem Fall wäre das noexcept wieder gelogen.


    Allgemeiner Rat an alle: Lasst die Finger von noexcept, es sei denn ihr wisst ganz genau wie es funktioniert, und dass und warum ihr es an der Stelle braucht.



  • @Zhavok sagte in C++20 Tutorial/Howto: Conceps:

    Vielen Dank für das Tutorial. Es sollte hier im Forum einen abgetrennten Bereich geben indem Mitglieder so etwas veröffentlichen können. Auch mit Anhängen etc.. Kenne das auch aus anderen Foren wo es gut funktioniert.

    Es gibt zumindest https://www.c-plusplus.net/forum/category/59/die-artikel. Schätze mal Moderatoren verschieben es da rein, wenn es sich lohnt?



  • @hustbaer sagte in C++20 Tutorial/Howto: Conceps:

    Weiteres Beispiel:

    template <Tuple <1,10>T>
    constexpr void printAll([[maybe_unused]] T &tuple) noexcept
    {
        [&]<size_t ...elements>(std::index_sequence<elements ...>)
        {
            (std::cout << elements << std::endl, ...);
        }
        (std::make_index_sequence<std::tuple_size_v<T>>{});
    }
    

    AFAIK kann man auf std::cout wie auf jedem anderen Stream Exceptions aktivieren. In dem Fall wäre das noexcept wieder gelogen.


    Allgemeiner Rat an alle: Lasst die Finger von noexcept, es sei denn ihr wisst ganz genau wie es funktioniert, und dass und warum ihr es an der Stelle braucht.

    Keine Sorge, ich weiß genau wann ich das noexcept verwende. Hier ist mit allerdings ein Fehler durchgerutscht. Muss an der Stelle ganz klar ein noexcept(false) sein. Sobald man auf einem Stream arbeitet, hat man im Normalfall auch Exceptions mit drin. Eventuell solltest du dir mal Scott Meyers Beitrag zu noexcept anschauen, es ist eben nicht so wie es in den meisten Referencen drinsteht. noexcept heißt nicht garantiert keine Exception, sondern höchstwahrscheinlich. Auch die Annahme, dass noexcept wegzulassen oder noexcept(false) zu schreiben, sei das Gleiche, trifft auch nicht zu. Ich arbeite damit schon seit 10+ Jahren und kenne die Fallstricke. Eventuell mach ich auch noch ein Tutorial/Howto zu C++ Gotchas. Das wird garantiert sehr spaßig.

    Und wo wir gerade bei Gotchas waren. Du bist in ein solches hineingelaufen. Dass vor dem tuple Parameter ein maybe_unused steht, hätte dich hellhörig machen sollen. Denn nirgendwo in dem Functiontemplate wird tuple verwendet. Naja, es ist keine Funktion, auch wenn es so aussieht. ☺



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Keine Sorge, ich weiß genau wann ich das noexcept verwende.

    Also es macht nicht den Eindruck.

    Hier ist mit allerdings ein Fehler durchgerutscht. Muss an der Stelle ganz klar ein noexcept(false) sein.

    MMn. gehört da, so wie fast überall, einfach gar kein noexcept hin. Eben weil man damit so schnell Fehler macht. Hatte ich aber auch schon geschrieben.

    Eventuell solltest du dir mal Scott Meyers Beitrag zu noexcept anschauen, es ist eben nicht so wie es in den meisten Referencen drinsteht. noexcept heißt nicht garantiert keine Exception, sondern höchstwahrscheinlich.

    Nein. noexcept(true) heisst: Aufrufer dieser Funktion können keine Exceptions verdauen, daher, falls doch eine Fliegt, bitte ruf terminate auf statt die Exception weiter fliegen zu lassen.

    Was Scott Meyers Beitrag angeht: Ich halte viel von Scott Meyers, aber den speziellen Rat "use noexcept wherever you can" halte ich für furchtbar.

    Auch die Annahme, dass noexcept wegzulassen oder noexcept(false) zu schreiben, sei das Gleiche, trifft auch nicht zu.

    Also mir fallen jetzt nur zwei Fälle ein wo es einen Unterschied macht:

    • Destruktoren
    • inline = default von "special member functions"

    Davon abgesehen: wo hab ich denn geschrieben dass ich das annehme?

    Und wo wir gerade bei Gotchas waren. Du bist in ein solches hineingelaufen. Dass vor dem tuple Parameter ein maybe_unused steht, hätte dich hellhörig machen sollen. Denn nirgendwo in dem Functiontemplate wird tuple verwendet. Naja, es ist keine Funktion, auch wenn es so aussieht. ☺

    Nein. Liest du auch mal was ich schreibe? Das Tuple interessiert mich nicht.

    Deine Funktion gibt die Indexe des Tuple-Typs mittels std::cout aus.
    Auf std::cout könnten Exceptions aktiviert sein. Das da meine ich: https://www.cplusplus.com/reference/ios/ios/exceptions/
    D.h. der Stream-Insertion Operator könnte eine Exception werfen wenn z.B. std::cout in ein File umgelenkt ist und die Disk voll ist.



  • @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Keine Sorge, ich weiß genau wann ich das noexcept verwende.

    Also es macht nicht den Eindruck.

    Sicher, alle Beispiele zuvor mit einen std::cout hatten ein noexcept(false) dran. Das Tuple Beispiel war das erste, dass das nicht hatte, weil ich es eben noch schnell eingefügt hatte.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Was Scott Meyers Beitrag angeht: Ich halte viel von Scott Meyers, aber den speziellen Rat "use noexcept wherever you can" halte ich für furchtbar.

    Entspricht aber nicht der Realität. Halbwegs gut gepflegter Code macht eine klare Ansage über das Interface. Schau zum Beispiel mal in den Qt Code, da ist es überall ordentlich definiert.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Nein. Liest du auch mal was ich schreibe? Das Tuple interessiert mich nicht.

    Ja tue ich. Was ich an der Stelle meinte ist, dass durch das Unrolling zum Schluss nur noch die reinen Calls zu den std::cout operator<< übrig bleiben. Das noexcept ist an der Stelle bedeutungslos, kann man in dem Assembler Output sehen.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Deine Funktion gibt die Indexe des Tuple-Typs mittels std::cout aus.

    Aber das ist wohl mein geringstes Problem hier ... uiuiui...

    Auf std::cout könnten Exceptions aktiviert sein. Das da meine ich: https://www.cplusplus.com/reference/ios/ios/exceptions/
    D.h. der Stream-Insertion Operator könnte eine Exception werfen wenn z.B. std::cout in ein File umgelenkt ist und die Disk voll ist.

    Ich weiß, dass std::cout Exceptions werfen kann. Alle Streams können zumindest eine Exception in Verbindung mit dem Stream-state werfen. Aber in einer Beziehung hast du völlig recht. Nur die Indexe des Tuples auszugeben ist ein wenig bescheuert. Wie du siehst, ich mache auch Fehler - reichlich, bescheuert und gelegentlich peinlich. Ist aber auch echt cool, dass jemand genau draufschaut. 😅 Ich sollte echt aufhören existierenden Code auf Minimalbeispiel zusammenschrupfen. Sowas kann echt schiefgehen, wie man hier sieht. Hmm, wie fixe ich es am besten, sodass es ein praktisches Compile-Zeit Beispiel bleibt? 🤔 Ich lass mir was einfallen.


  • Mod

    @Leon0402 sagte in C++20 Tutorial/Howto: Concepts:

    @Zhavok sagte in C++20 Tutorial/Howto: Conceps:

    Vielen Dank für das Tutorial. Es sollte hier im Forum einen abgetrennten Bereich geben indem Mitglieder so etwas veröffentlichen können. Auch mit Anhängen etc.. Kenne das auch aus anderen Foren wo es gut funktioniert.

    Es gibt zumindest https://www.c-plusplus.net/forum/category/59/die-artikel. Schätze mal Moderatoren verschieben es da rein, wenn es sich lohnt?

    Nein, nicht wirklich. Das ist Archiv aus lang vergangenen Tagen, als die Vision für die Seite "Redaktionelle Inhalte mit angehängtem Forum" war. Jetzt ist es primär ein Forum.



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Keine Sorge, ich weiß genau wann ich das noexcept verwende. Hier ist mit allerdings ein Fehler durchgerutscht. Muss an der Stelle ganz klar ein noexcept(false) sein. Sobald man auf einem Stream arbeitet, hat man im Normalfall auch Exceptions mit drin. Eventuell solltest du dir mal Scott Meyers Beitrag zu noexcept anschauen,

    Ich verstehe Scott Meyers aber nicht so, dass man überall noexcept oder noexcept(false) dran schreiben sollte, sondern nur noexcept wenn möglich. Abgesehen von wenigen Ausnahmen ist noexcept(false) default.
    Ist mir auch in der Form noch nie über den Weg gelaufen. Bei QT habe ich mir jetzt den Source Code nicht angeschaut, aber zumindest in der Doku konnte ich da auch nichts zu finden.



  • @Schlangenmensch sagte in C++20 Tutorial/Howto: Concepts:

    Ich verstehe Scott Meyers aber nicht so, dass man überall noexcept oder noexcept(false) dran schreiben sollte, sondern nur noexcept wenn möglich. Abgesehen von wenigen Ausnahmen ist noexcept(false) default.

    Ja, hätte ich vielleicht auch nochmal direkt erwähnen sollen. Scott Meyers sagt ganz klar, dass man es verwenden soll, wann immer man kann. Ich schreibe noexcept(false) überall dran (statt es komplett weg zu lassen), weil ich etwas pedantisch bin. Mein Ansatz hier ist: In dem Codeabschnitt besteht die Möglichkeit, dass Exceptions fliegen, weil Strukturen/Funktionen verwendet werden. Weglassen bedeutet für mich "ich weiß es nicht", was eben nicht stimmt. Das meine ich auch mit der klaren Ansage in Interfaces. Ich drücke damit aus, hier können Exceptions fliegen. Wirst auch sehen, dass ich virtual und override immer zusammen verwende, auch wenn man da virtual weglassen kann. Später wirst du auch sehen, dass ich noexcept(noexcept(expr)) verwende, wenn ein Concept Typen zulässt, die nicht exception-safe sind (ganz so wie es in der STL auch gemacht wurde).

    @Schlangenmensch sagte in C++20 Tutorial/Howto: Concepts:

    Ist mir auch in der Form noch nie über den Weg gelaufen. Bei QT habe ich mir jetzt den Source Code nicht angeschaut, aber zumindest in der Doku konnte ich da auch nichts zu finden.

    Ja, in der Doku und der Reference von Qt ist es nicht extra vermerkt. Aber wenn man zum Beispiel die Qt Quelltexte durchsuchen muss, weil bis heute das QLayout in deren Fusion Theme QGroupBox oben zwischen Überschrift und Content Bereich 6-12 Pixel abschneidet, fällt einem das schnell auf. (Okay, ich weiß nicht ob das in Qt6 gefixt ist, aber der Scheiß ist in allen Qt5 Versionen.) Richtig auffällig wird es, wenn du in die Qt Komponenten schaust, wo Performance eine große Rolle spielt. Zum Beispiel die Vector/Point und Matrix Implementierungen. Ansonsten kann man sich hier auch mal die STL Implementierungen (libstd++ und libc++, keine Ahnung wie es in den Visual Studio aussieht), insbesondere die C++11 und spätere Komponenten, anschauen. Da ist es konsequent umgesetzt.



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Ich schreibe noexcept(false) überall dran (statt es komplett weg zu lassen), weil ich etwas pedantisch bin.
    [...]
    Später wirst du auch sehen, dass ich noexcept(noexcept(expr)) verwende, wenn ein Concept Typen zulässt, die nicht exception-safe sind (ganz so wie es in der STL auch gemacht wurde).

    Gepaart mit Pedanterie kann das ganz schnell zum sprichwörtlichen "Rabbit Hole" werden. Man kann ja durchaus endlos lange noexcept(noexcept(expr))-Ausdrücke hinschreiben, die die gesamte Funktion nachzeichnen 😉 ... an den Punkt kam ich mal, als ich dachte "das ist ja ne tolle Sache". Seitdem beschränke ich mich bei noexcept hauptsächlich auf Konstruktoren (Container wie std::vector können da stellenweise effizientere Operationen durchführen, siehe std::move_if_noexcept) und kompakte (generische) Funktionen, die darauf ausgelegt sind, ohnehin nur in wenige Instruktionen zu zerfallen, wenn der Compiler mit ihnen durch ist.

    Man kann ja von Java halten was man mag, aber dieses Propagieren von Exception-Spezifikationen ist schon ne praktische Sache. Zumindest könnte der C++-Compiler ja vielleicht irgendwann mal ein implizites noexcept generieren, wenn in der Funktion keine Ausdrücke auftreten, die nicht noexcept sind - falls das nicht intern aus Optimierungsgründen ohnehin schon gemacht wird.



  • @Finnegan sagte in C++20 Tutorial/Howto: Concepts:

    Geppart mit Pedanterie kann das ganz schnell zum sprichwörtlichen "Rabbit Hole" werden. Man kann ja durchaus endlos lange lange noexcept(noexcept(expr))-Ausdrücke hinschreiben, die die gesamte Funktion nachzeichnen 😉 ... an den Pinkt kam ich mal, als ich dachte "das ist ja ne tolle Sache".

    Hrhr, ich weiß gaaanz genau was du meinst. Ich höre bloß nicht auf und ziehe es bis zum bitteren Ende durch. Nein, es macht mir einen perversen Spaß. 😂

    @Finnegan sagte in C++20 Tutorial/Howto: Concepts:

    Man kann ja von Java halten was man mag, aber dieses Propagieren von Exception-Spezifikationen ist schon ne praktische Sache.

    Also wenn ich über Java meckere, dann über das alte, was ich zu Studienzeiten lernen musste. Ein aktuelles Java ist durchaus okay und hat wie du schon sagst, durchaus brauchbare Features.

    Zumindest könnte der C++-Compiler ja vielleicht irgendwann mal ein implizites noexcept generieren, wenn in der Funktion keine Ausdrücke auftreten, die nicht noexcept sind - falls das nicht intern aus Optimierungsgründen ohnehin schon gemacht wird.

    Ja, wäre echt klasse. Ist aber nicht so einfach. Viele dieser Geschichten, auch in Verbindung mit trivial Typen, hängen von Heap und Stack Eigenschaften ab, und diese variieren von Platform zu Platform und OS zu OS. Und wie eklig das werden kann, sieht man an den verschiedenen Datenmodellen die von Betriebssystemen verwendet werden. Ein Linux auf AMD64 hat zum Beispiel ein LP64 Datenmodell, ein Windows auf AMD64 hat ein LLP64 Datenmodell und ein Unicos auf den guten alten Cray Mainframes war ein irres SILP64 Datenmodell.

    LLP64: long long, pointer, size_t = 64 Bit
    LP64: long, long long, pointer, size_t = 64 Bit
    SILP64: short, int, long, long long, pointer, size_t = 64 Bit

    Deswegen sind 64 Bit kompatible Codeportierungen zwischen Linux und Windows, wenn da Pointer zu longs gecastet werden, ein echter Krampf. Selbst unter Windows sind 32 -> 64 Bit Portierungen unerträglich, weil zu 32Bit Zeiten von "Idioten" longs für Pointerspielereien benutzt wurden, anstatt size_t/ssize_t oder besser ptrdiff_t. Ich komme damit immer wieder in Berührung weil ich zu den Entwicklern gehöre, die freiweillig an Neocron Classic arbeiten. Das sind 1,4 Millionen Zeilen gigantischer Blödsinn (stellweise pre C++98 Code).



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Was Scott Meyers Beitrag angeht: Ich halte viel von Scott Meyers, aber den speziellen Rat "use noexcept wherever you can" halte ich für furchtbar.

    Entspricht aber nicht der Realität. Halbwegs gut gepflegter Code macht eine klare Ansage über das Interface. Schau zum Beispiel mal in den Qt Code, da ist es überall ordentlich definiert.

    Was soll "entspricht nicht der Realität" heissen? Das ist (m)eine Meinung. Ich finde z.B. auch dass JavaScript eine furchtbare Programmiersprache ist. Das ist auch eine Meinung. Ist die jetzt auch "falsch" weil so viele Leute JavaScript nutzen? Nein.

    Davon abgesehen...

    Bei bestimmten Klassen in Libraries, speziell Container Klassen oder low-level Utilities wie Mutexen, Scoped-Lock etc., ist noexcept sinnvoll. Damit der Benutzer die Möglichkeit hat diese in den paar wenigen noexcept Funktionen die er selbst schreiben muss/möchte zu verwenden. Bzw. ganz speziell trifft es auch auf Copy- und Move-Konstruktoren und Zuweisungsoperatoren zu, denn hier kommt die Compiler-Magie zum Einsatz die bei implizit definierten solchen das noexcept automatisch passend wählt - abhängig vom noexcept der Basis-/Memberklassen.

    Davon abgesehen lässt man es lieber sein. Ich habe schon einiges an Code gesehen wo jmd. noexcept nutzt. Aber, abgesehen von trivialen Beispielen und Libraries deren Code von tausenden anderen Programmierern angesehen wird, noch nichts wo noexcept durchgängig korrekt gewesen wäre. Ich meine mich sogar zu erinnern dass in einer C++ Standard Library Spezifikation ein noexcept Fehler drin war.

    Und da die Fehlerrate so hoch ist, der Nutzen in normalen Anwendungscode so gering und die Folgen "drastisch" (terminate()) ist und bleibt meine Empfehlung ganz klar: lasst von noexcept die Finger, es sei denn ihr wisst ganz genau warum und wofür ihr es an einer Stelle braucht. Leuten zu empfehlen noexcept zu verwenden, wenn man quasi sicher weiss dass es viele oft falsch verwenden werden, macht mMn. einfach keinen Sinn.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Nein. Liest du auch mal was ich schreibe? Das Tuple interessiert mich nicht.

    Ja tue ich. Was ich an der Stelle meinte ist, dass durch das Unrolling zum Schluss nur noch die reinen Calls zu den std::cout operator<< übrig bleiben. Das noexcept ist an der Stelle bedeutungslos, kann man in dem Assembler Output sehen.

    Ist doch egal ob nach dem Unrolling nur noch std::cout operator<< übrig bleibt, der reicht ja schliesslich.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Deine Funktion gibt die Indexe des Tuple-Typs mittels std::cout aus.
    Auf std::cout könnten Exceptions aktiviert sein. Das da meine ich: https://www.cplusplus.com/reference/ios/ios/exceptions/
    D.h. der Stream-Insertion Operator könnte eine Exception werfen wenn z.B. std::cout in ein File umgelenkt ist und die Disk voll ist. Aber das ist wohl mein geringstes Problem hier ... uiuiui...

    Bitte beim Quoten aufpassen. Der Satz "Aber das ist wohl mein geringstes Problem hier ... uiuiui..." ist nicht von mir.

    Ich weiß, dass std::cout Exceptions werfen kann.

    OK. Wieso schreibst du dann

    Was ich an der Stelle meinte ist, dass durch das Unrolling zum Schluss nur noch die reinen Calls zu den std::cout operator<< übrig bleiben. Das noexcept ist an der Stelle bedeutungslos, kann man in dem Assembler Output sehen.

    ?
    Das widerspricht sich doch.



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Zumindest könnte der C++-Compiler ja vielleicht irgendwann mal ein implizites noexcept generieren, wenn in der Funktion keine Ausdrücke auftreten, die nicht noexcept sind - falls das nicht intern aus Optimierungsgründen ohnehin schon gemacht wird.

    Ja, wäre echt klasse. Ist aber nicht so einfach. Viele dieser Geschichten, auch in Verbindung mit trivial Typen, hängen von Heap und Stack Eigenschaften ab, und diese variieren von Platform zu Platform und OS zu OS. Und wie eklig das werden kann, sieht man an den verschiedenen Datenmodellen die von Betriebssystemen verwendet werden.
    (...)

    Für noexcept(auto) spielt das alles keine Rolle. Gibt in C++ schliesslich keine Exceptions für Dinge wie Stack-Overflow, falsches Alignment etc. Das ist alles UB und wird wenn überhaupt, dann über Mittel die sich ausserhalb des C++ Standards befinden behandelt.



  • @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Was soll "entspricht nicht der Realität" heissen? Das ist (m)eine Meinung. Ich finde z.B. auch dass JavaScript eine furchtbare Programmiersprache ist. Das ist auch eine Meinung. Ist die jetzt auch "falsch" weil so viele Leute JavaScript nutzen? Nein.

    Ah okay, dann habe ich es in den falschen Hals bekommen. Meine Empfehlung ist die, die auch Scott Meyers gibt, dranmachen wenn möglich. Da wo man es quasi bedenkenlos machen kann sind Getter, die nur einen trivialen member Typ zurückgeben.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Davon abgesehen lässt man es lieber sein. Ich habe schon einiges an Code gesehen wo jmd. noexcept nutzt. Aber, abgesehen von trivialen Beispielen und Libraries deren Code von tausenden anderen Programmierern angesehen wird, noch nichts wo noexcept durchgängig korrekt gewesen wäre.

    Naja, Beispiel Qt Code, wie schon erwähnt. Ich sehe es oft in gut gepflegten Codebasen in Firmen (also kein OpenSource). Durchgängig korrekt ist es selten, aber zu sagen wir mal gut 95%. Gilt natürlich nur für das, was ich bisher an Code gesehen habe.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Bitte beim Quoten aufpassen. Der Satz "Aber das ist wohl mein geringstes Problem hier ... uiuiui..." ist nicht von mir.

    Ohja, entschuldige, das war keine Absicht.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Was ich an der Stelle meinte ist, dass durch das Unrolling zum Schluss nur noch die reinen Calls zu den std::cout operator<< übrig bleiben. Das noexcept ist an der Stelle bedeutungslos, kann man in dem Assembler Output sehen.
    ?
    Das widerspricht sich doch.

    Diesmal hast du nicht genau gelesen. Es wird alles entrollt und zum Schluss bleiben nur die Calls zum operator übrig. Nicht zu verwechseln mit "zum Schluss bleiben nur die Calls zum operator in dem Functiontemplate übrig". Das Functionframe wird entfernt, damit auch noexcept am Funktionskopf. Du kannst es nicht nur im Assembleroutput sehen, du kannst es auch kompilieren, eine Exception provozieren und du wirst sehen, terminate() wird erst nach dem Stackunrolling/Exceptionhandler aufgerufen, wie als hätte da nie ein noexcept gestanden. Aber wieso diskutieren wir eigentlich immer noch darüber wobei ich da doch schon vor einer gefühlten Ewigkeit gesagt hatte, dass es falsch war und da ohne Zweifel ein noexcept(false) dran gehört? Dicht gefolgt von meiner Erklärung zu meiner Vorgehensweise "sind Streams beteiligt, können Exceptions fliegen".

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Für noexcept(auto) spielt das alles keine Rolle. Gibt in C++ schliesslich keine Exceptions für Dinge wie Stack-Overflow, falsches Alignment etc. Das ist alles UB und wird wenn überhaupt, dann über Mittel die sich ausserhalb des C++ Standards befinden behandelt.

    Naja, also es gibt schon Stack-Overflow Exceptions. Es sind nur keine Standard C++ Exceptions und damit ein Implementierungsdetails der Runtime, siehe SEH vs sjlj. Da kann man sich jetzt aber auch prima drüber streiten ob das als eine echte Stack-Overflow Exception durchgeht oder nicht. Aber ja, ist außerhalb des C++ Standards, da hast du völlig Recht. ☺ Ich wollte damit nur sagen, dass solche cleveren Mechanismen nur selten vollständig umgesetzt werden können. Sie hängen oft von Dingen ab, die der C++ Standard nicht hergibt bzw außerhalb des Wirkungsbereichs des Compilers/Runtime liegen, wie zum Beispiel der berühmte Linux OOM-Killer Kernel Task. Gibts sowas eigentlich auch unter Windows?



  • @VLSI_Akiko sagte in C++20 Tutorial/Howto: Concepts:

    Diesmal hast du nicht genau gelesen.

    Würde ich nicht so sagen, nein.

    Es wird alles entrollt und zum Schluss bleiben nur die Calls zum operator übrig.

    Korrekt. Ändert aber nix.

    Nicht zu verwechseln mit "zum Schluss bleiben nur die Calls zum operator in dem Functiontemplate übrig". Das Functionframe wird entfernt, damit auch noexcept am Funktionskopf.

    Falsch. Das noexcept bleibt natürlich. Wäre auch schlimm wenn es nicht bliebe - würde ja die "as if" Regel verletzen. Frame ist dafür keines nötig, das wird gern über diverse Tables implementiert. Ich gehe davon aus dass du das weisst - weswegen ich mich jetzt ein wenig wundere warum du das Inlining/Entfernen des Frames als Argument anführst.

    #include <iostream>
    #include <sstream>
    
    inline void test2(std::istream& s) noexcept {
        int i;
        s >> i;
        s >> i;
        s >> i;
    }
    
    __attribute__((noinline)) void test(std::istream& s) {
        test2(s);
    }
    
    int main() {
        try {
            std::stringstream s;
            s.exceptions(std::istream::failbit | std::istream::badbit);
            test(s);
        } catch (...) {
            std::cout << "catch" << std::endl;
        }
    }
    

    https://godbolt.org/z/57GqdY8Eo

    test2 wird hier auch vollständig in test inlined. Der Effekt des noexcept bleibt natürlich trotzdem erhalten.

    Output

    terminate called after throwing an instance of 'std::__ios_failure'
      what():  basic_ios::clear: iostream error
    

    Aber wieso diskutieren wir eigentlich immer noch darüber

    Weil du immer wieder versuchst mir was zu erklären, dabei aber falsche und/oder irreführende Dinge schreibst. So wie eben gerade.

    Naja, also es gibt schon Stack-Overflow Exceptions. Es sind nur keine Standard C++ Exceptions und damit ein Implementierungsdetails der Runtime, siehe SEH vs sjlj. Da kann man sich jetzt aber auch prima drüber streiten ob das als eine echte Stack-Overflow Exception durchgeht oder nicht.

    Wäre ein sinnloser Streit. Fakt ist dass es in C++ das Konzept einer Stack-Overflow Exception nicht gibt - es gibt in C++ ja nichtmal das Konzept eines Stack-Overflow. So war das gemeint.

    Ich wollte damit nur sagen, dass solche cleveren Mechanismen nur selten vollständig umgesetzt werden können. Sie hängen oft von Dingen ab, die der C++ Standard nicht hergibt bzw außerhalb des Wirkungsbereichs des Compilers/Runtime liegen

    Der C++ Standard hat ja noexcept(auto) - es ist bloss dummerweise auf implizit definierte spezielle Memberfunktionen eingeschränkt. Ich sehe aber keinen Grund warum es nicht auch für alle Funktionen gehen sollte.

    wie zum Beispiel der berühmte Linux OOM-Killer Kernel Task. Gibts sowas eigentlich auch unter Windows?

    Es ist denkbar dass es ein oder mehrere Third-Party Tools gibt. Ich kenne aber keine, und mit Windows kommt ganz sicher kein solches Tool mit. Windows kommt da besser damit durch, weil Windows Speicher nicht over-committed. Blöd wird's wenn man Adressbereiche hat die automatisch "on first use" committed werden, aber davor nur reserviert sind - wie z.B. Thread-Stacks. Das kann dann in einer STATUS_STACK_OVERFLOW SEH-Exception enden, obwohl der Stack-Pointer noch lange nicht am Ende war. Sehe ich immer wieder in Crash-Dumps. COW kann genau so schief gehen, Schreiben in so einen Bereich kann dann auch in einer SEH enden.

    Das Beispiel ist aber mMn. wieder kein gutes: der OOM-Killer ändert ja nichts am Verhalten des Programms. Er schickt dem Programm bloss ein Signal. Das kann man auch selbst senden. Das Programm wird in beiden Fällen gleich reagieren.



  • @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    test2 wird hier auch vollständig in test inlined. Der Effekt des noexcept bleibt natürlich trotzdem erhalten.

    Ich war jetzt drauf und drann herumzumotzen, dass normale Funktionen und Templates nicht das Gleiche sind. Also habe ich mal deinen Test mit meinem Tuple Concept aufgebaut und versucht so wenig wie möglich zu ändern. Und weißt du was? Du hast Recht. Und weißt du was mein Fehler war? Mir ist nicht mal im Ansatz in den Sinn gekommen die Exceptions vom Stream einzuschalten und das war echt bescheuert (weißt schon Anfängerlevel). Und ich erzähle noch groß rum, dass man es im Assembleroutput nicht sehen kann... 🤣 Also Entschuldigung wenn ich an der Stelle wirklich engstirnig war. Wer gibt schon gerne so peinliche Fehler zu? Vor allem nach 30+ Jahren Erfahrung mit der Sprache. 😳

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Aber wieso diskutieren wir eigentlich immer noch darüber

    Weil du immer wieder versuchst mir was zu erklären, dabei aber falsche und/oder irreführende Dinge schreibst. So wie eben gerade.

    Danke dass du drangeblieben bist. Es gibt nur wenige die überhaupt verstehen was da los ist und auch noch bereit sind es zu demonstrieren. Mich kann man immer mit guten Beispielen überzeugen.

    Weißt du was wirklich schlimm an der ganzen Thematik ist? Ich habe im Job mit etwa 20 C/C++ Entwicklern zu tun, und kein einziger davon weiß/versteht über was wir hier reden. Ich "darf" deren Auswüchse in einer 4,5 Millionen Zeilen Codebasis analysieren, fixen und modernisieren. Es ist also mal ganz nett wenn man mal mit jemanden zu tun hat, der die Details kennt.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Falsch. Das noexcept bleibt natürlich. Wäre auch schlimm wenn es nicht bliebe - würde ja die "as if" Regel verletzen.

    Ja, die Regel ist mir bekannt. Deswegen war ich selbst auch etwas überrascht, als ich meine ersten Tests gemacht habe. Ich habe die Behauptung ja nicht aufgestellt ohne es vorher auszuprobieren. Aber wenn man die Exceptions nicht einschaltet ... das wird mir noch Tage schlaflose Nächte bereiten.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Wäre ein sinnloser Streit. Fakt ist dass es in C++ das Konzept einer Stack-Overflow Exception nicht gibt - es gibt in C++ ja nichtmal das Konzept eines Stack-Overflow. So war das gemeint.

    Ja okay, jetzt ist es klar.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Der C++ Standard hat ja noexcept(auto) - es ist bloss dummerweise auf implizit definierte spezielle Memberfunktionen eingeschränkt. Ich sehe aber keinen Grund warum es nicht auch für alle Funktionen gehen sollte.

    Ja, einige der Entscheiden von dem C++ Komitee verstehe ich auch nicht. Wie sie zum Beispiel C++17 uniform initialization so vermasseln konnten. Würden die Compilerentwickler es so implementieren wie vom Komitee vorgeschlagen, könnte man eine Klasse mit privaten Default Constructor trotzdem ohne weiteres mit einer leeren uniform initialization instanzieren. Ich habe jetzt allerdings beim Compiler Explorer keine Compiler Version finden können, wo es nach Vorgabe implementiert (und damit kaputt) ist. Aber es war schon ein fast witziger Bug.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Es ist denkbar dass es ein oder mehrere Third-Party Tools gibt. Ich kenne aber keine, und mit Windows kommt ganz sicher kein solches Tool mit. Windows kommt da besser damit durch, weil Windows Speicher nicht over-committed. Blöd wird's wenn man Adressbereiche hat die automatisch "on first use" committed werden, aber davor nur reserviert sind - wie z.B. Thread-Stacks. Das kann dann in einer STATUS_STACK_OVERFLOW SEH-Exception enden, obwohl der Stack-Pointer noch lange nicht am Ende war. Sehe ich immer wieder in Crash-Dumps. COW kann genau so schief gehen, Schreiben in so einen Bereich kann dann auch in einer SEH enden.

    Hmm, interessant. Ich kenne bei Windows nur ein paar wenige spezifische Details, die mich mal irgendwann interessiert haben. Darüber hinaus benutze ich Windows seit 15+ Jahren nicht mehr. Naja abgesehen bis auf diese Spieleentwicklungsgeschichte, die ich weiter oben erwähnt hatte. Aber im Grund ist es auch nur Portierungsarbeit um es zuerst gegen die Wine-lib kompiliert zu bekommen um dann später einen kompletten Linux Port zu machen. Wobei ich denke, dass das nie fertig wird. Man kann bei dem ganzen nicht mal Spaghetticode sprechen, es ist eher Hackfleischcode.

    @hustbaer sagte in C++20 Tutorial/Howto: Concepts:

    Das Beispiel ist aber mMn. wieder kein gutes: der OOM-Killer ändert ja nichts am Verhalten des Programms. Er schickt dem Programm bloss ein Signal. Das kann man auch selbst senden. Das Programm wird in beiden Fällen gleich reagieren.

    Nicht ganz so. Es hängt von den Prozess Capabilities ab. Soweit ich mich erinnern kann, macht der OOM-Killer erst ein SIGTERM wenn CAP_SYS_IORAW (bin mir nicht 100% sicher) gesetzt ist. Damit soll dem Prozess ein sauberer Shutdown ermöglicht werden. Und erst dann ist es ein SIGKILL. Aber ja, im Endeffekt wird nur ein Signal geschickt.