Selama ini kita hanya memakai satu bentuk parameter template: typename T. Padahal daftar di dalam template <...> jauh lebih kaya. Ada kata kunci saingan bernama class, ada kemampuan memberi nilai bawaan seperti parameter fungsi, dan — paling menarik — ada parameter yang berisi template lain. Chapter ini merapikan ketiganya dengan satu file contoh, karena ketiganya akan muncul terus-menerus di standard library: std::vector memakai default argument, dan container adaptor seperti std::stack memakai template template parameter di deklarasi aslinya.

typename atau class: Dua Kata, Satu Makna #

Kedua kata kunci berarti sama persis dalam daftar parameter template. Baris 6-9 di bawah memakai template <class T> dan berperilaku identik dengan template <typename T> — tidak ada perbedaan semantik, tidak ada perbedaan performa. T di sana bukan class dalam arti OOP; ia bisa int, double, enum, pointer, tipe apa pun. class di posisi ini hanyalah warisan sejarah: kata itu lebih dulu ada, typename ditambahkan belakangan karena class terasa menyesatkan untuk tipe non-class.

cpp
#include <iostream>
#include <string>
#include <vector>

// "class" dalam daftar parameter template identik dengan "typename"
template <class T>
T kuadrat(T x) {
    return x * x;
}

// Default template argument: bila Container tak disebut, pakai std::vector<T>
template <typename T, typename Container = std::vector<T>>
class Tumpukan {
public:
    void taruh(const T& nilai) { data_.push_back(nilai); }
    T ambil() {
        T nilaiAtas = data_.back();
        data_.pop_back();
        return nilaiAtas;
    }
    bool kosong() const { return data_.empty(); }

private:
    Container data_;
};

// Template dengan satu parameter tipe, siap diselipkan ke template lain
template <typename T>
class Kotak {
public:
    explicit Kotak(const T& isi) : isi_{isi} {}
    const T& isi() const { return isi_; }

private:
    T isi_;
};

template <typename T>
class LabelHarga {
public:
    explicit LabelHarga(const T& nilai) : nilai_{nilai} {}
    const T& isi() const { return nilai_; }

private:
    T nilai_;
};

// Template template parameter: C adalah template lain (satu parameter tipe)
template <typename T, template <typename> class C = Kotak>
class Penyimpan {
public:
    explicit Penyimpan(const T& isi) : kotak_{isi} {}
    void tampilkan() const { std::cout << "disimpan: " << kotak_.isi() << "\n"; }

private:
    C<T> kotak_;
};

int main() {
    std::cout << "kuadrat(6) = " << kuadrat(6) << "\n";

    Tumpukan<std::string> tumpukan;  // Container default: std::vector<std::string>
    tumpukan.taruh("Budi");
    tumpukan.taruh("Siti");
    std::cout << "ambil: " << tumpukan.ambil() << "\n";

    Penyimpan<int> bawaan{7};          // C default: Kotak
    bawaan.tampilkan();

    Penyimpan<int, LabelHarga> lain{99};  // C diganti: LabelHarga
    lain.tampilkan();

    return 0;
}

Output yang Dihasilkan:

text
kuadrat(6) = 36
ambil: Siti
disimpan: 7
disimpan: 99

Bedah singkatnya:

  1. Baris 6-9 (template <class T> T kuadrat): satu-satunya tempat class dipakai sebagai pengganti typename di file ini. Konvensi komunitas modern cenderung memakai typename karena lebih jujur — ikuti saja satu gaya secara konsisten dalam satu proyek.
  2. Baris 12 (typename Container = std::vector<T>): parameter kedua punya nilai bawaan. Pemanggilan Tumpukan<std::string> pada baris 62 tidak menyebut Container, sehingga compiler mengisinya dengan std::vector<std::string>.
  3. Baris 49 (template <typename T, template <typename> class C = Kotak>): C adalah template template parameter — argumennya bukan tipe, melainkan template lain yang punya tepat satu parameter tipe. Baris 56 memakainya sebagai C<T>.
  4. Baris 70 (Penyimpan<int, LabelHarga>): Kotak diganti LabelHarga. Selama template penggantinya punya satu parameter tipe dan menyediakan isi(), baris 53 tetap dikompilasi — inilah cikal bakal pola policy-based design yang kita tulis di akhir Section.

Karena Tumpukan diinstansiasi dengan std::string, urutan taruh lalu ambil pada baris 63-65 menghasilkan perilaku LIFO: nilai terakhir yang masuk (“Siti”) keluar duluan.

Default Template Argument #

Nilai bawaan pada parameter template bekerja seperti default argument pada fungsi: bila pemanggil tidak mengisi, compiler mengisi. Tiga aturan praktisnya cukup diingat:

  • Nilai bawaan boleh bergantung pada parameter sebelumnya — std::vector<T> memakai T yang dideklarasikan di kiriannya.
  • Setelah satu parameter berisi nilai bawaan, semua parameter sesudahnya sebaiknya juga punya nilai bawaan; pemanggil tidak bisa “melewati” parameter tengah karena argumen diisi berurutan.
  • Standard library memakai pola ini di mana-mana: std::vector<T, Allocator = std::allocator<T>> dan std::basic_string<CharT, Traits, Allocator> semuanya menyembunyikan parameter teknis di belakang nilai bawaan.

Satu catatan penting bila kita ingin meniru std::vector di posisi parameter template yang lain: std::vector punya dua parameter (tipe dan allocator), sehingga template template parameter yang memintanya harus menerima dua parameter pula. Detail pencocokannya tergolong lanjutan; cukup tahu bahwa container standard “sedikit lebih gemuk” daripada Kotak buatan kita.

Template Template Parameter: Template sebagai Argumen #

Mengapa fitur ini perlu ada? Karena kadang kita ingin menyuruh class: “pakai jenis penyimpanan ini” tanpa menyebut tipenya terlebih dahulu. Pada Penyimpan<int, Kotak>, keputusan cara menyimpan (Kotak) dipisahkan dari keputusan apa yang disimpan (int). Kalau kita hanya pakai parameter tipe biasa, kita terpaksa menulis Penyimpan<Kotak<int>> — tipe penyimpanan dan isi sudah dilekatkan sejak awal, dan class kita tidak bisa mengambil T sendiri.

Deklarasi template <typename> class C membaca: “C adalah nama yang merujuk template class dengan satu parameter tipe”. Di dalam badan class, C<T> menginstansiasi template itu dengan tipe kita. Pola ini persis yang dipakai std::stack: deklarasi aslinya berbunyi template <class T, class Container = std::deque<T>> class stack; — isi tumpukan diserahkan ke container lain yang bisa diganti pemakainya.

Dengan tipe, nilai bawaan, dan template sebagai parameter, daftar template <...> masih menyisakan satu jenis parameter yang belum kita sentuh: parameter yang isinya angka, bukan tipe. Chapter berikutnya membahas non-type template parameter — mesin di balik std::array<int, 5> — dan cara compiler memperlakukan nilai pada saat kompilasi.

Referensi resmi: cppreference — Template parameters and arguments dan cppreference — std::stack .