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.
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]:
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:
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:
Dank des Compiler Explorer [4] kann ich die Ergebnisse der Berechnung deutlich beeindruckender prÀsentieren. Dies sind die entscheidenden Assembler-Instruktionen mit dem GCC:
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:
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