constexpr Funktionen
Nach der Template-Metaprogrammierung und der Type-Traits-Bibliothek geht es heute vor allem um constexpr-Funktionen.
Nach der Template-Metaprogrammierung und der Type-Traits-Bibliothek geht es heute vor allem um constexpr-Funktionen.

Ich habe in den letzten Jahren bereits ein paar Beiträge über constexpr
geschrieben. Hier ist meine Motivation: Erstens werde ich interessante Ähnlichkeiten zwischen constexpr
Funktionen und Templaten aufzeigen. Zweitens möchte ich über die Verbesserungen von constexpr
in C++20 schreiben. Und schließlich gehe ich auch auf consteval
in C++20 ein. Wenn eine Theorie in meinen Beiträgen nicht ausführlich genug ist, werde ich auf frühere Beiträge verweisen. Beginnen wir mit einer kurzen Zusammenfassung, bevor ich auf die neuen Themen eingehe.
Ein kurzer Rückblick
constexpr
ermöglicht es, zur Compilezeit mit der typischen C++-Syntax zu programmieren. Konstante Ausdrücke mit constexpr
können drei Formen haben.
Variablen
- sind implizit const.
- müssen durch einen konstanten Ausdruck initialisiert werden.
constexpr double pi = 3,14;
Funktionen
constexpr
Funktionen in C++14 sind recht komfortabel. Sie können
- andere
constexpr
Funktionen aufrufen. - können Variablen haben, die durch einen konstanten Ausdruck initialisiert werden müssen.
- können bedingte Ausdrücke oder Schleifen enthalten.
- sind implizit inline.
- können keine static oder
thread_local
Daten besitzen.
Benutzerdefinierte Typen
- müssen einen Konstruktor besitzen, der ein konstanter Ausdruck ist.
- können keine virtuellen Funktionen besitzen.
- können keine virtuelle Basisklasse besitzen.
Die Regeln für constexpr
Funktionen oder Memberfunktionen sind simpel. Der Einfachheit wegen, nenne ich beide Funktionen.
constexpr
Funktionen müssen alle ihr Abhängigkeit zur Compliezeit auflösen können. Eine constexpr
-Funktion zu sein, bedeutet nicht, dass die Funktion zur Compilezeit ausgeführt wird. Es bedeutet, dass die Funktion das Potenzial hat, zur Compilezeit ausgeführt zu werden. Eine constexpr
-Funktion kann auch zur Runtime ausgeführt werden. Es ist oft eine Frage des Compilers und der Optimierungsstufe, ob eine constexpr
Funktion zur Compilezeit oder zur Runtime ausgeführt wird. Es gibt zwei Kontexte, in denen eine constexpr
-Funktion func
zur Compilezeit ausgeführt werden muss.
- Die
constexpr
Funktion wird in einem Kontext ausgeführt, der zur Compilezeit ausgewertet wird. Das kann einstatic_assert
-Ausdruck wie bei der type-traits-Bibliothek oder die Initialisierung eines C-Arrays sein. - Der Wert einer
constexpr
-Funktion wird mit constexpr angefordert:constexpr auto res = func(5);
Hier ist ein kleines Beispiel zur Theorie. Das Programm constexpr14.cpp
berechnet den größten gemeinsamen Teiler zweier Zahlen.
// constexpr14.cpp
#include <iostream>
constexpr auto gcd(int a, int b){
while (b != 0){
auto t= b;
b= a % b;
a= t;
}
return a;
}
int main(){
std::cout << '\n';
constexpr int i= gcd(11, 121); // (1)
int a= 11;
int b= 121;
int j= gcd(a, b); // (2)
std::cout << "gcd(11,121): " << i << '\n';
std::cout << "gcd(a,b): " << j << '\n';
std::cout << '\n';
}
(1) berechnet das Ergebnis i
zur Compilezeit und (2) j
zur Runtime. Der Compiler würde sich beschweren, wenn ich j
als constexpr
deklariere: constexpr int j = gcd(a, b)
. Das Problem ist in diesem Fall, dass die Integer a
und b
keine konstanten Ausdrücke sind.
Die Ausgabe des Programms sollte nicht überraschen.

Die Überraschung kann jetzt beginnen. Die Magie zeige ich mit dem Compiler Explorer.

(1) im Programm constexpr14.cpp
läuft auf die Konstante 11 im folgenden Ausdruck hinaus: mov DWORD PTR[rbp-4], 11
(Zeile 33 im Screenshot). Im Gegensatz dazu ist Zeile (2) ein Funktionsaufruf: call gcd(int, int)
(Zeile 41 im Screenshot).
Nach dieser Zusammenfassung möchte ich auf die Gemeinsamkeiten von constexpr
-Funktionen und Template-Metaprogrammierung eingehen.
Template-Metaprogrammierung
constexpr
-Funktionen haben viel mit der Template-Metaprogrammierung gemeinsam. Wer mit der Template-Metaprogrammierung nicht vertraut ist, sollte meine drei vorangegangenen Beiträge einen Eindruck vermitteln.
- Template-Metaprogrammierung - Wie alles begann [1]
- Template-Metaprogrammierung - Wie es funktioniert [2]
- Template Metaprogrammierung - Hybride Programmierung [3]
Hier ist das große Bild, das constexpr
-Funktionen mit Template-Metaprogrammierung vergleicht:

