Function template yang kita tulis selama ini punya jumlah parameter yang pasti: satu, dua, tiga. Padahal kebutuhan nyata sering berbeda — std::format menerima jumlah argumen apa pun, std::make_unique<Sensor>(5, "dht", 0.5) juga begitu. Variadic template adalah fitur yang membuat hal itu mungkin: template dengan parameter pack, yaitu daftar tipe dan argumen yang panjangnya ditentukan pemanggil — nol, satu, atau dua belas. Chapter ini membangun pola klasiknya: memecah pack menjadi “satu argumen sekarang, sisanya nanti” lewat rekursi, yang menolong kita memahami mekanisme sebelum digantikan alat yang lebih ringkas.

Parameter Pack: Argumen dengan Panjang Bebas #

Tanda ... adalah kuncinya. typename... Rest mendeklarasikan template parameter pack — bukan satu tipe, melainkan deretan tipe. const Rest&... sisa lalu mendeklarasikan function parameter pack yang isinya nyata. Dua contoh di bawah memakai keduanya, ditambah sizeof...(Args) yang menghitung isi pack tanpa menjalankan satu baris kode pun saat runtime:

cpp
#include <iostream>

// Base case: fungsi biasa tanpa template, menghentikan rekursi
void cetakSemua() {
    std::cout << "selesai\n";
}

// Recursive case: satu argumen diambil, sisanya tetap berupa pack
template <typename T, typename... Rest>
void cetakSemua(const T& pertama, const Rest&... sisa) {
    std::cout << pertama << " ";
    cetakSemua(sisa...);
}

// sizeof...(Args): jumlah elemen pack diketahui pada saat kompilasi
template <typename... Args>
void laporkan() {
    std::cout << "jumlah argumen: " << sizeof...(Args) << "\n";
}

int main() {
    cetakSemua(1, 2.5, "tiga", 'x');
    laporkan<int, const char*, double>();
    laporkan();

    return 0;
}

Output yang Dihasilkan:

text
1 2.5 tiga x selesai
jumlah argumen: 3
jumlah argumen: 0

Bedah baris demi baris:

  1. Baris 4-6 (void cetakSemua()): base case — fungsi biasa tanpa template untuk nol argumen. Tanpa pemberhenti ini, rekursi tidak akan punya ujung dan kompilasi berhenti dengan “no matching function”.
  2. Baris 9-10 (template <typename T, typename... Rest>): pola bacaan pentingnya — satu parameter biasa dulu (T), lalu pack. Wajib: pemanggilan dengan satu argumen saja akan menduksi T dan membiarkan Rest kosong.
  3. Baris 11-12: pertama dicetak seperti variabel biasa; cetakSemua(sisa...) adalah pack expansion — pack dibuka, dan setiap elemennya menjadi satu argumen pada pemanggilan berikutnya. Argumen mengempis satu per satu.
  4. Baris 16-18 (sizeof...(Args)): operator khusus pack yang menghasilkan konstanta compile time. laporkan<int, const char*, double>() pada baris 23 lapor 3; baris 24 tanpa argumen lapor 0 — pack kosong itu sah.
  5. Baris 22: pemanggilan dengan empat argumen tipe campur; compiler yang menyusun rantai instansiasinya, bukan kita.

Melacak Rekursinya #

Untuk cetakSemua(1, 2.5, "tiga", 'x'), compiler menginstansiasi empat langkah berurutan — semua terjadi saat kompilasi:

  1. cetakSemua<int, double, const char*, char>(1, 2.5, "tiga", 'x') → cetak 1, panggil dengan sisa tiga argumen.
  2. cetakSemua<double, const char*, char>(2.5, "tiga", 'x') → cetak 2.5, panggil dengan sisa dua.
  3. cetakSemua<const char*, char>("tiga", 'x') → cetak tiga, panggil dengan sisa satu.
  4. cetakSemua<char>('x') → cetak x, panggil dengan nol argumen.
  5. cetakSemua() (base case) → cetak selesai.

Setiap langkah adalah fungsi nyata yang dihasilkan compiler, lengkap dengan tipe yang presisi. Tidak ada void*, tidak ada konversi, tidak ada array dinamis — inilah keunggulan variadic template dibanding cara C lama seperti printf, yang menerima argumen bebas tetapi kehilangan tipe.

Pratinjau: Fold Expressions #

Pola rekursi di atas jujur saja panjang: dua fungsi, satu base case, satu langkah rekursif. C++17 merampingkannya menjadi satu baris lewat fold expression:

cpp Cetakan satu baris dengan fold (pratinjau C++17)
template <typename... Args>
void cetakSemua(const Args&... args) {
    (std::cout << ... << args);  // binary left fold: sambung semua argumen
}

Satu ekspresi menggantikan seluruh rantai rekursi — compiler sendiri yang menulis “pengulangannya”. Karena topik ini cukup luas (empat bentuk fold, nilai awal, batasannya), kita beri chapter tersendiri: Fold Expressions tepat setelah dua chapter berikutnya.

Masih ada yang perlu diluruskan sebelum sampai ke sana: expansion sisa... di atas adalah bentuk paling sederhana. Sebenarnya pola di kiri ... bisa jauh lebih kaya — panggilan fungsi, ekspresi, bahkan pemanggilan method — dan inilah topik Chapter Parameter Pack: anatomi expansion pattern beserta contoh nyatanya di emplace_back.

Referensi resmi: cppreference — Parameter pack dan cppreference — Fold expression .