Seite 1 von 1

Compile-time function execution, constexpr

Verfasst: 22.04.2021, 20:36
von smurfer
Hallo zusammen,

ich habe mal eine Frage zu constexpr und dessen Nutzung zum/zur Compile-time programming/function execution: Mir fällt es momentan schwer, wirklich praktische Beispiele zu finden, wo der Einsatz über Showcases oder den Missbrauch des Compilers als Taschenrechner hinausgeht. Was ich damit meine:

Beispiel 1: Selbst wenn ich noch so komplexe mathematische Ausdrücke zur Compile-Zeit berechnen kann, müssen irgendwo ganz am Anfang konstante Werte rein. Das könnte ich auch außerhalb der Programmiersprache ausrechnen und einfach das Resultat in den Code schreiben. Dann habe ich einmal Aufwand außerhalb der Programmierwelt, die gleiche Laufzeit und jedesmal verringerte Compile-Zeit.
Selbst wenn es wegen Bedingungen und ähnlichem händisch zu aufwendig wird, würde ich das Programm, das mir die Berechnungen durchführt (der "Taschenrechner"), auslagern. Dann kann ich aber auch gleich wieder zur Runtime rechnen, da es sich nicht auf das Hauptprojekt auswirkt und die Compile-Zeit-Berechnung langsamer ist, siehe nächstes Beispiel.

Beispiel 2: Es gibt ja mittlerweile ganze Raytracer auf GitHub, die eine statische Szene zu Compile-Zeit rendern. Jetzt könnte ich mir vorstellen, die verschiedenen Szenen, die ich generieren möchte (oder andere Resourcen, wenn man vom Raytracer weggeht), alle direkt in Codeform zu speichern und vor dem Kompilieren einfach den richtigen Header einbinden. Das wäre sinnvoll, wenn nicht der Compile-Time Raytracer wesentlich langsamer wäre, als der Runtime-Raytracer. In einem C++17-Projekt bei GitHub wird hier der Faktor 6000 genannt. D.h. der einzige Vorteil an der Stelle ist, dass ich die exakt gleiche Szene mehrfach rendern kann und ab dem sechstausendersten Mal dann womöglich schneller bin, als die Runtime-Variante.

Beispiel 3: Ich habe in einem vergangenen Projekt über rekursive Templates Schleifen abgebildet, um alle möglichen bekannten Funktionssignaturen, die ich für ein sehr generellen std::function-Wrapper haben wollte, abzubilden. Das war hilfreich, hatte aber nicht den Sinn, die Laufzeit zu verbessern, sondern war lediglich eine Krücke, um mit std::function und sauberen Template-Parametern zu arbeiten. Das alles hätte ich mir mit void-Pointern ersparen können.

Ich hoffe, es ist klar geworden, worauf ich hinaus will.

Beste Grüße

Edit: Kurzer Nachtrag: Ein paar Dinge, die mir einfallen, sind Komfortfunktionen wie constexpr-Strings. Aber danach wird es dünn, mit meiner Vorstellungskraft.

Re: Compile-time function execution, constexpr

Verfasst: 22.04.2021, 21:04
von Krishty
ich habe mal eine Frage zu constexpr
[kein einziges Fragezeichen im ganzen Post]
Ich hoffe, es ist klar geworden, worauf ich hinaus will.
SCNR

Ja; viele C++-Compiler konnten mathematische und logische Ausdrücke schon vor constexpr auflösen, weil irgendwo ganz am Anfang konstante Werte reinkommen. Das stimmt.

Allerdings konntest du die Ergebnisse damals nicht zur Template-Metaprogrammierung verwenden.

Stell dir vor, du programmierst einen zeitkritischen Algorithmus. Sortierung oder so. Der kann theoretisch beliebig große Datenmengen sortieren, aber praktisch hast du immer nur eine bestimmte Menge (weil deine Levels eine Höchstzahl Lichtquellen haben oder so ein Grütz; ist halt hypothetisch).

Und dabei brauchst du ein Bisschen Platz für Zwischenergebnisse. Du packst alles in einen std::vector – der ja völlig dynamisch ist – und das funktioniert. Aber das Ding ist lahm wie nix, weil der std::vector dauernd reallokiert und rumkopiert.

Du setzt dich also hin, machst die Mathematik, und das Ergebnis ist ganz eindeutig: Dein cleverer Algorithmus braucht genau sqrt(n) Zwischenergebnisse! Die Eingabegröße, n, hast du direkt als Template-Parameter. Jetzt musst du bloß ein Array definieren:

    Temp temp[sqrt(n)];

