Variadische Templates
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 67 von 104.
Was ist, wenn du eine Funktion benötigst, die eine beliebige Anzahl von Argumenten beliebiger Typen akzeptiert? Herkömmliche Templates erfordern, dass du genau angibst, wie viele Typparameter du benötigst. Variadische Templates lösen dieses Problem, indem sie Templates ermöglichen, eine beliebige Anzahl von Template-Argumenten zu akzeptieren.
Die Syntax verwendet ... (Auslassungspunkte), um ein Parameterpaket zu erstellen:
template <typename... Args>
void printAll(Args... args) {
// Args ist ein Template-Parameterpack
// args ist ein Funktionsparameterpack
}Um die Argumente zu verarbeiten, verwendest du typischerweise Rekursion mit einem Basisfall. Das Parameterpaket wird jeweils um ein Argument erweitert, bis keine Argumente mehr übrig sind:
// Basisfall: keine Argumente übrig
void print() {
std::cout << std::endl;
}
// Rekursiver Fall: erstes Argument verarbeiten, mit dem Rest rekursieren
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Verbleibende Argumente expandieren
}
print(1, 3.14, "hello", 'x'); // Ausgabe: 1 3.14 hello xDu kannst auch Fold-Ausdrücke (C++17) für einfachere Operationen ohne explizite Rekursion verwenden:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold-Expression: addiert alle Argumente
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Ausgabe: 10Variadische Templates bilden die Grundlage für viele Funktionen der Standardbibliothek wie std::make_unique, std::tuple und std::function. Sie ermöglichen typsichere Funktionen, die mit jeder Kombination von Argumenttypen und -anzahlen funktionieren.
Aufgabe
EinfachErstelle ein flexibles Protokollierungssystem, das Variadic Templates verwendet, um Nachrichten mit beliebig vielen Argumenten zu verarbeiten. Du erstellst Hilfsfunktionen, die Werte verketten, Argumente zählen und formatierte Ausgaben erzeugen – alles mithilfe von Parameter-Packs.
Du organisierst deinen Code über zwei Dateien:
Variadic.h: Definiere hier deine Variadic-Template-Funktionen.Erstelle eine
printAll-Funktion, die alle ihre Argumente durch Leerzeichen getrennt ausgibt, gefolgt von einem Zeilenumbruch. Verwende den rekursiven Ansatz mit einem Basisfall, der lediglich einen Zeilenumbruch ausgibt, und einem rekursiven Fall, der das erste Argument, ein Leerzeichen und anschließend den rekursiven Aufruf mit den verbleibenden Argumenten ausgibt.Erstelle eine
countArgs-Funktion, die die Anzahl der an sie übergebenen Argumente zurückgibt. Verwendesizeof..., um die Größe des Parameter-Packs zu ermitteln.Erstelle eine
sum-Funktion, die alle numerischen Argumente addiert und das Ergebnis zurückgibt. Verwende einen Fold-Ausdruck mit dem Operator+.Erstelle eine
product-Funktion, die alle numerischen Argumente multipliziert und das Ergebnis zurückgibt. Verwende einen Fold-Ausdruck mit dem Operator*.main.cpp: Lies vier Eingaben ein (jede in einer eigenen Zeile):- Eine Ganzzahl
- Eine Gleitkommazahl vom Typ double
- Eine Zeichenkette
- Ein Zeichen
Demonstriere deine Variadic Templates durch:
- Aufrufen von
printAllmit allen vier Eingaben und Ausgeben des Ergebnisses - Ausgeben der Anzahl der Argumente beim Aufruf von
countArgsmit der Ganzzahl, der Gleitkommazahl und dem Zeichen:Argument count: <count> - Berechnen der Summe aus der Ganzzahl, der Gleitkommazahl sowie den Werten 10 und 5 und anschließendes Ausgeben von:
Sum: <result> - Berechnen des Produkts aus der Ganzzahl und den Werten 2 und 3 und anschließendes Ausgeben von:
Product: <result> - Aufrufen von
printAllnur mit der Zeichenkette (ein einzelnes Argument) - Aufrufen von
printAllohne Argumente (soll lediglich einen Zeilenumbruch ausgeben)
Bei den Eingaben 5, 2.5, Hello und X sieht die Ausgabe beispielsweise so aus:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
Beachte, wie printAll eine beliebige Anzahl von Argumenten unterschiedlichen Typs verarbeitet – von vier Argumenten bis hin zu null. Die Funktionen sum und product verwenden Fold-Ausdrücke, um alle Werte elegant zu kombinieren, ohne eine explizite Rekursion zu benötigen. Deine countArgs-Funktion zeigt, wie sizeof... die Größe des Packs zur Kompilierzeit liefert.
Probier es selbst
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// Eingaben lesen
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: Rufe printAll mit allen vier Eingaben auf
// TODO: Print argument count using countArgs with intVal, doubleVal, and charVal
// Format: "Argument count: <count>"
// TODO: Berechne und gib die Summe von intVal, doubleVal, 10 und 5 aus
// Format: "Sum: <result>"
// TODO: Berechne und gib das Produkt von intVal, 2 und 3 aus
// Format: "Product: <result>"
// TODO: Rufe printAll nur mit dem String auf
// TODO: Rufe printAll ohne Argumente auf
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler