Sepanjang Section ini, setiap panggilan algoritma selalu diawali pasangan begin() dan end(), dan pengolahan bertahap memerlukan container sementara untuk menampung hasil antara. C++20 menghadirkan jawaban modern bernama Ranges library: satu header <ranges> berisi view adaptor yang dapat dirangkai seperti pipa, ditambah versi baru semua algoritma di namespace std::ranges. View adalah cara melihat data tanpa menyalinnya — view tidak menyimpan elemen dan tidak menghitung apa pun saat dirangkai. Pekerjaan baru dilakukan ketika hasil pipeline diiterasi, satu elemen demi satu elemen. Chapter ini menutup Section Iterators dan Algorithms: kita merangkai view menjadi pipeline, membuktikan lazy evaluation dengan deret tak hingga, membandingkan gaya ranges dengan gaya iterator klasik, lalu merangkum perjalanan Section ini.
Apa Itu Range dan View #
Range (rentang) adalah istilah umum untuk segala sesuatu yang bisa diiterasi dengan begin() dan end() — std::vector, std::string, array, bahkan hasil view. Definisi ini dijaga oleh concept std::ranges::range sehingga algoritma versi ranges dapat menerima semua jenis sumber data tersebut. View (lebih tepatnya range adaptor) adalah range yang ringan: ia tidak memiliki elemen sendiri, melainkan menyimpan referensi ke range lain beserta satu aturan transformasi. std::views::filter(predikat) misalnya menghasilkan view yang saat diiterasi hanya melewatkan elemen yang lolos predikat, tanpa memindahkan satu byte pun dari sumbernya.
Beberapa adaptor yang paling sering dipakai:
std::views::filter(p)— hanya melewatkan elemen yang lolos predikatpstd::views::transform(f)— melewatkan hasilf(x)untuk setiap elemenxstd::views::take(n)/std::views::drop(n)— mengambil atau membuangnelemen pertamastd::views::iota(a, b)— menghasilkan dereta, a+1, …hingga sebelumb; tanpab, deretnya tak hinggastd::views::reverse— membalik urutan elemen
Adaptor dirangkai dengan operator pipe |: ekspresi di kiri menjadi masukan adaptor di kanan, sehingga pipeline panjang dibaca dari kiri ke kanan persis mengikuti urutan pengolahan. Semua view dideklarasikan di header <ranges>, sedangkan algoritma versi ranges tetap tinggal di <algorithm> dan <numeric> yang sudah kita kenal. Catatan penting: merangkai view tidak pernah mengubah container sumber — sifat yang sama dengan algoritma klasik yang membaca data lewat iterator.
Pipeline Views dengan Sintaks Pipe #
Program berikut menyatukan materi inti chapter ini dalam satu file: satu pipeline tiga adaptor di atas vector, satu pipeline di atas deret tak hingga, kombinasi drop dan reverse, serta penutup berupa std::ranges::sort:
#include <algorithm>
#include <iostream>
#include <ranges>
#include <vector>
bool prima(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) return false;
}
return true;
}
// Helper cetak untuk rentang apa pun. Parameternya R&& (bukan const R&)
// karena banyak view C++20 tidak bisa diiterasi lewat referensi const.
template <typename R>
void cetak(R&& r) {
bool pertama = true;
for (auto&& x : r) {
if (!pertama) std::cout << ' ';
std::cout << x;
pertama = false;
}
std::cout << '\n';
}
int main() {
std::vector<int> transaksi = {120, 45, 300, 85, 500, 60, 220};
// 1. Pipeline views: filter -> transform -> take, tanpa container baru
auto promo = transaksi
| std::views::filter([](int x) { return x >= 100; })
| std::views::transform([](int x) { return x / 10; })
| std::views::take(3);
std::cout << "Pipeline : ";
cetak(promo);
// 2. Lazy evaluation: deret TAK HINGGA tetap aman diiterasi
std::cout << "5 prima pertama: ";
cetak(std::views::iota(1) | std::views::filter(prima)
| std::views::take(5));
// 3. drop dan reverse
std::cout << "drop+reverse : ";
cetak(std::views::iota(1, 8) | std::views::drop(2) | std::views::reverse);
// 4. Algoritma pada ranges: tanpa begin()/end() eksplisit
std::ranges::sort(transaksi);
std::cout << "ranges::sort : ";
cetak(transaksi);
return 0;
}Output yang Dihasilkan:
Pipeline : 12 30 50
5 prima pertama: 2 3 5 7 11
drop+reverse : 7 6 5 4 3
ranges::sort : 45 60 85 120 220 300 500Bedah baris demi baris:
- Baris 14-25 (helper
cetakgenerik): berbeda dari chapter-chapter sebelumnya yang menerimaconst std::vector<int>&, helper ini menerimaR&&— referensi ke tipe rentang apa pun — sehingga bisa dipakai untuk vector maupun view. Alasannya: banyak view C++20 tidak dapat diiterasi lewat referensiconst(misalnyafilter_viewyang menyimpan cache iterator internal), sehingga parameterconst R&akan menolak pipeline seperti di atas. Pemisah spasi ditaruh sebagai prefix agar tidak ada spasi tersisa di akhir baris output. - Baris 31-34 (membentuk pipeline): tiga adaptor dirangkai dengan
|ke vectortransaksi. Pada tahap ini belum ada angka yang diproses —promohanya menyimpan referensi ketransaksiplus daftar adaptor. Tipe lengkapnya panjang, dan itulah alasanautohampir selalu dipakai untuk variabel view. - Baris 35-36 (iterasi memicu komputasi): range-for di dalam
cetakmenarik elemen satu per satu. Filter melewatkan120, 300, 500, 220, transform membaginya 10 menjadi12, 30, 50, 22, dantake(3)menghentikan penarikan setelah tiga hasil terkumpul — itulah isi baris output pertama. - Baris 40-41 (deret tak hingga):
std::views::iota(1)menghasilkan 1, 2, 3, … tanpa batas. Dirangkai denganfilter(prima)dantake(5), program berhenti rapi di2 3 5 7 11— bukti nyata lazy evaluation yang kita bedah di bagian berikutnya. - Baris 45 (drop dan reverse):
std::views::iota(1, 8)menghasilkan 1 sampai 7,drop(2)membuang dua elemen pertama menjadi 3–7, lalureversemembalik urutannya menjadi7 6 5 4 3. Perhatikanreversedipakai tanpa tanda kurung karena adaptor ini tidak membutuhkan argumen. - Baris 48 (
std::ranges::sort(transaksi)): algoritma versi ranges mengurutkan container langsung tanpabegin()/end(). Perbedaannya dengan versi klasik kita bahas di bagian selanjutnya.
Lazy Evaluation: Dihitung Saat Diiterasi #
Baris auto promo = transaksi | std::views::filter(...) | …; di atas tidak memproses satu angka pun. Variabel promo hanyalah objek kecil yang menyimpan referensi ke transaksi plus daftar adaptor — menyalin view semurah menyalin pointer. Komputasi baru terjadi ketika cetak(promo) menjalankan range-for: iterator view menarik elemen dari sumber satu per satu, dan setiap elemen mengalir melewati seluruh rantai adaptor sebelum elemen berikutnya ditarik.
Diagram di atas menggambarkan pipeline pertama. Kotak filter, transform, dan take bukan wadah penyimpan — masing-masing hanya aturan yang diterapkan saat elemen ditarik. Ketika range-for meminta satu elemen, permintaan itu merambat dari kanan ke kiri: take(3) meminta elemen berikutnya dari transform, transform meminta dari filter, dan filter mengambil dari vector sumber sampai menemukan yang lolos. Tidak pernah ada vector antara yang terbentuk di sepanjang proses.
Bukti paling meyakinkan ada di pipeline kedua. std::views::iota(1) menghasilkan deret 1, 2, 3, … tanpa akhir; dengan strategi eager — menyalin dulu, memproses kemudian — program tersebut tidak akan pernah selesai. Karena view menghitung elemen hanya saat diminta, take(5) cukup berhenti setelah lima prima pertama ditemukan, dan program berakhir normal dengan output 2 3 5 7 11.
Lazy evaluation memberi dua keuntungan praktis. Pertama, tidak ada container sementara antar tahap: pipeline empat adaptor tidak membuat satu vector baru pun. Kedua, pekerjaan ikut hemat, karena pada pipeline pertama lambda transform hanya dieksekusi untuk transaksi bernilai minimal 100, bukan untuk seluruh data.
std::ranges::filter_view dataPromo() {
std::vector<int> transaksi = {120, 45, 300};
return transaksi | std::views::filter([](int x) { return x >= 100; });
// transaksi hilang di sini; view yang dikembalikan menunjuk data
// yang sudah tidak ada -> undefined behavior saat diiterasi
}Perbedaan Algoritma Ranges dan Klasik #
Sejak C++20, hampir semua algoritma di <algorithm> punya kembaran di namespace std::ranges yang menerima range langsung: std::ranges::sort(v), std::ranges::find(v, nilai), std::ranges::count_if(v, predikat), dan seterusnya. Bentuk ini menggantikan pasangan begin()/end() yang sebelumnya wajib ditulis. Algoritma ranges juga constrained: lewat C++20 concepts, compiler memeriksa sejak awal apakah iterator sumber memenuhi syarat algoritma, sehingga kesalahan terdeteksi lebih cepat dengan pesan error yang lebih jelas. Fitur tambahan yang paling terasa manfaatnya adalah projection — parameter opsional yang memetakan elemen ke kunci operasi.
Program berikut menghadapkan kedua gaya pada kasus yang sama, lalu menutup dengan materialisasi hasil pipeline ke container:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <ranges>
#include <string>
#include <vector>
struct Produk {
std::string nama;
int harga;
};
int main() {
std::vector<int> angka = {5, 2, 8, 1, 9, 3};
// Gaya klasik: pasangan iterator begin()/end() ditulis eksplisit
auto it = std::find(angka.begin(), angka.end(), 8);
std::cout << "Klasik : indeks " << (it - angka.begin()) << "\n";
// Gaya ranges: container dikirim langsung tanpa begin()/end()
auto pos = std::ranges::find(angka, 8);
std::cout << "Ranges : indeks " << (pos - angka.begin()) << "\n";
// Projection: urutkan berdasarkan member tanpa lambda tambahan
std::vector<Produk> katalog = {{"Pena", 3000}, {"Buku", 12000},
{"Penghapus", 2000}};
std::ranges::sort(katalog, {}, &Produk::harga);
for (std::size_t i = 0; i < katalog.size(); ++i) {
if (i > 0) std::cout << ' ';
std::cout << katalog[i].nama << '=' << katalog[i].harga;
}
std::cout << "\n";
// Materialisasi: hasil pipeline disimpan ke container sungguhan
std::vector<int> kuadratGenap;
std::ranges::copy(angka | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; }),
std::back_inserter(kuadratGenap));
for (std::size_t i = 0; i < kuadratGenap.size(); ++i) {
if (i > 0) std::cout << ' ';
std::cout << kuadratGenap[i];
}
std::cout << "\n";
return 0;
}Output yang Dihasilkan:
Klasik : indeks 2
Ranges : indeks 2
Penghapus=2000 Pena=3000 Buku=12000
4 64Bedah baris penting:
- Baris 16-18 dan 20-22 (dua gaya find): versi klasik menulis pasangan
angka.begin(), angka.end(), sedangkan versi ranges cukupstd::ranges::find(angka, 8). Keduanya mengembalikan iterator dengan makna yang sama — menunjuk elemen di indeks 2 — sehingga cara menghitung indeksnya pun identik. Bila suatu saat kita butuh rentang parsial, overload ranges yang menerima iterator (std::ranges::find(angka.begin(), angka.end(), 8)) juga tersedia. - Baris 27 (
std::ranges::sort(katalog, {}, &Produk::harga)): argumen kosong{}berarti comparator default (<), dan parameter ketiga adalah projection — alamat memberhargadipakai sebagai kunci pengurutan tanpa lambda tambahan. Hasilnya katalog terurut menaik berdasarkan harga:Penghapus=2000 Pena=3000 Buku=12000. - Baris 35-38 (materialisasi): pipeline filter-genap lalu transform-kuadrat dijadikan argumen pertama
std::ranges::copy, denganstd::back_insertersebagai tujuan. Di sinilah view yang sifatnya sementara berubah menjadi vector sungguhan bernamakuadratGenapyang berisi4 64.
Gaya klasik tetap wajib dikenali. Mayoritas kode C++ yang beredar ditulis sebelum C++20, dan pola [first, last) masih paling jelas untuk memproses bagian tengah container seperti contoh sort tiga elemen pada Chapter Algoritma Berbasis Rentang. Sumber yang bukan range penuh — misalnya iterator yang ditulis manual ke buffer — juga tetap ditangani dengan gaya iterator.
Terakhir, ingat bahwa hasil pipeline berupa view bukanlah wadah. Untuk menyimpannya, gunakan std::ranges::copy dengan std::back_inserter seperti pada program di atas, atau — sejak C++23 — pintasan std::ranges::to<std::vector>() yang membentuk container langsung dari pipeline. Setelah dimaterialisasi, hasilnya berdiri sendiri dan bebas dari risiko view menggantung yang disebut di bagian sebelumnya.
Penutup Section: dari Iterator ke Ranges #
Section Iterators dan Algorithms kini tuntas. Kita mulai dari begin()/end() dan kategori iterator, lalu memakai pola [first, last) pada seluruh algoritma inti: sort, find, binary search, min/max, count, copy/move, transform, idiom remove-erase, partition, accumulate, dan algoritma <numeric>, hingga custom comparator dan function objects. Chapter ini menutup lingkaran tersebut: Ranges dan views bukan pengganti fondasi itu, melainkan lapisan modern di atasnya — boilerplate iterator disembunyikan, sedangkan model iterasi dan concept iterator tetap sama persis di balik layar.
Selama 18 chapter, data yang kita olah selalu sudah berada di memori — vector yang kita isi sendiri lewat initializer list. Program sungguhan membaca data dari luar: ketikan pengguna, file konfigurasi, log, gambar, atau jaringan. Section berikutnya, Input-Output dan File, dibuka dengan Chapter Peta iostream yang membedah keluarga stream di balik std::cout dan std::cin yang selama ini kita pakai tanpa banyak pertanyaan. Setelah itu kita akan memformat output dengan manipulator, membaca dan menulis file teks maupun biner, sampai menjelajah direktori dengan <filesystem>.
Rujukan resmi: Ranges library , std::ranges::filter_view , dan std::ranges::iota_view .