Template adalah mekanisme C++ untuk menulis kode generik — satu definisi yang bekerja untuk banyak tipe. Sebuah template <typename T> T max(T a, T b) bekerja untuk int, double, std::string, atau tipe apa pun yang mendukung operator>. Template di-instantiate saat kompilasi: setiap tipe baru menghasilkan kode mesin berbeda. Pada Chapter ini kita akan membedah function template, class template, non-type parameter, variadic template, dan dua teknik lanjutan — template specialization dan SFINAE — yang memungkinkan pemilihan implementasi berdasarkan sifat tipe.
Empat Komponen Template #
| Komponen | Sintaks | Contoh |
|---|---|---|
| Function template | template<typename T> T f(...) | max<T>(a, b) |
| Class template | template<typename T> class C | Stack<int> |
| Non-type parameter | template<size_t N> | FixedBuffer<8> |
| Variadic template | template<typename... Ts> | print(1, 2.5, "x") |
Mekanisme: Instantiation Saat Kompilasi #
Saat compiler melihat my_max(3, 7), ia menurunkan tipe T = int dari argumen lalu menghasilkan instance int my_max(int, int) — kode mesin khusus untuk int. Panggilan my_max(2.5, 1.5) menghasilkan instance double my_max(double, double). Kedua instance adalah fungsi terpisah di binary akhir.
Konsekuensi:
- Kode template harus tersedia di header (visibility saat kompilasi).
- Bila tipe
Ttidak mendukung operasi yang dipakai (mis.Ttanpaoperator>), kompilasi gagal dengan error panjang. - Setiap kombinasi tipe menghasilkan kode terpisah → binary bloat bila banyak tipe berbeda.
Kode: Delapan Demo Template #
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
#include <type_traits>
// 1) Function template
template <typename T>
T my_max(T a, T b) {
return a > b ? a : b;
}
// 2) Multiple type parameters
template <typename T, typename U>
auto add(const T& a, const U& b) -> decltype(a + b) {
return a + b;
}
// 3) Non-type template parameter
template <size_t N>
struct FixedBuffer {
int data[N];
constexpr size_t size() const { return N; }
};
// 4) Class template
template <typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& x) { data_.push_back(x); }
void push(T&& x) { data_.push_back(std::move(x)); }
T pop() {
T v = std::move(data_.back());
data_.pop_back();
return v;
}
bool empty() const { return data_.empty(); }
size_t size() const { return data_.size(); }
};
// 5) Template specialization — versi khusus untuk bool
template <>
class Stack<bool> {
std::vector<bool> data_; // bit-packed!
public:
void push(bool x) { data_.push_back(x); }
bool pop() {
bool v = data_.back();
data_.pop_back();
return v;
}
bool empty() const { return data_.empty(); }
};
// 6) Variadic template — print semua argumen
void print() {} // base case
template <typename T, typename... Rest>
void print(const T& first, const Rest&... rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) std::cout << ", ";
print(rest...);
}
// 7) Concepts (C++20) — constraint template
// (kode konsep ditampilkan tanpa kompilasi concept untuk compat C++17)
// 8) SFINAE — enable_if
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T safe_abs(T x) {
return x < 0 ? -x : x;
}
int main() {
// Function template
std::cout << "max(3, 7) = " << my_max(3, 7) << '\n';
std::cout << "max(2.5, 1.5) = " << my_max(2.5, 1.5) << '\n';
std::cout << "max(\"abc\", \"abd\") = " << my_max<std::string>("abc", "abd") << '\n';
// Multiple types
std::cout << "add(3, 4.5) = " << add(3, 4.5) << '\n';
std::cout << "add(2L, 3.14) = " << add(2L, 3.14) << '\n';
// Non-type param
FixedBuffer<8> buf;
buf.data[0] = 99;
std::cout << "buf.size() = " << buf.size() << ", buf.data[0] = " << buf.data[0] << '\n';
// Class template
Stack<int> s;
s.push(1); s.push(2); s.push(3);
std::cout << "stack pop: " << s.pop() << ' ' << s.pop() << '\n';
Stack<bool> bs;
bs.push(true); bs.push(false); bs.push(true);
std::cout << "bool stack pop: " << bs.pop() << ' ' << bs.pop() << '\n';
// Variadic template
std::cout << "variadic: ";
print(1, 2.5, "hello", 'x');
std::cout << '\n';
// SFINAE
std::cout << "safe_abs(-5) = " << safe_abs(-5) << '\n';
std::cout << "safe_abs(7L) = " << safe_abs(7L) << '\n';
// safe_abs(3.14); // ERROR: enable_if menolak non-integral
return 0;
}Output yang Dihasilkan:
max(3, 7) = 7
max(2.5, 1.5) = 2.5
max("abc", "abd") = abd
add(3, 4.5) = 7.5
add(2L, 3.14) = 5.14
buf.size() = 8, buf.data[0] = 99
stack pop: 3 2
bool stack pop: 1 0
variadic: 1, 2.5, hello, x
safe_abs(-5) = 5
safe_abs(7L) = 7Bedah Kode Baris-per-Baris #
template <typename T> T my_max(T a, T b): function template dengan satu parameter tipeT. Saat dipanggilmy_max(3, 7), compiler menurunkanT = intdari argumen.my_max(2.5, 1.5)→T = double.my_max<std::string>(...)menentukan tipe eksplisit.template <typename T, typename U> auto add(const T& a, const U& b) -> decltype(a + b): dua parameter tipe berbeda.add(3, 4.5)→T = int,U = double. Return type dideduksi viadecltype(a + b)— bilaint + doublemenghasilkandouble, fungsi mengembalikandouble. Output7.5.template <size_t N> struct FixedBuffer: parameter non-tipeN(konstanta compile-time).FixedBuffer<8>membuat struct denganint data[8].size()mengembalikan8—constexprmemungkinkan penggunaan sebagai constant expression.template <typename T> class Stack: class template generik.Stack<int>danStack<std::string>adalah dua tipe berbeda — masing-masing menghasilkan kode terpisah.void push(const T& x)danvoid push(T&& x): dua overload — copy dan move. Move overload menghindari copy untuk argumen rvalue.template <> class Stack<bool>: full specialization untukbool. Menggunakanstd::vector<bool>yang bit-packed (8× lebih kecil darivector<char>). SaatStack<bool>di-instantiate, compiler memilih versi khusus ini, bukan template utama.void print()(base case) +template<typename T, typename... Rest> print(...): variadic template. Template rekursif: cetakfirst, lalu rekursif keprint(rest...). Saat paket parameter kosong, base case dipanggil.sizeof...(rest)menghitung jumlah argumen tersisa.if constexpr (sizeof...(rest) > 0):if constexpr(C++17) — branch dievaluasi saat kompilasi. Bilasizeof...(rest) == 0, kodestd::cout << ", ";tidak di-generate sama sekali → tidak ada output koma setelah elemen terakhir.template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>: SFINAE — Substitution Failure Is Not An Error.std::enable_if_t<cond>hanya valid bilacondtrue; bila false, substitusi gagal dan overload ini dihapus dari kandidat.safe_abs(3.14)tidak memiliki kandidat valid → compile error.safe_abs(-5)dansafe_abs(7L):intdanlongadalah integral → overload valid.doublebukan integral → ditolak.
Template Specialization #
Dua jenis:
- Full specialization: semua parameter ditentukan.
template <> class Stack<bool> { ... };— versi khusus untukbool. - Partial specialization: sebagian parameter ditentukan.
template <typename T> class Stack<T*> { ... };— khusus untuk pointer.
// Partial: untuk tipe pointer
template <typename T>
class Stack<T*> {
std::vector<T*> data_;
// ... bisa menambahkan method khusus pointer
};Specialization memungkinkan optimisasi per-kategori tipe (mis. vector<bool> bit-packed) tanpa mengubah API.
Variadic Template: Fold Expression (C++17) #
C++17 menambahkan fold expression — jauh lebih ringkas dari rekursi:
template <typename... Ts>
auto sum_all(Ts... args) {
return (args + ...); // fold: a1 + a2 + a3 + ...
}
// sum_all(1, 2, 3, 4) == 10
Empat bentuk fold: unary (... op args), (args op ...), binary (init op ... op args), (args op ... op init).
Pola Umum di DSA #
Generic Container #
template <typename T, typename Compare = std::less<T>>
class PriorityQueue { /* ... */ };Algorithm Generik #
template <typename It, typename T>
It find(It first, It last, const T& value) {
while (first != last && *first != value) ++first;
return first;
}Type Traits untuk Konstrain #
template <typename T>
void must_be_integer(T x) {
static_assert(std::is_integral_v<T>, "T must be integral");
// ...
}Ringkasan #
- Empat komponen: function template, class template, non-type parameter, variadic template.
- Template di-instantiate saat kompilasi — kode mesin terpisah per tipe.
- Specialization (full/partial) memungkinkan versi khusus per tipe atau kategori.
- Variadic template + fold expression (C++17) menggantikan rekursi untuk paket parameter.
- SFINAE (
enable_if) dan concepts (C++20) membatasi tipe yang valid → API lebih aman, error lebih jelas. - Untuk DSA: pakai template untuk container/algorithm generik. Hindari kompleksitas berlebih.