Forum: PC-Programmierung C++ constexpr-Funktionen


von Walter T. (nicolas)


Lesenswert?

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?

:
von Εrnst B. (ernst)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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
von Mikro 7. (mikro77)


Lesenswert?

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!”)

von Walter T. (nicolas)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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
von Oliver S. (oliverso)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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
von Walter T. (nicolas)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

Sinus-LUT ist ein gutes Stichwort. Müssen alle Funktionen in der 
Aufrufkette constexpr sein, oder nur die äußerste?

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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
}();

von Walter T. (nicolas)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

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

von Walter T. (nicolas)


Lesenswert?

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?

von Vincent H. (vinci)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
Noch kein Account? Hier anmelden.