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 #

KomponenSintaksContoh
Function templatetemplate<typename T> T f(...)max<T>(a, b)
Class templatetemplate<typename T> class CStack<int>
Non-type parametertemplate<size_t N>FixedBuffer<8>
Variadic templatetemplate<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 T tidak mendukung operasi yang dipakai (mis. T tanpa operator>), kompilasi gagal dengan error panjang.
  • Setiap kombinasi tipe menghasilkan kode terpisah → binary bloat bila banyak tipe berbeda.

Kode: Delapan Demo Template #

cpp
#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:

text
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)  = 7

Bedah Kode Baris-per-Baris #

  1. template <typename T> T my_max(T a, T b): function template dengan satu parameter tipe T. Saat dipanggil my_max(3, 7), compiler menurunkan T = int dari argumen. my_max(2.5, 1.5) → T = double. my_max<std::string>(...) menentukan tipe eksplisit.
  2. 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 via decltype(a + b) — bila int + double menghasilkan double, fungsi mengembalikan double. Output 7.5.
  3. template <size_t N> struct FixedBuffer: parameter non-tipe N (konstanta compile-time). FixedBuffer<8> membuat struct dengan int data[8]. size() mengembalikan 8 — constexpr memungkinkan penggunaan sebagai constant expression.
  4. template <typename T> class Stack: class template generik. Stack<int> dan Stack<std::string> adalah dua tipe berbeda — masing-masing menghasilkan kode terpisah.
  5. void push(const T& x) dan void push(T&& x): dua overload — copy dan move. Move overload menghindari copy untuk argumen rvalue.
  6. template <> class Stack<bool>: full specialization untuk bool. Menggunakan std::vector<bool> yang bit-packed (8× lebih kecil dari vector<char>). Saat Stack<bool> di-instantiate, compiler memilih versi khusus ini, bukan template utama.
  7. void print() (base case) + template<typename T, typename... Rest> print(...): variadic template. Template rekursif: cetak first, lalu rekursif ke print(rest...). Saat paket parameter kosong, base case dipanggil. sizeof...(rest) menghitung jumlah argumen tersisa.
  8. if constexpr (sizeof...(rest) > 0): if constexpr (C++17) — branch dievaluasi saat kompilasi. Bila sizeof...(rest) == 0, kode std::cout << ", "; tidak di-generate sama sekali → tidak ada output koma setelah elemen terakhir.
  9. 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 bila cond true; bila false, substitusi gagal dan overload ini dihapus dari kandidat. safe_abs(3.14) tidak memiliki kandidat valid → compile error.
  10. safe_abs(-5) dan safe_abs(7L): int dan long adalah integral → overload valid. double bukan integral → ditolak.

Template Specialization #

Dua jenis:

  1. Full specialization: semua parameter ditentukan. template <> class Stack<bool> { ... }; — versi khusus untuk bool.
  2. Partial specialization: sebagian parameter ditentukan. template <typename T> class Stack<T*> { ... }; — khusus untuk pointer.
cpp
// 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:

cpp
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 #

cpp
template <typename T, typename Compare = std::less<T>>
class PriorityQueue { /* ... */ };

Algorithm Generik #

cpp
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 #

cpp
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.