Hallo zusammen, ich versuche gerade, C++ zu lernen, und bin bei den constrexp-Funktionen. Ich habe dafür das Beispiel aus dem Buch abgetippt. (Man kann die Quelltexte auch herunterladen, aber ich tippe lieber selbst, um mich an die Schreibweise zu gewöhnen). -> https://godbolt.org/z/TdjKso Und jetzt habe ich Probleme, den Unterschied zwischen einer constexpr-Funktion und einer normalen inline-Funktion zu verstehen. In allen Fällen wird das Ergebnis vom Compiler so zusammenoptimiert, dass am Ende nur noch das Ergebnis dasteht. In welchen Fällen gibt es einen nennenwerten Unterschied zwischen constexpr und inline?
:
Walter T. schrieb: > In welchen Fällen gibt es einen nennenwerten Unterschied zwischen > constexpr und inline? Ändere die Funktion mal so, dass sie nicht zur Compile-Zeit auswertbar ist, und schau was dir der Compiler jeweils dazu erzählt.
Das eine hat mit dem anderen nichts zu tun. Compiler optimieren zur Compilezeit vieles, was sich da optimieren lässt. In deinem Programm siehst du den Effekt von "loop unrolling". Das hat weder was min inline noch was mit constexpr zu tun. Da du in deinem Programm letztendlich nur echte Konstanten als Parameter einsetzt, kann der Compiler alles vorausberechnen, und das tut der dann auch. Oliver
Nachtrag: Noch lustiger wirds, gerade mit clang und solchen Minimalprogrammen, wenn du die Ausgabe nach std::cout weglässt. https://godbolt.org/z/hGhqo3 Diese Optimierung macht der C-Compiler genauso, das hat nichts mit C++ zu tun. https://godbolt.org/z/qjvbjx Oliver
:
Bearbeitet durch User
Constexpr wird zwingend zur Compiletime gerechnet. Mal so als Beispiel was man mit constexpr erreichen kann: https://www.youtube.com/watch?v=PJwd4JLYJJY (CppCon 2017: Ben Deane & Jason Turner “constexpr ALL the Things!”)
Also ist der Unterschied: Bei einer constexpr-Funktion wird eine Fehlermeldung ausgeworfen, wenn es sich nicht zur Compilezeit auswerten lässt, aber von der Performance ist inline gleichwertig, weil wenn möglich das sowieso gemacht wird?
Walter T. schrieb: > Also ist der Unterschied: Bei einer constexpr-Funktion wird eine > Fehlermeldung ausgeworfen, wenn es sich nicht zur Compilezeit auswerten > lässt, aber von der Performance ist inline gleichwertig, weil wenn > möglich das sowieso gemacht wird? Inline ist eine ganz andere Baustelle, das hat damit überhaupt nichts zu tun. Optimierungen führt der Compiler zur Compilezeit durch, das liegt nun mal in der Natur der Sache. Unabhängig von constexpr rechnet der konstante Daten aus, wenn er kann. Das fängt mit so etwas einfachen wie int x = 24/2; an, und hört bei deinem Beispiel noch lange nicht auf, egal, ob inline oder nicht. Dank Linktime-Optimization macht der das auch über sourcefile-Grenzen hinweg. Oliver
:
Bearbeitet durch User
constexpr liefert eine echte "constant expression". Auch wenn der Compiler auch nicht explizit mit constexpr gekennzeichnete Werte zur Compilezeit berechnet, ist das keine "constant expression". https://godbolt.org/z/hP9s38 Oliver
Walter T. schrieb: > Und jetzt habe ich Probleme, den Unterschied zwischen einer > constexpr-Funktion und einer normalen inline-Funktion zu verstehen. Eine constexpr-Funktion wird zur Compilezeit ausgewertet und das Ergebnis der Auswertung wird (anstelle des Funktionsaufrufes) in dein Programm geschrieben. Die Funktion existiert danach nicht mehr. Eine inline-Funktion wird immer (anstelle deines Funktionsaufes) in dein Programm geschrieben. Sie kann zusätzlich auch als normale Funktion existieren, wenn das nötig ist. (Anmerkung: Eine inline-Funktion muss nicht zwingend ihren Funktionsaufruf ersetzen. Das entscheidet der Compiler selbst; will man ihn zwingen, braucht man zusätzliche Parameter, wie z.B. attribute((always_inline)).) > In welchen Fällen gibt es einen nennenwerten Unterschied zwischen > constexpr und inline? Wenn das Ergebnis nicht zur Compilezeit ausgewertet werden kann, sondern z.B. davon abhängt, was der Benutzer irgendwie eingibt. Oder was ein Webserver ausgibt. Oder oder oder.
:
Bearbeitet durch User
Ich verstehe es immer noch nicht. Auch im Beispiel oben. Was der Compiler in main() aus der Funktion macht, scheint keinerlei Abhängigkeit davon zu haben, ob die Funktion constexpr definiert ist, sondern nur von den Eingabeparametern und Ausgabevariablen abzuhängen.
Walter T. schrieb: > Ich verstehe es immer noch nicht. Auch im Beispiel oben. Was der > Compiler in main() aus der Funktion macht, scheint keinerlei > Abhängigkeit davon zu haben, ob die Funktion constexpr definiert ist, > sondern nur von den Eingabeparametern und Ausgabevariablen abzuhängen. Hat es zur Laufzeit auch nicht. Hättest du allerdings ausschließlich constexpr Funktionen und Ausdrücke, die zur Compilezeit auflösbar sind, dann würde der Compiler die constexpr Funktion nach dem Compilieren wegwerfen. Das ganze ist so nützlich, dass es mit C++20 sogar das neue Keyword "consteval" gibt, mit dem man dann forcieren kann, dass eine Funktion gar nicht mehr zur Laufzeit ausführbar ist. Vielleicht hilft es etwas wenn du constexpr/consteval Funktionen ein wenig wie einen Codegenerator betrachtest.
Ich versuche das nochmal zu paraphrasieren, um zu testen, ob ich das richtig verstanden habe: Bei constexpr-Funktionen will der Programmierer sicherstellen, dass der Funktionsrumpf wegoptimiert wird. Der Programmierer kann sich dessen sicher sein, denn sollte das aufgrund der Aufruf-Parameter nicht klappen, wirft der Compiler eine Fehlermeldung. Wenn ich eine constexpr-Variable initialisieren will, muss ich das über ein Literal oder eine constexpr-Funktion tun. Bei normalen inline-Funktionen optimiert der Compiler den Funktionsrumpf auch weg, wenn es das kann, gibt aber keine Garantie, dass er das tut.
1 | #include <iostream> |
2 | |
3 | int foo0(const int a) |
4 | {
|
5 | return a; |
6 | }
|
7 | |
8 | constexpr int foo1(const int a) |
9 | {
|
10 | return a; |
11 | }
|
12 | |
13 | int main() |
14 | {
|
15 | const int a0 {foo0(3)}; // a) |
16 | //constexpr int a1 {foo0(3)}; // b)
|
17 | constexpr int a2 {foo1(3)}; // c) |
18 | int a3 {foo0(3)}; // d) |
19 | volatile int a4 {foo1(3)}; // e) |
20 | volatile int a5 = foo1(3); // f) |
21 | |
22 | std::cout << a0 << a2 << a3 << a4 << a5; |
23 | }
|
a): Der Aufruf wird wegoptimiert, weil es gerade zufällig geht. b): Funktioniert nicht, weil sich constexpr-Variablen nicht über normale Funktionen zuweisen lassen? c): So funktioniert es dann d): Die rechte Seite wird wegoptimiert, weil es gerade zufällig geht. e): Die Initialisierung erfolgt mit einer Konstanten (ähnlich einem Literal). f): Die Ermittlung der rechten Seite resultiert in einer Konstanten (ähnlich einem Literal), die Zuweisung erfolgt normal, weil nach der Auswertung der rechten Seite ein sequence point liegt?
:
Bearbeitet durch User
Walter T. schrieb: > Bei constexpr-Funktionen will der Programmierer sicherstellen, dass der > Funktionsrumpf wegoptimiert wird. Nein. constexpr sagt, dass diese Funktion in einem constexpr-Kontext verwendet werden kann(!). Ist es kein constexpr-Kontext, so wird diese Funktion als Laufzeitfunktion betrachtet und normal behandelt, also mit allen Optimierungen. consteval besagt hingegen, dass eine Funktion zur Compilezeit auswertbar sein muss. Einen constexpr-Kontext kannst Du erzwingen, indem Du versuchst, eine constexpr-Variable zu initialisieren.
Walter T. schrieb: > Ich versuche das nochmal zu paraphrasieren, um zu testen, ob ich das > richtig verstanden habe: > > Bei constexpr-Funktionen will der Programmierer sicherstellen, dass der > Funktionsrumpf wegoptimiert wird. Der Programmierer kann sich dessen > sicher sein, denn sollte das aufgrund der Aufruf-Parameter nicht > klappen, wirft der Compiler eine Fehlermeldung. Eher: Mit constexpr-Funktionen will der Programmierer sicherstellen, dass sein Sinus-LUT nicht zur Laufzeit berechnet wird. Der Programmierer kann sich dessen sicher sein, weil er sein constexpr Array mit einer constexpr Funktion erzeugt.
Sinus-LUT ist ein gutes Stichwort. Müssen alle Funktionen in der Aufrufkette constexpr sein, oder nur die äußerste?
:
Bearbeitet durch User
Walter T. schrieb: > Sinus-LUT ist ein gutes Stichwort. Müssen alle Funktionen in der > Aufrufkette constexpr sein, oder nur die äußerste? Alle. Der Compiler wird dir das auch sagen sofern dem nicht so ist.
Gibt es Tricks, wenn ich Funktionen benötige, die sowohl für die Berechnung von LUTs als auch zur Laufzeit benötigt werden? Muss ich dann alles doppelt implementieren? (z.B. der Klassiker: LUT für erste Näherung, Newton-Verfahren für die letzten signifikanten Stellen.)
Walter T. schrieb: > Gibt es Tricks, wenn ich Funktionen benötige, die sowohl für die > Berechnung von LUTs als auch zur Laufzeit benötigt werden? Muss ich dann alles doppelt implementieren? Wilhelm M. schrieb: > Nein. constexpr sagt, dass diese Funktion in einem constexpr-Kontext > verwendet werden kann(!). Ist es kein constexpr-Kontext, so wird diese > Funktion als Laufzeitfunktion betrachtet und normal behandelt, also mit > allen Optimierungen. Oliver
Walter T. schrieb: > Sinus-LUT ist ein gutes Stichwort. Übrigens liefern Lmabda-Expressions auch constexpr-Closures. Daher eignen sie sich als sog. IIFE (immediate invoked function expressions) gut zur komplexen Initialisierung.
1 | constexpr auto lut = []{ |
2 | // Berechnung von data
|
3 | return data; |
4 | }();
|
Oliver S. schrieb: > Walter T. schrieb: >> Gibt es Tricks, wenn ich Funktionen benötige, die sowohl für die Oliver S. schrieb: > Wilhelm M. schrieb: >> Nein. constexpr sagt, dass diese Funktion in einem constexpr-Kontext >> verwendet werden kann(!). Hoppla. Den Beitrag habe ich wohl übersehen. Ich sollte mal wieder ein paar dutzend Tabs schließen.
Walter T. schrieb: > Gibt es Tricks, wenn ich Funktionen benötige, die sowohl für die > Berechnung von LUTs als auch zur Laufzeit benötigt werden? Muss ich dann > alles doppelt implementieren? (z.B. der Klassiker: LUT für erste > Näherung, Newton-Verfahren für die letzten signifikanten Stellen.) https://en.cppreference.com/w/cpp/types/is_constant_evaluated
Unterm Strich heißt das: Wenn ich eine Inline-Funktion schreibe, sollte ich prüfen, ob ich sie nicht sogar constrexp mache, weil das unterm Strich nie schaden kann?
Mir fällt spontan ehrlich gesagt kein Grund ein der dagegen spricht. Bei Funktionen impliziert constexpr übrigens inline. (Im Gegensatz zu Variablen, da muss man aufpassen...)
Walter T. schrieb: > Unterm Strich heißt das: Wenn ich eine Inline-Funktion schreibe, sollte > ich prüfen, ob ich sie nicht sogar constrexp mache, weil das unterm > Strich nie schaden kann? Es gibt den Ansatz: "constexpr all functions", auch in der stdlib Es schadet nichts, überall constexpr einzusetzen. Sollte es bei einer bestimmten Verwendung(!) nicht möglich sein, sagt der Compiler Dir das.
Danke für die Diskussion und die Tipps! Das verspricht auch wieder, ein sehr nützlicher Baustein zu werden.
Beitrag #6490781 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.