Und nun stehst du im Regen: sqrt(n) ist keine Constant Expression. Du kannst kein Array reservieren, dessen Größe dem Compiler vorher nicht bekannt war. Kompiliert einfach nicht.

Und nun? Du weißt, dass n in deinem Level immer == 256 ist. Schreibst du nun temp [16] von Hand hin? Und wenn sich die 256 mal ändert, bricht alles zusammen? Oder wenn du den selben Code in einem anderen Projekt verwenden willst, das kleiner ist, und sqrt(128) braucht?

Du könntest dir dein eigenes sqrt() implementieren. Als rekursives Template (denn das ist das einzige, was Compiler vor constexpr verstanden). Ich würde mir da wohl eher die Kugel geben.

Aber jetzt haben wir constexpr, und du kannst sqrt() als constexpr-Funktion deklarieren und dann tatsächlich temp[sqrt(n)] schreiben.

… NOT, HAHA! Die Mathe-Funktionen sind noch nicht constexpr. Darüber muss ich irgendwann nochmal den Jammer-Thread vollkotzen. Aber ich hoffe, die Kernaussage kam rüber!

————

Bei einem meiner Projekte habe ich constexpr für Unit Tests verwendet: Alle Tests sind constexpr und müssen deshalb beim Kompilieren ausgewertet werden. Schlägt ein Test fehl, gibt das einen Compile-Fehler – du kannst das Projekt ohne funktionierende Tests gar nicht erst kompilieren! Der Compiler entscheidet anhand der Dateiänderungen, welche Unit Tests neu kompiliert werden müssen – du musst also nicht die Tests neu kompilieren, an denen sich nichts geändert hat. Und noch besser: constexpr verbietet Undefined Behavior, wie Überlauf von int. Darauf muss der Compiler testen. Wenn dein Test Undefined Behavior enthält, kompiliert er erst gar nicht(!). Das ist irre nützlich, war aber sicher nicht das Primärziel.

Re: Compile-time function execution, constexpr

Verfasst: 22.04.2021, 21:12
von Krishty
smurfer hat geschrieben: 22.04.2021, 20:36Ein paar Dinge, die mir einfallen, sind Komfortfunktionen wie constexpr-Strings. Aber danach wird es dünn, mit meiner Vorstellungskraft.
Das ist alles Komfort. C++-Templates waren auch vor constexpr schon Turing-vollständig, also konntest du da das gleiche machen wie mit constexpr, nur halt umständlicher.

Der C-Präprozessor ist annähernd Turing-Vollständig (du musst selber für die Ausführungstiefe sorgen), also konntest du vor Templates das gleiche machen wie mit Templates, nur halt umständlicher.

Kommt halt darauf an, wo du die Linie für Komfort ziehst.

Re: Compile-time function execution, constexpr

Verfasst: 22.04.2021, 21:26
von smurfer
Danke für die schnelle Antwort, zu Deinem SCNR: Hehe, ja, Du hast natürlich recht??? ;-)

Die Beispiele sind sehr gut, gerade das Unit-Test-Beispiel. Solche Anwendungen wie bei deinem Sortierbeispiel hatte ich unter zunächst für mich mit unter "Taschenrechner" verbucht (auch wenn es bei der Metaprogrammierung nicht so ganz passt), aber zumindest in dem Sinne, dass die Werte wie beschrieben auch von Hand eingegeben werden könnten. Mit Deinem Beispiel kommt allerdings ein Aspekt mit rein (nein, zwei, jetzt muss ich aufpassen ;-)), den ich so erst nicht bedacht hatte, die Wiederverwendbarkeit und eine gewisse Absicherung gegen falsche, händisch eingegebene Werte.

Danke nochmal.

Re: Compile-time function execution, constexpr

Verfasst: 22.04.2021, 21:40
von NeuroCoder
Ich verwende constexpr und User Defined Literals um Strings zur Compile Time zu hashen.
So spare ich mir ein Preprocessing des Codes mit einem separaten Tool und habe immer korrekte / konsistente Hashes.

Re: Compile-time function execution, constexpr

Verfasst: 23.04.2021, 02:31
von dot
Krishty hat geschrieben: 22.04.2021, 21:04… NOT, HAHA! Die Mathe-Funktionen sind noch nicht constexpr. Darüber muss ich irgendwann nochmal den Jammer-Thread vollkotzen. Aber ich hoffe, die Kernaussage kam rüber!
one word: errno

Es wird echt Zeit dass C++ eine ordentliche Mathe-Lib bekommt…