Templates sind Vorlagen für Funktionen und Klassen, die für beliebige Typen funktionieren. Der Compiler erzeugt aus ihnen beim Übersetzen den passenden Code, ohne Laufzeit-Overhead. Die Standardbibliothek (vector<T>, map<K,V>, sort) besteht aus Templates.
Funktions-Templates
#include <iostream>
#include <string>
template <typename T>
T groesser(T a, T b) { return a > b ? a : b; }
template <typename T, typename U>
auto addiere(T a, U b) { return a + b; }
int main() {
std::cout << groesser(3, 9) << ' ' << groesser(2.5, 1.5) << ' ' << groesser(std::string("a"), std::string("b")) << '\n';
std::cout << addiere(1, 2.5) << ' ' << addiere(std::string("Ha"), "llo") << '\n';
std::cout << groesser<double>(3, 2.5) << '\n'; // Typ explizit angeben
return 0;
}Ausgabe
9 2.5 b 3.5 Hallo 3
Der Compiler erkennt T aus den Argumenten. Bei groesser(3, 2.5) würde das scheitern (zwei verschiedene Typen), dafür gibt es groesser<double>.
Klassen-Templates
#include <iostream>
#include <vector>
#include <stdexcept>
template <typename T>
class Stapel {
public:
void push(const T& x) { daten_.push_back(x); }
T pop() {
if (daten_.empty()) throw std::out_of_range("Stapel leer");
T x = daten_.back();
daten_.pop_back();
return x;
}
bool leer() const { return daten_.empty(); }
std::size_t groesse() const { return daten_.size(); }
private:
std::vector<T> daten_;
};
template <typename K, typename V>
struct Paar { K erstes; V zweites; };
int main() {
Stapel<int> s;
s.push(1); s.push(2); s.push(3);
std::cout << s.pop() << s.pop() << ' ' << s.groesse() << '\n';
Stapel<std::string> t;
t.push("a");
try { t.pop(); t.pop(); } catch (const std::out_of_range& e) { std::cout << e.what() << '\n'; }
Paar<std::string, int> p{"Alter", 17}; // Typen werden erkannt (CTAD)
std::cout << p.erstes << '=' << p.zweites << '\n';
return 0;
}Ausgabe
32 1 Stapel leer Alter=17
Concepts: Anforderungen an Typen (C++20)
Ohne Hilfe liefern falsche Template-Argumente riesige, schwer lesbare Fehlermeldungen. Concepts beschreiben, was ein Typ können muss:
#include <iostream>
#include <concepts>
#include <vector>
#include <string>
template <typename T>
concept Zahl = std::integral<T> || std::floating_point<T>;
template <Zahl T>
T quadrat(T x) { return x * x; }
template <typename T>
concept HatGroesse = requires(const T& t) { { t.size() } -> std::convertible_to<std::size_t>; };
template <HatGroesse C>
std::size_t laenge(const C& c) { return c.size(); }
auto verdopple(std::integral auto x) { return x * 2; } // Kurzschreibweise
int main() {
std::cout << quadrat(5) << ' ' << quadrat(1.5) << '\n';
std::cout << laenge(std::vector<int>{1, 2, 3}) << ' ' << laenge(std::string("Hallo")) << '\n';
std::cout << verdopple(21) << '\n';
// quadrat("text"); // Fehler: "text" erfüllt Zahl nicht - mit klarer Meldung
return 0;
}Ausgabe
25 2.25 3 5 42
Fertige Concepts gibt es in <concepts> (std::integral, std::same_as, std::totally_ordered, ...) und für Bereiche in <ranges>.
Variadische Templates und Fold Expressions
#include <iostream>
template <typename... Args>
auto summe(Args... args) { return (args + ...); } // Fold Expression (C++17)
template <typename... Args>
void ausgeben(const Args&... args) { ((std::cout << args << ' '), ...); std::cout << '\n'; }
int main() {
std::cout << summe(1, 2, 3, 4) << ' ' << summe(1.5, 2.5) << '\n';
ausgeben("a", 1, 2.5, 'c');
return 0;
}Ausgabe
10 4 a 1 2.5 c
Template-Spezialisierung
#include <iostream>
#include <string>
template <typename T>
std::string beschreibe(const T&) { return "irgendwas"; }
template <>
std::string beschreibe<int>(const int& x) { return "int " + std::to_string(x); } // Spezialfall
template <typename T>
std::string typ() {
if constexpr (std::is_integral_v<T>) return "Ganzzahl"; // Auswahl zur Übersetzungszeit
else if constexpr (std::is_floating_point_v<T>) return "Kommazahl";
else return "anderes";
}
int main() {
std::cout << beschreibe(5) << ", " << beschreibe(2.5) << '\n';
std::cout << typ<int>() << ' ' << typ<double>() << ' ' << typ<std::string>() << '\n';
return 0;
}Ausgabe
int 5, irgendwas Ganzzahl Kommazahl anderes
if constexpr entscheidet beim Übersetzen, welcher Zweig existiert. Die nicht gewählten Zweige müssen nicht einmal übersetzbar sein.
Templates in Header-Dateien
Der Compiler braucht den vollständigen Code eines Templates an der Stelle, wo es benutzt wird. Deshalb stehen Templates (Deklaration und Umsetzung) in Header-Dateien (.h/.hpp), nicht in .cpp.
Merke
template <typename T>macht Funktionen und Klassen für viele Typen nutzbar, ohne Laufzeitkosten- Der Compiler erkennt Typen aus Argumenten; sonst
f<int>(...)explizit - Concepts (
requires,std::integral) formulieren Anforderungen und liefern klare Fehlermeldungen - Variadische Templates + Fold Expressions verarbeiten beliebig viele Argumente
if constexprwählt Code zur Übersetzungszeit; Templates gehören in Header
Aufgabe
Schreibe ein Template mittelwert für beliebige Container von Zahlen, eingeschränkt mit einem Concept.