constexpr und consteval Funktionen in C++20
Mit C++20 wurde constexpr viel mächtiger. Zusätzlich besitzt C++20 consteval Funktionen, die constexpr Funktionen sehr ähnlich sind.
- Rainer Grimm
Mit C++20 wurde constexpr
viel mächtiger. Zusätzlich besitzt C++20 consteval
Funktionen, die constexpr
Funktionen sehr ähnlich sind.
Ich möchte zunächst eine Funktionalität in C++20 beschreiben, die mich wohl am meisten überrascht hat.
constexpr
Container und Algorithmen der Standard Template Library
C++20 bietet die constexpr
Container std::vector
und std::string
, wobei constexpr
bedeutet, dass die Memberfunktionen beider Container zur Compilezeit angewendet werden können. Zusätzlich können die mehr als 100 klassischen Algorithmen der Standard Template Library als constexpr
deklariert werden. Damit lässt sich ein std::vector
von ints zur Compilezeit sortieren.
Schauen wir uns an, was das bedeutet:
// constexprVector.cpp
#include <algorithm>
#include <iostream>
#include <vector>
constexpr int maxElement() {
std::vector myVec = {1, 2, 4, 3}; // (1)
std::sort(myVec.begin(), myVec.end());
return myVec.back();
}
int main() {
std::cout << '\n';
constexpr int maxValue = maxElement();
std::cout << "maxValue: " << maxValue << '\n';
constexpr int maxValue2 = [] {
std::vector myVec = {1, 2, 4, 3}; // (2)
std::sort(myVec.begin(), myVec.end()) ;
return myVec.back();
}();
std::cout << "maxValue2: " << maxValue2 << '\n';
std::cout << '\n';
}
Die beiden Container std::vector
((1) und (2)) werden zur Compilezeit mithilfe von constexpr
-deklarierten Funktionen sortiert. Im ersten Fall gibt die Funktion maxElemen
t das letzte Element des Vektors myVec
zurĂĽck, was dessen Maximalwert ist. Im zweiten Fall verwende ich eine direkt aufgerufene Lambda-Funktion, die als constexpr
deklariert ist. Hier ist das Ergebnis des Programms:
Der entscheidende Grund fĂĽr constexpr
Container ist die transiente Zuweisung.
Transiente Zuweisung
Transiente Zuweisung bedeutet, dass der zur Compilezeit zugewiesene Speicher auch zur Compilezeit wieder freigegeben werden muss. So kann der Compiler ein Missverhältnis zwischen Zuweisung und Freigabe in einer constexpr
Funktion erkennen. Das folgende Beispiel wendet die transiente Zuweisung an.
// transientAllocation.cpp
#include <memory>
constexpr auto correctRelease() {
auto* p = new int[2020];
delete [] p;
return 2020;
}
constexpr auto forgottenRelease() { // (1)
auto* p = new int[2020];
return 2020;
}
constexpr auto falseRelease() { // (3)
auto* p = new int[2020];
delete p; // (2)
return 2020;
}
int main() {
constexpr int res1 = correctRelease();
constexpr int res2 = forgottenRelease();
constexpr int res3 = falseRelease();
}
Das kleine Programm hat zwei ernsthafte Probleme. Erstens wird der Speicher in der constexpr
Funktion forgottenRelease
(1) nicht freigegeben. Zweitens stimmt die Nicht-Array-Freigabe (3) in der constexpr
Funktion falseRelease
(2) nicht mit der Array-Freigabe überein. Infolgedessen schlägt die Kompilierung fehl.
Mit C++20 gibt es consteval
Funktionen, die den contexpr
Funktionen sehr ähnlich sind.
consteval
Funktionen
Oft sind Entwickler irritiert, weil sie nicht wissen, ob eine constexpr
Funktion zur Laufzeit oder zur Compilezeit ausgefĂĽhrt wird. Betrachten wir den folgenden Codeschnipsel.
constexpr int constexprFunction(int arg) {
return arg * arg;
}
static_assert(constexprFunction(10) == 100); // (1)
int arrayNewWithConstExpressiomFunction[constexprFunction(100)]; // (2)
constexpr int prod = constexprFunction(100); // (3)
int a = 100;
int runTime = constexprFunction(a); // (4)
int runTimeOrCompiletime = constexprFunction(100); // (5)
constexprFunction
ist, wie der Name schon sagt, eine constexpr
Funktion.
- Eine
constexpr
Funktion muss zur Compilezeit ausgefĂĽhrt werden, wenn sie in einemconstexpr
Kontext verwendet wird oder das Ergebnis zur Compilezeit explizit angefordert wird. (1) und (2) sindconstexpr
Kontexte. (3) hingegen erfordert explizit die FunktionsausfĂĽhrung vonconstexprFuncion
zur Compilezeit. - Der Aufruf
constexprFunction(a)
(4) muss zur Laufzeit ausgefĂĽhrt werden, daa
kein konstanter Ausdruck ist. - (5) ist der interessante Fall. Es gibt keine Anforderungen an die AusfĂĽhrung der Funktion. Daher kann der Aufruf
constexprFunction(100)
zur Laufzeit oder zur Compilezeit ausgefĂĽhrt werden. Aus Sicht des C++-Standards ist beides in Ordnung.
Im Gegensatz zu einer constexpr
Funktion kann eine consteval
Funktion nur zur Compilezeit ausgefĂĽhrt werden.
consteval erzeugt eine sogenannte immediate Funktion.
consteval int sqr(int n) {
return
n * n;
}
Jeder Aufruf einer immediate Funktion erzeugt eine Konstante zur Compilezeit. consteval
kann nicht auf Destruktoren oder Funktionen angewendet werden, die allokieren oder deallokieren. Eine consteval
Funktion ist wie eine constexpr
Funktion implizit inline
und muss die Anforderungen an eine constexpr
Funktion erfĂĽllen.
Die Anforderungen an eine constexpr
Funktion in C++14 und damit auch an eine consteval
Funktion sind:
Eine consteval (constexpr)
Funktion kann
- bedingte Sprung- oder Schleifenanweisungen enthalten.
- mehr als eine Anweisung haben.
constexpr
Funktionen aufrufen. Eineconsteval
Funktion kann nur eineconstexpr
Funktion aufrufen, aber nicht umgekehrt.- built-in Datentypen als Variablen verwenden, die mit einem konstanten Ausdruck initialisiert werden mĂĽssen.
Eine consteval (constexpr)
Funktion kann nicht
- statische oder thread_local Daten besitzen
- weder einen
try
-Block noch einegoto
-Anweisung besitzen. - nicht
conteval
Funktionen oder nichtconstexpr
Daten aufrufen oder verwenden.
Es gibt einen interessanten Anwendungsfall, den consteval
ermöglicht. Mit consteval
lässt sich eine lokale nicht konstante Variable zur Compilezeit initialisieren.
// compileTimeInitializationLocal.cpp
consteval auto doubleMe(auto val) {
return 2 * val;
}
int main() {
auto res = doubleMe(1010); // (1)
++res; // 2021 (2)
}
Die lokale Variable res
wird zur Compilezeit initialisiert (1) und zur Laufzeit geändert (2). Wenn die Funktion doubleMe
hingegen als constexpr
deklariert wird, könnte sie zur Laufzeit ausgeführt werden.
Wie geht's weiter?
Bevor ich in den neuen Themenblock Design mit Templates eintauche, möchte ich im nächsten Beitrag das C++17-Feature constexpr if
vorstellen. constexpr if
ermöglicht es, Quellcode bedingt zu kompilieren und kann auch für nette Tricks zur Compilezeit verwendet werden.
()