Clevere Tricks mit Parameterpacks und Fold Expressions
Als Vervollständigung zu den Beiträgen zu Variadic Templates und Fold Expressions gibt es clevere Tricks mit Parameterpacks und Fold Expressions.
Als Vervollständigung zu den Beiträgen zu Variadic Templates und Fold Expressions gibt es clevere Tricks mit Parameterpacks und Fold Expressions.
![Clevere Tricks mit Parameterpacks und Fold Expressions](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/7/0/4/1/6/templates-73ce50cfb7afb925.png)
Fold Expressions ermöglichen es, ein Parameterpack mit einem binären Operator zu reduzieren. Dank ihnen kann man prägnante Ausdrücke für wiederholte Operationen schreiben, beispielsweise eine print
-Funktion oder eine push_back
-Funktion , um Elemente in einen Vektor zu schieben. Beginnen möchte ich mit der print
Funktion.
// printFoldExpressions.cpp
#include <iostream>
#include <string>
template<typename ... Args>
void printMe(Args&& ... args) {
(std::cout << ... << std::forward<Args>(args)) << '\n';
}
int main() {
std::cout << '\n';
std::cout << std::boolalpha;
printMe();
printMe("Rainer ", "Grimm");
printMe(true, " ", "+", " ",false, " = ", true + false);
std::cout << '\n';
}
Die Funktion printMe
kann eine beliebige Anzahl von Argumenten annehmen. In der konkreten Funktion bedeutet das: kein Argumente, zwei C-Strings sowie ein paar Strings und Zahlen. Die printMe
Funktion bestimmt automatisch deren Typen und zeigt sie an. Drei leistungsstarke C++-Techniken sind beteiligt.
- Variadic Templates (
...
): akzeptiert eine beliebige Anzahl von Argumenten. Mehr Details bieten die folgenden Artikel zu "Variadic Templates oder die Power der drei Punkte [1]" und "Mehr über Variadic Templates [2]" an. - Perfect Forwarding (
std::forward
): Leitet die Argumente weiter, ohne ihre Wertkategorie zu ändern. Mehr Details bietet der Artikel "Perfect Forwarding [3]" an. - Fold Expressios (
std::cout << ... << std::forward<Args>(args)
): reduziert das Parameterpack von links mithilfe des binären Operators<<
und dem Anfangswertstd::cout
. Mehr Details bietet der Artikel "Von Variadic Templates zu Fold Expressions [4]" an.
Zum Schluss ist hier die Ausgabe des Programms.
![Clevere Tricks mit Parameterpacks und Fold Expressions](https://heise.cloudimg.io/width/425/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/7/0/4/1/6/printFoldExpressions-bfb621be90204edd.png)
Dank der Fold Expression lässt sich eine beliebige Anzahl von Argumenten in einen Vektor schieben.
// pushBackFoldExpressions.cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
template<typename T, typename... Args>
void myPushBack(vector<T>& v, Args&&... args) {
(v.push_back(args), ...); // (1)
}
int main() {
std::cout << '\n';
std::vector<int> myIntVec;
myPushBack(myIntVec, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for (auto v : myIntVec) std::cout << v << ' ';
std::cout << "\n\n";
std::vector myDoubleVec{1.1, 2.2, 3.3}; // (2)
myPushBack(myDoubleVec, 4.4, 5.5, 6.6);
for (auto v: myDoubleVec) std::cout << v << ' ';
std::cout << "\n\n";
}
(1) und (2) sind besonders interessant. (2) schiebt die drei double
-Werte in den Vektor. Mit C++17 kann der Compiler automatisch die Typen der Argumente ableiten. Der Ausdruck (v.push_back(args),...
) schiebt die Elemente von rechts mit dem binären Kommaoperator (,) auf den Vektor. Da er assoziativ ist, lassen sich die Elemente auch von links drauf schieben: (...,
v.push_back(args)
). Ehrlich gesagt, sieht das ungewohnt aus. Deshalb bevorzuge ich die erste Variante.
Der folgende Screenshot zeigt die Ausgabe des Programms.
![Clevere Tricks mit Parameterpacks und Fold Expressions](https://heise.cloudimg.io/width/513/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/7/0/4/1/6/pushBackFoldExpressions-007a3b838917661a.png)
Jetzt möchte ich einen Schritt zurück von den Fold Expressions zu den Variadic Templates gehen und das Overload Pattern vorstellen. Das Overload Pattern ist eine clevere Methode, um mehrere Lambdas in ein Overload Set zu packen.
Overload Pattern
Ich will es kurz machen. Hier ist das Overload Pattern mit C++20 implementiert:
template<typename ... Ts> struct Overload : Ts ... { using Ts::operator() ... ; };
Was? Entschuldigung, mein Fehler. Ich sollte es richtig layouten.
template<typename ... Ts>
struct Overload : Ts ... {
using Ts::operator() ... ;
};
Die struct
Overload
kann beliebig viele Basisklassen (Ts ...
) besitzen. Sie leitet sich von jeder Klasse public
ab und nimmt den Aufrufoperator (Ts::operator...
) jeder Basisklasse in ihren Geltungsbereich auf.
Es gibt noch mehr über diese vier magischen Codezeilen zu erklären. Bevor ich das in meinem nächsten Beitrag tue, möchte ich das Overload Pattern verwenden, um die Typen der Ganzzahlliterale darzustellen. Das folgende Programm erfordert einen C++20-Compiler.
// overloadPattern.cpp
#include <iostream>
template<typename ... Ts>
struct Overload : Ts ... {
using Ts::operator() ...;
};
int main() {
std::cout << '\n';
auto TypeOfIntegral = Overload {
[](int) { return " int"; },
[](unsigned int) { return " unsigned int"; },
[](long int) { return " long int"; },
[](long long int) { return "long long int"; },
[](auto) { return "unbekannter Typ"; },
};
std::cout << "TypeOfIntegral(5): " << TypeOfIntegral(5) << '\n';
std::cout << "TypeOfIntegral(5u): " << TypeOfIntegral(5u) << '\n';
std::cout << "TypeOfIntegral(5U): " << TypeOfIntegral(5U) << '\n';
std::cout << "TypeOfIntegral(5l): " << TypeOfIntegral(5l) << '\n';
std::cout << "TypeOfIntegral(5L): " << TypeOfIntegral(5L) << '\n';
std::cout << "TypeOfIntegral(5ll): " << TypeOfIntegral(5ll) << '\n';
std::cout << "TypeOfIntegral(5LL): " << TypeOfIntegral(5LL) << '\n';
std::cout << '\n';
std::cout << "TypeOfIntegral(5ul): " << TypeOfIntegral(5ul) << '\n';
std::cout << "TypeOfIntegral(5.5): " << TypeOfIntegral(5.5) << '\n';
std::cout << '\n';
}
Im Programm overloadPattern.cpp
besteht das Overload Set aus Lambda-Ausdrücken, die einen int
, einen unsigned int
, einen long int
, einen long long int
und auto
akzeptieren. auto
ist der Fallback, der verwendet wird, wenn das Overload Set mit einem unbekannten Typ aufgerufen wird. Das passiert zum Beispiel, wenn ich TypeOfIntegral
mit einem unsigned long
oder einem double
Wert aufrufe.
![Clevere Tricks mit Parameterpacks und Fold Expressions](https://heise.cloudimg.io/width/403/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/7/0/4/1/6/overloadPattern-d6d7e040e5f0404b.png)
Wie geht es weiter?
Normalerweise kommt das Overload Pattern für eine std::variant
zum Einsatz, die eine typsichere Union ist. Eine Instanz var
von std::variant
(C++17) hat einen Wert aus einem ihrer Typen. std::visit
ermöglicht es, einen Besucher auf var
anzuwenden. Genau hier kommt das Overload Pattern sehr praktisch ins Spiel. Lies mehr über std::variant, std::visit
und das Overload Pattern in meinem nächsten Beitrag.
Pdf-Päckchen: C++20 Module
Auf der Grundlage der letzten Umfrage habe ich das nächste Pdf-Päckchen erstellt.
![Clevere Tricks mit Parameterpacks und Fold Expressions](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/7/0/4/1/6/bundle-5e1108ce3543f2e3.png)
Das pdf-Päckchen enthält alle
- Beiträge.
- Quellcode-Dateien zu diesen Beiträgen.
In dem Artikel "The New pdf Bundle is Ready: C++20 Modules [5]" erkläre ich, wie man das pdf-Päckchen einfach erhalten kann.
( [6])
URL dieses Artikels:
https://www.heise.de/-6195143
Links in diesem Artikel:
[1] https://heise.de/-6157802
[2] https://heise.de/-6165404
[3] https://www.grimm-jaud.de/index.php/blog/perfect-forwarding
[4] https://heise.de/-6189541
[5] https://www.modernescpp.com/index.php/the-new-pdf-bundle-is-ready-c-20-modules
[6] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien