Pola rekursi pada Chapter Variadic Templates bekerja, tetapi harganya jelas: dua fungsi, base case, dan rantai instansiasi yang menumpuk. C++17 menawarkan jalan pintas bernama fold expression — satu ekspresi berkurung yang menyuruh compiler melipat satu operator ke seluruh isi pack, kiri-ke-kanan atau sebaliknya. Penjumlahan, pencetakan berantai, pengujian logika, pemanggilan fungsi beruntun: semuanya menyusut menjadi satu baris. Chapter ini memetakan keempat bentuknya lalu mempraktikkannya sebagai pengganti langsung rekursi kita.
Empat Bentuk Fold #
Semua fold ditulis di dalam tanda kurung, dengan pack atau pola berisi pack di salah satu sisi operator. Tabel berikut merangkum bentuknya untuk pack (a, b, c):
| Bentuk | Sintaks | Hasil perluasan untuk (a, b, c) |
|---|---|---|
| Unary left fold | (... op E) | ((a op b) op c) |
| Unary right fold | (E op ...) | (a op (b op c)) |
| Binary left fold | (init op ... op E) | (((init op a) op b) op c) |
| Binary right fold | (E op ... op init) | (a op (b op (c op init))) |
Baca tabelnya perlahan: “left” dan “right” menunjuk ke arah pengelompokan, bukan ke posisi pack. Bentuk binary (baca: “bervalue-awal”) menyisipkan nilai awal init — perbedaan kecil dengan dampak besar saat pack kosong. Untuk jumlahkan(1, 2, 3) dengan binary left fold (0 + ... + args), compiler menghasilkan ((0 + 1) + 2) + 3.
Menggantikan Rekursi: Jumlah, Cetak, dan Uji #
Tiga template berikut menggantikan pola rekursi Chapter Variadic Templates dan Chapter Parameter Pack — tanpa base case, tanpa fungsi kedua:
#include <iostream>
// Binary left fold: jumlah semua argumen, dimulai dari nilai awal 0
template <typename... Args>
auto jumlahkan(const Args&... args) {
return (0 + ... + args);
}
// Unary right fold dengan koma: cetak semua argumen berurutan
template <typename... Args>
void cetakSemua(const Args&... args) {
((std::cout << args << " "), ...);
std::cout << "\n";
}
// Unary left fold: benar bila semua argumen bernilai benar
template <typename... Args>
bool semuaBenar(const Args&... args) {
return (... && args);
}
int main() {
std::cout << "jumlah(1..5) : " << jumlahkan(1, 2, 3, 4, 5) << "\n";
std::cout << "jumlah() kosong: " << jumlahkan() << "\n";
cetakSemua("a", 2, 3.5, 'z');
std::cout << std::boolalpha
<< "semuaBenar(1, 2): " << semuaBenar(1, 2) << "\n"
<< "semuaBenar(1, 0): " << semuaBenar(1, 0) << "\n";
return 0;
}Output yang Dihasilkan:
jumlah(1..5) : 15
jumlah() kosong: 0
a 2 3.5 z
semuaBenar(1, 2): true
semuaBenar(1, 0): falseBedah baris demi baris:
- Baris 6 (
return (0 + ... + args);): binary left fold atasoperator+. Nilai awal0berperan ganda: memberi tipe hasil yang pasti dan menyelamatkan pemanggilan tanpa argumen — baris 24 menghasilkan0, bukan error. - Baris 12 (
((std::cout << args << " "), ...);): unary right fold atas operator koma. Pola per elemennya adalah “cetak argumen, lalu spasi”; fold mengulanginya untuk setiap elemen dengan urutan yang kita kendalikan sendiri. Spasi ekstra di akhir baris keluaran adalah konsekuensi pola ini — setiap elemen diikuti spasi, termasuk yang terakhir. - Baris 19 (
return (... && args);): unary left fold atasoperator&&. UntuksemuaBenar(1, 0), compiler menghasilkan(1 && 0)— bernilaifalse. Denganstd::boolalpha, hasilnya tampil sebagaitrue/false. - Baris 23-28: tiga pemanggilan dengan panjang pack berbeda — lima, nol, dan empat — tanpa satu pun perubahan definisi template.
Perbandingan dengan versi rekursif sudah cukup jelas: dua fungsi menjadi satu ekspresi, dan compiler tidak perlu menginstansiasi rantai fungsi per panjang argumen.
Kaidah Pack Kosong dan Jebakan Lain #
Pack kosong pada unary fold tidak selalu sah — hanya tiga operator yang punya nilai identitas bawaan standar:
&&→ nilai identitasnyatrue||→ nilai identitasnyafalse,→ nilai identitasnyavoid()
Untuk operator lain (+, *, <<, dan seterusnya), pack kosong pada unary fold adalah error kompilasi. Karena itu kebiasaan baiknya: bila pemanggil tanpa argumen itu mungkin, pakai bentuk binary dengan nilai awal yang tepat — 0 untuk penjumlahan, 1 untuk perkalian, dan seterusnya. jumlahkan() di atas sudah mencontohkannya.
Sampai di sini, perangkat sintaks template kita lengkap: tipe, nilai, spesialisasi, pack, dan fold. Bagian berikutnya mengalihkan fokus dari bagaimana menulis template ke bagaimana mengendalikan template: memeriksa tipe pada saat kompilasi dengan <type_traits>, agar template tidak lagi menerima tipe yang tidak pantas.
Referensi resmi: cppreference — Fold expression .