Ich möchte meiner Tabelle noch ein paar Anmerkungen hinzufügen.
- Ein Template-Metaprogramm wird zur Compilezeit ausgeführt, aber eine
constexpr
-Funktion kann zur Compilezeit oder zur Runtime ausgeführt werden. - Argumente eines Template-Metaprogramms können Typen, Nicht-Typen wie
int
oder auch Templates sein. - Zur Compilezeit gibt es keinen Zustand und daher auch keine Veränderung. Das bedeutet, dass die Template-Metaprogrammierung ein rein funktionaler Programmierstil ist. Hier sind die Merkmale aus der Perspektive des funktionalen Stils:
- Bei der Template-Metaprogrammierung wird ein Wert nicht verändert, sondern jedes Mal ein neuer Wert zurückgegeben.
- Die Steuerung einer
for
-Schleife durch das Erhöhen einer Variablen wiei
ist zur Compilezeit nicht möglich:for (int i; i <= 10; ++i)
. Die Template-Metaprogrammierung ersetzt daher Schleifen durch Rekursion. - Bei der Template-Metaprogrammierung wird die bedingte Ausführung durch eine Template-Spezialisierung ersetzt.
Zugegeben, dieser Vergleich war recht knapp. Ein bildlicher Vergleich einer Metafunktion (siehe Template-Metaprogrammierung - Wie es funktioniert [4]) und einer constexpr
-Funktion sollte die offenen Fragen beantworten. Beide Funktionen berechnen die Fakultät einer Zahl.
- Die Funktionsargumente einer
constexpr
-Funktion entsprechen den Template-Argumenten einer Metafunktion.

- Eine
constexpr
-Funktion kann Variablen besitzen und diese verändern. Eine Metafunktion erzeugt einen neuen Wert.

- Eine Metafunktion verwendet Rekursion, um eine Schleife zu simulieren.

- Anstelle einer Endbedingung verwendet eine Metafunktion eine vollständige Spezialisierung eines Templates, um eine Schleife zu beenden. Außerdem verwendet eine Metafunktion eine teilweise oder vollständige Spezialisierung, um eine bedingte Ausführung wie
if
-Anweisungen durchzuführen.

- Anstelle eines aktualisierten Wertes
res
erzeugt die Metafunktion in jeder Iteration einen neuen Wert.

- Eine Metafunktion hat keine Rückgabeanweisung. Sie verwendet den Wert als Rückgabewert.

constexpr
-Funktionen und Templates haben aber noch mehr gemeinsam.
Instanziierung von Templates
Details zur Template-Instantiierung finden sich in meinen vorherigen Beitrag "Template-Instanziierung [5]". Ich möchte hier nur die wichtigsten Fakten hervorheben.
Eine Template wie isSmaller
wird zweimal syntaktisch geprüft:
template<typename T>
bool isSmaller(T fir, T sec){
return fir < sec;
}
isSmaller(5, 10); // (1)
std::unordered_set<int> set1;
std::unordered_set<int> set2;
- Zuerst wird die Syntax der Template-Definition geprüft. Diese Prüfung ist nicht durch den C++-Standard gefordert, aber erlaubt und wird in der Regel von Compilern durchgeführt.
- Zweitens leitet der Compiler die Template-Argumente aus den Funktionsargumenten ab. Er erstellt dabei für jedes Template-Aargument eine konkrete Funktion und überprüft deren Syntax. Dieser Instanziierungsprozess schlägt im Fall von
std::unordered_set<int>
(2) fehl, weil der Datentyp den<
-Operator nicht unterstützt.
constexpr
-Funktionen werden ebenfalls zweimal auf ihre Syntax geprüft.
constexpr auto gcd(int a, int b){
while (b != 0){
auto t= b;
b= a % b;
a= t;
}
return a;
}
constexpr int i= gcd(11, 121); // (1)
int a= 11;
int b= 121;
constexpr int j= gcd(a, b); // (2)
- Zunächst prüft der Compiler, ob die Funktion
gcd
zur Compilezeit ausgeführt werden kann. Das bedeutet im Wesentlichen, dass alle Abhängigkeiten einerconstexpr
-Funktion, wie zum Beispiel Funktion, die sie aufruft,constexpr
sein müssen. - Der Compiler muss bei jedem Aufruf von
gcd
darüber hinaus prüfen, ob die Argumente konstante Ausdrücke sind. Das hat zur Folge, dass der erste Aufruf (1) gültig ist, der zweite (2) jedoch nicht.
Letztlich sind sich Templates und constexpr
-Funktionen auch in Bezug auf die Sichtbarkeit ihrer Definition sehr ähnlich.
Sichtbarkeit
Zum Instanziieren eines Template muss dessen Definition sichtbar sein. Das Gleiche gilt für constexpr
-Funktionen.
Wie geht's weiter?
Im nächsten Beitrag schreibe ich über constexpr
-Funktionen in C++20 und das neue C++20 Schlüsselwort consteval
.
( [6])
URL dieses Artikels:
https://www.heise.de/-6342383
Links in diesem Artikel:
[1] https://www.heise.de/developer/artikel/Template-Metaprogrammierung-Wie-alles-begann-6233576.html
[2] https://www.heise.de/developer/artikel/Template-Metaprogrammierung-Wie-es-funktioniert-6237233.html
[3] https://www.heise.de/developer/artikel/Template-Metaprogrammierung-Hybride-Programmierung-6266012.html
[4] https://www.heise.de/developer/artikel/Template-Metaprogrammierung-Wie-es-funktioniert-6237233.html
[5] https://www.heise.de/developer/artikel/Template-Instanziierung-6151298.html
[6] mailto:rainer@grimm-jaud.de
Copyright © 2022 Heise Medien