zurück zum Artikel

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Rainer Grimm

Diese Serie zur Programmierung zur Compilezeit begann mit der Template-Metaprogrammierung, gefolgt von der Type-Traits-Bibliothek und endet heute mit konstanten Ausdrücken (constexpr).

Diese Serie zur Programmierung zur Compilezeit begann mit der Template-Metaprogrammierung, gefolgt von der Type-Traits-Bibliothek und endet heute mit konstanten Ausdrücken (constexpr).

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Jetzt sind wir endlich an der Spitze des Dreiecks angekommen. Dies ist aber mehr als nur ein Bild.

constexpr erlaubt es explizit zur Compilezeit zu programmieren und dies in der vertrauten C++-Syntax. Der Fokus dieses Artikels liegt nicht darin, alle Details zu constexpr vorzustellen, sondern vielmehr darin, Template-Metaprogrammierung mit constexpr-Funktionen zu vergleichen. Des Vergleich willens, möchte ich aber erst einen kurzen Überblick zu constexpr geben. Falls dieser Vergleich zu mager ist, bieten meine früheren Artikel zu constexpr [1] ausreichend Nahrung. Welche Vorteile besitzen konstante Ausdrücke?

Ein konstanter Ausdruck

Konstante Ausdrücke mit constexpr können drei Formen besitzen.

Variablen

Funktionen

constexpr Funktionen in C++14 sind sehr einfach einzusetzen. Sie sind implizit inline und können

Benutzerdefinierte Typen

Die Regeln für constexpr-Funktionen und -Methoden sind ziemlich ähnlich. Daher spreche ich nun nur noch von Funktionen.

constexpr-Funktionen dürfen nur von Funktionalität abhängen, die einen konstanten Ausdruck darstellt. Eine constexpr-Funktion bedeutet nicht, dass eine Funktion zur Compilezeit ausgeführt wird. Es bedeutet, dass eine Funktion das Potenzial besitzt, zur Compilezeit ausgeführt zu werden. Eine constexpr-Funktion kann auch zur Laufzeit ausgeführt werden. Es ist oft eine Frage des Compilers oder der Optimierung, ob eine Funktion zur Compile- oder Laufzeit ausgeführt wird. Es gibt aber zwei Gründe, warum constexpr-Funktionen zur Compilezeit ausgeführt werden müssen.

  1. Die constexpr-Funktion wird in einem Kontext eingesetzt, der zur Compilezeit ausgewertet wird. Dies kann ein static_assert-Ausdruck oder die Initialisierung eines C-Arrays sein.
  2. Der Wert eine constexpr-Funktion wird explizit zur Compilezeit angefordert: constexpr auto res = func(5);

Hier kommt ein kleines Beispiel zu der 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 << std::endl;

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 << std::endl;
std::cout << "gcd(a,b): " << j << std::endl;

std::cout << std::endl;

}

Die Zeile (1) berechnet das Ergebnis i zur Compilezeit und die Zeile (2) zur Laufzeit. Der Compiler würde sich eindeutig beschweren, wenn ich j als constexpr erklären würde: constexpr int j = gcd(a, b). Der Grund ist, dass weder a noch b konstante Ausdrücke sind.

Die Ausgabe des Programms sollte keine Überraschung bergen.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Vielleicht geht ja die Überraschung jetzt los. Lass mich die Magie mit dem Compiler Explorer [2] vorstellen:

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Die Zeile (1) des Programms constexpr14.cpp reduziert sich auf die Konstante 11 in dem folgenden Ausdruck: mov DWORD PTR[rbp-4], 11 (Zeile 33 im Screenshot). Im Gegensatz dazu, wird die Zeile (2) zu einem Funktionsaufruf: call gcd(int, int) (Zeile 41 in dem Screenshot).

Jetz kann ich endlich mein Hauptanliegen vorstellen.

Zuerst einmal das große Bild:

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Die Tabelle verlangt ein paar Erläuterungen.

Zugegeben, dieser Vergleich ist sehr kurz und knapp. Eine bildliche Gegenüberstellung von Metafunktionen [5] und constexpr-Funktionen wird die offenen Fragen beantworten. Beide Funktionen berechnen die Fakultät einer Zahl.

Die Funktionsargumente der constexpr-Funktion entsprechen den Template-Argumenten der Metafunktion.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

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

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Eine Metafunktion stellt Schleifen durch Rekursion dar.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Anstelle einer Endbedingung, verwendet eine Metafunktion eine vollständige Spezialisierung zur Beendigung einer Rekursion. Zusätzlich erlaubt teilweise oder vollständige Spezialisierung eines Templates bedingt Codeausführung entsprechend einer if-Anweisung.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Anstelle eines veränderten Wertes res, erzeugt eine Metafunktion immer einen neuen Wert.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Eine Metafunktion besitzt keine return-Anweisung. Stattdessen verwendet sie value als Rückgabewert.

C++ Core Guidelines: Programmierung zur Compilezeit mit constexpr

Neben den Vorteilen, dass constexpr-Funktionen komfortabler zu schreiben und zu pflegen sind und zur Laufzeit ausgeführt werden können, besitzen sie noch einen weiteren Vorteil. Der Codeschnipsel stellt ihn vor:

constexpr double average(double fir , double sec){
return (fir + sec) / 2;
}

int main(){
constexpr double res = average(2, 3);
}

constexpr-Funktionen können mit Fließkommazahlen umgehen. Template-Metaprogrammierung verlangt Ganzzahlen.

Dieser Artikel beendet meinen Umweg zur Programmierung zur Compilezeit. Das nächste Mal schreibe ich über die verbleibenden Regeln zu Templates. ( [6])


URL dieses Artikels:
https://www.heise.de/-4296535

Links in diesem Artikel:
[1] https://www.grimm-jaud.de/index.php/blog/tag/constexpr
[2] https://godbolt.org/
[3] https://www.grimm-jaud.de/index.php/blog/funktionale-programmierung-die-definition
[4] https://www.grimm-jaud.de/index.php/blog/category/funktional?start=0
[5] https://heise.de/-4271375
[6] mailto:rainer@grimm-jaud.de