std::vector,std::unique_ptr,std::vector<int> vec; // Enthält nur intDie Lösung: Templates! 😀
template kündigt Schablone an<typename T> beschreibt SchablonenparameterT als Platzhalter für einen DatentypT eine_variable#include <iostream>
using std::cout, std::endl;
// Deklaration von Template-Funktionen
template <typename T>
T add(T sum1, T sum2) {
return (sum1 + sum2);
}
// Mehrere verschiedene Typen auch ok
template <typename T, typename U>
T add_mixed(T sum1, U sum2) {
return (sum1 + sum2);
}
int main() {
cout << "Add double: " << add<double>(2.5, 1.3) << endl;
cout << "Add int: " << add<int>(4, 3) << endl;
// Ggf. Rundungsfehler in Implementierung, abhängig von erstem Typ T
cout << "Add_mixed int + double: " << add_mixed<int, double>(2.5, 1.3) << endl;
cout << "Add_mixed double + int: " << add_mixed<double, int>(2.5, 1.3) << endl;
}
Automatische Umwandlung in C++ → implizite Template-Instanziierung
Umwandlung eines generischen Typs in einen Konkreten
→ Monomorphisierung (Monomorphization)
Generische Funktionen / Klassen können neuen Programmcode erzeugen
→ Meta-Funktionen / Meta-Klassen
→ Metaprogrammierung
Häufiger Begriff: Template Metaprogramming (synonyme Verwendung)
Auch Klassen können eine Schablone sein
Hier: Datentyp Pair
Zwei Templateparameter
→ Angabe zweier Datentypen
Randbemerkung: Gibt es auch in der STL
→ std::pair verfügbar
Bspw. std::vector<int>
#include <iostream>
using namespace std;
template <typename T, int NUM>
class Storage {
T data[NUM];
int pos;
public:
Storage() : pos(0) {}
void put(T val) {
data[pos] = val;
pos = (pos + 1) % NUM;
}
void print() {
for (int i = 0; i < NUM; i++) {
cout << data[i] << ",";
}
}
};
int main() {
Storage<double, 3> a;
a.put(3.2);
a.put(1.42);
a.put(0);
a.print();
return 0;
}
Während der Übersetzung: Compiler ersetzt Platzhalter
Compiler findet „unbekannten” Funktionsaufruf
Wichtig: Es wird für jeden Datentyp eine eigene Kopie erstellt
Ergebnis: Automatisch erzeugter Programmschnipsel
Trennung von Programmcode in Header- und Source-Datei nicht möglich
→ Schablonen im Header definieren
Kann sehr unübersichtlich sein
Über Tricks ist die Trennung dennoch möglich
#include-Anweisung: Fügt Quellcode in den Header ein#include <iostream>
using namespace std;
template <typename T, typename U>
struct Pair {
T first;
U second;
Pair(T f, U u) : first(f), second(u) {}
};
int main() {
// 0 Typen angegeben. ERROR
Pair<> p1 = {1, 2};
cout << p1.first << endl;
Pair<int, int> p2 = {1, 2}; // Ok
cout << p2.first << endl;
// Ab C++20 ok (siehe Regelliste)
Pair p3 = {3.5f, 2.3f};
cout << p3.first << endl;
Pair<float, float> p4 = {3.5f, 2.3f}; // Ok
cout << p4.first << endl;
}
#include <iostream>
using std::cout, std::endl;
template <typename T>
T add(T sum1, T sum2) {
return (sum1 + sum2);
}
int main() {
cout << "Add double: " << add(2.5, 1.3) << endl;
cout << "Add int: " << add(4, 3) << endl;
}
Viele Templates → erhöhte Übersetzungszeit (Instanziierung)
Kann auch unnötiger Code generiert werden → größeres Binary
Fehler in (geschachtelter) Templateprogrammierung sind anspruchsvoll
→ Kryptische Fehlermeldungen beim Kompilieren
Foo und Bar mithilfe von add()requires markiert Einschränkung&& (logisches UND) bzw. || (logisches ODER)true oder false zurück// add() soll nur Integer bzw.
// float/double akzeptieren
#include <iostream>
#include <type_traits> // für is_integral_v bzw. is_floating_point_v
using std::cout, std::endl;
template <typename T>
// Entweder Integer ODER Float
requires std::is_integral_v<T> || std::is_floating_point_v<T>
T add(T sum1, T sum2) {
return sum1 + sum2;
}
class Foo {};
int main() {
cout << add<int>(3,4) << endl;
cout << add<float>(2.5, 1.3) << endl;
// Error: constraints not satisfied
// cout << add<Foo>(Foo(), Foo()) << endl;
}
Zusammenfassen von Constraints
→ Concept
Bildung eines abstrakten Typen, der alle Eigenschaften erfüllt
Erspart Schreibarbeit und erlaubt einfache Wiederverwendung
#include <iostream>
#include <type_traits>
using std::cout, std::endl;
// Jetzt als abstrakter Typ
template <typename T>
concept FloatOrInt = std::is_integral_v<T> || std::is_floating_point_v<T>;
template <typename T>
requires FloatOrInt<T> // Benutzung identisch
T add(T sum1, T sum2) {
return sum1 + sum2;
}
class Foo {};
int main() {
cout << add<int>(3,4) << endl;
cout << add<float>(2.5, 1.3) << endl;
// Error: constraints not satisfied
// cout << add<Foo>(Foo(), Foo()) << endl;
}
std::derived_from<X,Y>; // Teil des Headers <concepts>std::vector (Verdoppelung des alten Werts)vtable kostet Speicher, Rechenzeitstatic_cast wählt statisch die Kind-Implementierungvtable und kein dynamisches Binden mehr#include <iostream>
template <typename T>
struct Shape {
void draw() const {
static_cast<const T&>(*this).drawImpl();
}
};
struct Circle : Shape<Circle> {
void drawImpl() const {
std::cout << "Circle\n";
}
};
struct Square : Shape<Square> {
void drawImpl() const {
std::cout << "Square\n";
}
};
int main() {
Circle c;
Square s;
c.draw();
s.draw();
}