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:
#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:
1 2.5 tiga x selesai
jumlah argumen: 3
jumlah argumen: 0Bedah baris demi baris:
- 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”. - 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 menduksiTdan membiarkanRestkosong. - Baris 11-12:
pertamadicetak seperti variabel biasa;cetakSemua(sisa...)adalah pack expansion — pack dibuka, dan setiap elemennya menjadi satu argumen pada pemanggilan berikutnya. Argumen mengempis satu per satu. - 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. - 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:
cetakSemua<int, double, const char*, char>(1, 2.5, "tiga", 'x')→ cetak1, panggil dengan sisa tiga argumen.cetakSemua<double, const char*, char>(2.5, "tiga", 'x')→ cetak2.5, panggil dengan sisa dua.cetakSemua<const char*, char>("tiga", 'x')→ cetaktiga, panggil dengan sisa satu.cetakSemua<char>('x')→ cetakx, panggil dengan nol argumen.cetakSemua()(base case) → cetakselesai.
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:
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 .