Chapter sebelumnya memperkenalkan parameter pack lewat pola rekursi. Sekarang kita perhalus sisi yang paling sering dipakai di kode nyata: pack expansion — bagaimana compiler “membuka” pack dan menyalin pola yang kita tulis untuk setiap elemennya. Pola yang diperluas tidak harus sekadar nama variabel; bisa berupa pemanggilan fungsi, ekspresi, atau kombinasi keduanya. Kemampuan inilah yang dipakai emplace_back, std::format, dan berbagai fungsi standard library untuk meneruskan argumen bebas jumlahnya ke tujuan akhirnya.
Expansion Pattern: Apa yang Sebenarnya Diperluas #
Aturan dasarnya satu kalimat: tulis pola untuk satu elemen, lalu tempel ... di kanannya — compiler mengulangi pola itu untuk setiap elemen pack, dipisah koma. Beberapa bentuk yang paling sering muncul:
| Pola | Hasil untuk pack (10, "dua", 3.5) |
|---|---|
args... | 10, "dua", 3.5 — diteruskan apa adanya |
prosesSatu(args)... | prosesSatu(10), prosesSatu("dua"), prosesSatu(3.5) |
(args, 0)... | (10, 0), ("dua", 0), (3.5, 0) — hasil tiap elemen 0 |
std::forward<Args>(args)... | setiap elemen diteruskan dengan menjaga kategori nilainya |
Contoh di bawah memakai pola pemanggilan fungsi: satu wrapper menerima pack, lalu menyalurkan setiap elemen ke prosesSatu tanpa menulis rekursi.
#include <iostream>
#include <string>
// Fungsi target: menerima satu nilai
template <typename T>
void prosesSatu(const T& nilai) {
std::cout << "proses: " << nilai << "\n";
}
// Wrapper: memforward pack ke fungsi target, satu panggilan per elemen
template <typename... Args>
void prosesSemua(const Args&... args) {
// Expansion pattern: prosesSatu(args) diulang untuk setiap elemen pack
[[maybe_unused]] int penanda[] = { (prosesSatu(args), 0)... };
}
int main() {
prosesSemua(10, "dua puluh", 3.5);
return 0;
}Output yang Dihasilkan:
proses: 10
proses: dua puluh
proses: 3.5Bedah baris demi baris:
- Baris 12 (
const Args&... args): pack di daftar parameter fungsi. Untuk pemanggilan pada baris 18,Argsdiduksi menjadi{int, const char*, double}danargsberisi tiga rujukan konstan. - Baris 14 (
(prosesSatu(args), 0)...): polanya adalah ekspresi koma — panggilprosesSatuuntuk satu elemen, lalu hasilkan0sebagai nilainya. Dengan...di ujung, compiler menulis ulang seluruh pola menjadi{ (prosesSatu(10), 0), (prosesSatu("dua puluh"), 0), (prosesSatu(3.5), 0) }. - Baris 14 juga menjelaskan wadahnya: ekspresi pola butuh context yang menerima daftar berpisah koma; daftar inisialisasi array adalah context klasiknya. Nilainya sendiri tidak dipakai —
[[maybe_unused]]menenangkan compiler. - Urutan terjamin: di dalam daftar inisialisasi kurung kurawal, setiap elemen dievaluasi kiri-ke-kanan, sehingga tiga baris
proses:muncul persis dalam urutan argumen.
Memforward Pack ke Fungsi Lain #
prosesSemua di atas adalah contoh forwarding: fungsi perantara yang tidak memproses apa pun sendiri, hanya meneruskan isi pack ke fungsi lain. Bentuk paling sederhananya f(args)...; bentuk paling lengkapnya f(std::forward<Args>(args)...) — versi yang menjaga sifat rujukan setiap argumen, dikenal sebagai perfect forwarding. Kita tinggalkan detailnya ke Section Move Semantics dan Value Categories; untuk sekarang cukup pegang bentuknya, karena signature emplace_back yang segera kita bongkar persis memakai pola itu.
Perlu jujur diakui: trik array int penanda[] adalah warisan pra-C++17 yang masih sah, tetapi terlihat rancu. C++17 memberi ganti yang bersih — fold expression — dan seluruh pola koma ini bisa dipangkas jadi satu baris. Itu judul chapter berikutnya.
Contoh Nyata: emplace_back #
emplace_back adalah pengguna parameter pack paling terkenal di standard library. Signature aslinya berbunyi:
template <class... Args>
reference emplace_back(Args&&... args);Bacaannya: terima argumen apa saja, teruskan semuanya ke constructor tipe elemen, dan bangun object langsung di dalam memori vector — tanpa object sementara yang kemudian disalin. Program berikut membuktikannya dengan jejak constructor:
#include <iostream>
#include <string>
#include <vector>
class Siswa {
public:
Siswa(std::string nama, int umur) : nama_{std::move(nama)}, umur_{umur} {
std::cout << "constructor Siswa untuk " << nama_ << "\n";
}
const std::string& nama() const { return nama_; }
int umur() const { return umur_; }
private:
std::string nama_;
int umur_;
};
int main() {
std::vector<Siswa> daftar;
daftar.reserve(2);
// emplace_back meneruskan seluruh argumen ke constructor Siswa
daftar.emplace_back("Budi", 17);
daftar.emplace_back("Siti", 16);
for (const Siswa& s : daftar) {
std::cout << s.nama() << " (" << s.umur() << " tahun)\n";
}
return 0;
}Output yang Dihasilkan:
constructor Siswa untuk Budi
constructor Siswa untuk Siti
Budi (17 tahun)
Siti (16 tahun)Yang patut diperhatikan:
- Baris 7-9: constructor mencetak jejak setiap kali dijalankan. Hanya dua baris “constructor Siswa” muncul — satu per
emplace_back— membuktikan tidak ada penyalinan object sementara. - Baris 24-25:
emplace_back("Budi", 17)mengirim dua argumen dengan tipe berbeda; parameter pack di dalam vector yang menerimanya.push_backsetara harus diisi satu objectSiswayang sudah jadi, sedangkanemplace_backmengirim bahan bakunya. - Baris 7 lagi:
std::move(nama)memindahkan string ke data member tanpa menyalin isinya — detail yang dibedah di Section Move Semantics. - Baris 21:
reserve(2)memesan kapasitas lebih dulu agar tidak ada alokasi ulang yang mengejutkan; kebiasaan baik saat jumlah elemen sudah diketahui.
Dengan pack dan pola perluasannya terkuasai, potongan yang tersisa kecil namun berdampak besar: bentuk penjumlahan dan pemanggilan berantai yang dulu butuh rekursi kini jadi satu ekspresi. Chapter Fold Expressions menutup trilogi variadic ini.
Referensi resmi: cppreference — Parameter pack dan cppreference — vector::emplace_back .