constexpr std::vector und constexpr std::string in C++20
C++20 erhält einen constexpr std::vector und einen constexpr std::string. Beide Container lassen sich mit den constexpr-Algorithmen der STL zur Compilezeit modifizieren.
Das wohl am einflussreichste Schlüsselwort im modernem C++ ist constexpr. Mit C++20 erhalten wir einen constexpr std::vector
und einen constexpr std::string
. Zusätzlich lassen sich beide Container mit den constexpr-Algorithmen der Standard Template Library zur Compilezeit modifizieren
In diesem Artikel möchte ich die Summe und das Produkt mehrerer Zahlen zur Compilezeit berechnen. Je nachdem, welchen C++-Standard ich dabei einsetze, ist die Aufgabe anspruchsvoll und aufwendig oder geht leicht von der Hand. Der Artikel startet mit C++11.
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/TimelineCpp20CoreLanguage-812c9a3778d4b7a3.png)
Variadic Templates in C++11
Ein Variadic Template ist ein Template, das sich mit einer beliebigen Anzahl von Argumenten aufrufen lässt. Durch die Verwendung der Ellipse (...) wird tails
zu einem Parameter-Pack. Nur zwei Operationen lassen sich auf ein Parameter-Pack anwenden: packen und entpacken. Wenn die Ellipse links von tails
steht, wird gepackt, wenn sie rechts von tails
steht, entpackt:
// compiletimeVariadicTemplates.cpp
#include <iostream>
template<int...>
struct sum;
template<>
struct sum<> {
static constexpr int value = 0;
};
template<int i, int... tail>
struct sum<i, tail...> {
static constexpr int value = i + sum<tail...>::value;
};
template<int...> // (1)
struct product;
template<> // (2)
struct product<> {
static constexpr int value = 1;
};
template<int i, int... tail> // (3)
struct product<i, tail...> {
static constexpr int value = i * product<tail...>::value;
};
int main() {
std::cout << std::endl;
std::cout << "sum<1, 2, 3, 4, 5>::value: " << sum<1, 2, 3, 4, 5>::value << std::endl;
std::cout << "product<1, 2, 3, 4, 5>::value: " << product<1, 2, 3, 4, 5>::value << std::endl;
std::cout << std::endl;
}
Das Programm berechnet die Summe und das Produkt der Zahlen 1 bis 5 zur Compilezeit. Im Fall des Funktions-Templates product
erklärt die Zeile (1) das primäre Template, die Zeile (2) die vollständige Spezialisierung für kein Argument und die Zeile (3) die partielle Spezialisierung für zumindest ein Argument. Die Definition des primären Templates ist nicht notwendig, falls sie nicht verwendet wird. Die partielle Spezialisierung (3) startet die rekursive Instanziierung, die dann zum Ende kommt, wenn alle Argumente konsumiert sind. In diesem Fall wird die vollständige Spezialisierung für kein Argument als Endbedingung verwendet. Wenn du die Entpackung des Parameter-Packs genauer studieren möchtest, studiere das Beispiel compileTimeVariadicTemplate.cpp
auf C++ Insights [1]:
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/533/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/compileTimeVariadicTemplate-0d4ac9c639d6219d.png)
Dank Fold Expressions wird diese Berechnung deutlich einfacher.
Fold Expression in C++17
Mit C++17 können wir Parameter-Packs direkt über einem binären Operator reduzieren:
// compiletimeFoldExpressions.cpp
#include <iostream>
template<typename... Args>
auto sum(const Args&... args)
{
return (args + ...);
}
template<typename... Args>
auto product(const Args&... args)
{
return (args * ...);
}
int main() {
std::cout << std::endl;
std::cout << "sum(1, 2, 3, 4, 5): " << sum(1, 2, 3, 4, 5) << std::endl;
std::cout << "product(1, 2, 3, 4, 5): " << product(1, 2, 3, 4, 5) << std::endl;
std::cout << std::endl;
}
Das Programm compileTimeFoldExpressions.cpp
liefert dieselben Ergebnisse wie das vorherige Programm compileTimeVariadicTemplates.cpp
:
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/627/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/compiletimeFoldExpressions-71a82cdff8a045e0.png)
Natürlich gibt es mehr zu Fold Expressions in C++17 zu erzählen. Diese Details lassen sich in meinem früheren Artikel Fold Expressions [2] nachlesen.
Jetzt will ich mich aber endlich mit C++20 beschäftigen.
constexper
-Container und -Algorithmen in C++20
C++20 unterstützt die constexpr
-Container std::vector
und std::string
. constexpr
bedeutet in diesem Fall, dass die Methoden beider Container zur Compilezeit ausgeführt werden können.
Bevor ich aber über beide Container schreibe, muss ich nochmals einen kleinen Abstecher zu C++17 machen. Der Grund ist einfach: Kein Compiler unterstützt zum gegenwärtigen Zeitpunkt einen constexpr
std::vector
oder constexpr std::string
. Im Gegensatz dazu unterstützen der GCC und der MS Compiler die constexpr
-Algorithmen der STL.
In meinem folgenden Beispiel verwende ich anstelle des constexpr std::vector
das constexpr std::array
. Seit C++17 lässt sich ein std::array
als constexpr
deklarieren: constexpr std::array myArray{1, 2, 3}.
Jetzt geht der Spaß los. Mit C++20 lässt sich ein std::array
zur Compilezeit verwenden:
// constexprArray.cpp
#include <iostream>
#include <numeric>
#include <array>
int main() {
std::cout << std::endl;
constexpr std::array myArray{1, 2, 3, 4, 5}; // (1)
constexpr auto sum = std::accumulate(myArray.begin(), myArray.end(), 0); // (2)
std::cout << "sum: " << sum << std::endl;
constexpr auto product = std::accumulate(myArray.begin(), myArray.end(), 1, // (3)
std::multiplies<int>());
std::cout << "product: " << product << std::endl;
constexpr auto product2 = std::accumulate(myArray.begin(), myArray.end(), 1, // (4)
[](auto a, auto b) { return a * b;});
std::cout << "product2: " << product2 << std::endl;
std::cout << std::endl;
}
Das std::array
(1) und alle Ergebnisse der Berechnungen sind als constexpr
deklariert. Zeile (2) berechnet die Summe aller Elemente und die Zeilen (3) und (4) das Produkt aller Elemente von myArrray
. Die Zeile (2) ist gültig, da myArray
ein constexpr
-Container und der Algorithmus std::accumulate
als constexpr
deklariert ist. Die Zeilen (3) und (4) sind deutlich interessanter. Der Klammeroperator von std::multiplies [3]
ist constexpr
deklariert und seit C++17 können Lambda-Ausdrücke constexpr
sein.
Hier ist die Ausgabe des Programms:
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/500/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/constexprArray-ceafcc649a4dc17c.png)
Dank des Compiler Explorer [4] kann ich die Ergebnisse der Berechnung deutlich beeindruckender präsentieren. Dies sind die entscheidenden Assembler-Instruktionen mit dem GCC:
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/406/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/constexprArrayCompilerExplorer-84e681ade811b2c3.png)
Die Zeilen 19, 29 und 39 zeigen, dass die Ergebnisse der Array-Berechnungen Werte in den Assember-Instruktionen sind. Das heißt, dass std::accumulate
zur Compilezeit ausgeführt und die Ergebnisse zur Laufzeit vorhanden sind.
Wie ich bereits geschrieben habe, unterstützt zum jetzigen Zeitpunkt kein Compiler einen constexpr std::vector
oder std::string
. Daher muss ich jetzt ein wenig schummeln und annehmen, dass mein Compiler beide constexpr
-Container vollständig unterstützt:
// constexprVectorString.cpp
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::cout << std::endl;
constexpr std::vector myVec {15, -5, 0, 5, 10};
constexpr std::sort(myVec.begin(), myVec.end());
for (auto v: myVec) std::cout << v << " ";
std::cout << "\n\n";
using namespace std::string_literals;
constexpr std::vector<std::string> myStringVec{"Stroustrup"s, "Vandevoorde"s,
"Sutter"s, "Josuttis"s, "Wong"s };
constexpr std::sort(myStringVec.begin(), myStringVec.end());
for (auto s: myStringVec) std::cout << s << " ";
std::cout << "\n\n";
}
Mit C++20 lässt sich ein std::vector
oder ein std::string
zur Compilezeit sortieren:
![constexpr Vektor und String in C++20](https://heise.cloudimg.io/width/476/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/7/1/3/6/5/constexprVectorString-b058fbba1665c1e3.png)
Wie geht's weiter?
C++20 bietet zusätzlich viele Funktionen an, die den Umgang mit Containern der Standard Template Library deutlich angenehmer machen. Dank den Funktionen std::erase
und std::erase_if
geht das Löschen der Elemente eines Containers deutlich leichter von der Hand. Mit der Funktion contains
ist es einfach zu bestimmen, ob ein bestimmtes Element in einem assoziativen Container enthalten ist.
( [5])
URL dieses Artikels:
https://www.heise.de/-4906108
Links in diesem Artikel:
[1] https://cppinsights.io/s/33f60630
[2] https://www.grimm-jaud.de/index.php/44-blog/funktional/190-fold-expressions
[3] https://en.cppreference.com/w/cpp/utility/functional/multiplies
[4] https://godbolt.org/z/ezv7Po
[5] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien