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):

BentukSintaksHasil 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:

cpp
#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:

text
jumlah(1..5)   : 15
jumlah() kosong: 0
a 2 3.5 z 
semuaBenar(1, 2): true
semuaBenar(1, 0): false

Bedah baris demi baris:

  1. Baris 6 (return (0 + ... + args);): binary left fold atas operator+. Nilai awal 0 berperan ganda: memberi tipe hasil yang pasti dan menyelamatkan pemanggilan tanpa argumen — baris 24 menghasilkan 0, bukan error.
  2. 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.
  3. Baris 19 (return (... && args);): unary left fold atas operator&&. Untuk semuaBenar(1, 0), compiler menghasilkan (1 && 0) — bernilai false. Dengan std::boolalpha, hasilnya tampil sebagai true/false.
  4. 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 identitasnya true
  • || → nilai identitasnya false
  • , → nilai identitasnya void()

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 .