Pada Chapter Ranges dan Views kita sesekali menulis std::ranges::sort(katalog, {}, &Produk::harga) — comparator kosong diikuti alamat member — tanpa menjelaskan kedudukan kedua parameter itu. Chapter penutup Section ini melengkapi sisa peta: projection sebagai cara memberi kunci operasi tanpa lambda eksplisit, perbandingannya dengan comparator, lalu tiga fitur C++23 yang membuat pipeline ranges makin ringkas: std::ranges::to, std::views::zip, dan std::views::enumerate. Setelah chapter ini, Section Iterators dan Algorithms resmi tuntas.
Projection: Kunci Operasi Tanpa Lambda #
Hampir semua algoritma di namespace std::ranges menerima satu parameter tambahan di posisi paling akhir: projection. Projection adalah callable yang diterapkan pada setiap elemen sebelum elemen diteruskan ke logika algoritma — comparator, predikat, maupun operasi inti. Saat kita menulis std::ranges::sort(v, {}, &Produk::nama), algoritma membandingkan a.nama dengan b.nama memakai comparator default {}; elemen Produk tidak pernah diubah, tidak ada container sementara, dan tidak ada salinan nama yang kita tulis sendiri.
Mekanismenya: algoritma ranges menerapkan projection lewat std::invoke, sehingga pointer-to-member berfungsi langsung — &Produk::nama dipanggil sebagai elemen.*(&Produk::nama) untuk setiap elemen. Lambda juga sah sebagai projection dan berguna saat kunci berupa hasil perhitungan, misalnya stok * harga. Karena projection diterapkan pada posisi elemen saat algoritma berjalan, nilai asli container tetap utuh; yang dibandingkan, dicari, atau dihitung adalah hasil pemetaannya.
Comparator vs Projection #
Karena keduanya sama-sama callable, projection sering dikelirukan dengan comparator. Tugasnya berbeda: comparator membandingkan dua kunci, projection memetakan satu elemen menjadi kunci.
| Aspek | Comparator | Projection |
|---|---|---|
| Tugas | membandingkan dua kunci | memetakan satu elemen menjadi kunci |
| Bentuk panggilan | comp(kunciA, kunciB) | proj(elemen) |
| Jumlah argumen | dua | satu |
| Nilai kembali | bool | kunci apa pun (int, std::string, dst.) |
| Urutan parameter | sebelum projection | paling akhir |
| Contoh | std::ranges::greater{} | &Produk::harga |
Pada std::ranges::sort(v, comp, proj), pasangan kunci proj(a) dan proj(b) dibandingkan lewat comp; comparator kosong {} berarti < pada kunci hasil projection. Projection tidak pernah menggantikan comparator — keduanya berpasangan: projection menentukan kunci apa yang dibandingkan, comparator menentukan urutannya. Keputusan praktisnya: kunci berupa satu member dipetakan lewat pointer-to-member karena paling ringkas dan langsung terbaca maksudnya; kunci berupa hasil perhitungan dipetakan lewat lambda; sedangkan pengurutan multi-kriteria tetap wilayah comparator lambda dengan pola dari Chapter Custom Comparator.
Program berikut menguji keempat pola tersebut pada satu data katalog:
#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
struct Produk {
std::string nama;
int stok;
int harga;
};
void cetak(const std::vector<Produk>& gudang) {
for (std::size_t i = 0; i < gudang.size(); ++i) {
if (i > 0) std::cout << " | ";
std::cout << gudang[i].nama << '(' << gudang[i].stok << ','
<< gudang[i].harga << ')';
}
std::cout << '\n';
}
int main() {
std::vector<Produk> gudang = {
{"Buku", 12, 45000},
{"Pena", 30, 3000},
{"Agenda", 8, 60000},
{"Penghapus", 25, 2000},
{"Penggaris", 15, 5000},
};
// 1. Projection tanpa comparator eksplisit: urut berdasarkan nama
std::ranges::sort(gudang, {}, &Produk::nama);
std::cout << "Sort nama : ";
cetak(gudang);
// 2. Projection + comparator: harga menurun lewat std::ranges::greater
std::ranges::sort(gudang, std::ranges::greater{}, &Produk::harga);
std::cout << "Sort harga : ";
cetak(gudang);
// 3. Projection berupa lambda: kunci hasil perhitungan stok * harga
std::ranges::sort(gudang, {},
[](const Produk& p) { return p.stok * p.harga; });
std::cout << "Sort nilai : ";
cetak(gudang);
// 4. Projection pada algoritma lain: cari stok terbesar tanpa loop manual
auto stokTerbesar = std::ranges::max_element(gudang, {}, &Produk::stok);
std::cout << "Stok terbesar : " << stokTerbesar->nama << " ("
<< stokTerbesar->stok << ")\n";
return 0;
}Output yang Dihasilkan:
Sort nama : Agenda(8,60000) | Buku(12,45000) | Pena(30,3000) | Penggaris(15,5000) | Penghapus(25,2000)
Sort harga : Agenda(8,60000) | Buku(12,45000) | Penggaris(15,5000) | Pena(30,3000) | Penghapus(25,2000)
Sort nilai : Penghapus(25,2000) | Penggaris(15,5000) | Pena(30,3000) | Agenda(8,60000) | Buku(12,45000)
Stok terbesar : Pena (30)Bedah baris demi baris:
- Baris 32 (
std::ranges::sort(gudang, {}, &Produk::nama)): argumen{}adalah comparator default<untukstd::string, dan&Produk::namamenjadi projection. Katalog terurut alfabetis tanpa satu lambda pun. - Baris 37 (harga menurun):
std::ranges::greater{}dipasangkan dengan projection&Produk::harga; comparator menerima duaintharga, bukan duaProduk, sehingga perbandingan berlangsung pada kunci hasil projection. - Baris 42-43 (lambda sebagai projection): kunci
p.stok * p.hargadihitung pada setiap pembandingan. Urutan berubah menjadi naik berdasarkan nilai persediaan, dan elemen asli tetap utuh. - Baris 48 (
std::ranges::max_element(gudang, {}, &Produk::stok)): projection bukan miliksortsaja —max_element,min_element,find_if, hinggacount_ifversi ranges menerima parameter yang sama. Hasilnya iterator ke produk berstok30tanpa loop manual. - Baris 13-20 (helper
cetak): menampilkan setiap produk sebagainama(stok,harga)agar isi katalog pada tiap percobaan terbaca jelas; helper ini tidak berubah sepanjang program.
ranges::to: Dari Pipeline ke Container #
Sebelum C++23, menyimpan hasil view ke container selalu lewat idiom penyalinan: deklarasikan tujuan kosong, lalu std::ranges::copy(pipeline, std::back_inserter(tujuan)) — dua langkah dan dua nama variabel. C++23 menambahkan std::ranges::to yang membentuk container langsung di ujung pipeline: nilai | std::views::filter(...) | std::ranges::to<std::vector>(). Tipe elemen disimpulkan otomatis dari pipeline, dan bila ingin eksplisit kita bisa menulis std::ranges::to<std::vector<int>>(). Tujuannya tidak terbatas pada vector — std::deque, std::list, std::string, hingga std::map dapat dipakai selama elemen hasil pipeline cocok dengan jenis containernya.
zip dan enumerate #
Dua view C++23 lain yang sering menggantikan loop indeks adalah std::views::zip dan std::views::enumerate. zip(r1, r2, ...) memasangkan elemen ke-i dari setiap range menjadi satu tuple; panjang hasil mengikuti range terpendek, sehingga dua rentang yang beda panjang tetap bisa dirangkai. enumerate(r) adalah kasus khusus yang nyaman: setiap elemen dipasangkan dengan indeksnya mulai dari 0, sehingga range-for yang butuh nomor urut tidak lagi memelihara variabel std::size_t i manual.
Keduanya lazy seperti view lain: elemen dipasangkan saat diiterasi, bukan saat dirangkai. Hasil zip dan enumerate sama-sama tuple, sehingga structured binding auto&& [a, b] adalah cara standar membacanya. Program terakhir Section ini merangkum ketiganya — zip, enumerate, dan ranges::to — dalam satu alur uji:
#include <algorithm>
#include <iostream>
#include <ranges>
#include <string>
#include <vector>
int main() {
std::vector<std::string> peserta = {"Budi", "Siti", "Dewi", "Rian"};
std::vector<int> skor = {88, 92, 75};
// 1. zip: pasangkan elemen ke-i tiap range; berhenti di range terpendek
std::cout << "zip:\n";
for (auto&& [nama, poin] : std::views::zip(peserta, skor)) {
std::cout << " " << nama << " -> " << poin << '\n';
}
// 2. enumerate: setiap elemen dipasangkan dengan indeksnya
std::vector<std::string> tugas = {"bersih", "urut", "uji"};
std::cout << "enumerate:\n";
for (auto&& [indeks, nama] : std::views::enumerate(tugas)) {
std::cout << " " << indeks << ": " << nama << '\n';
}
// 3. ranges::to: materialisasi hasil pipeline ke container
std::vector<int> nilai = {70, 55, 90, 62, 88, 45};
auto lulus = nilai | std::views::filter([](int x) { return x >= 70; })
| std::ranges::to<std::vector>();
std::cout << "lulus: ";
bool pertama = true;
for (int x : lulus) {
if (!pertama) std::cout << ' ';
std::cout << x;
pertama = false;
}
std::cout << " (ukuran " << lulus.size() << ")\n";
return 0;
}Output yang Dihasilkan:
zip:
Budi -> 88
Siti -> 92
Dewi -> 75
enumerate:
0: bersih
1: urut
2: uji
lulus: 70 90 88 (ukuran 3)Bedah baris demi baris:
- Baris 8-9 dan 13 (zip dua vector):
pesertaberisi empat nama,skorhanya tiga angka. Zip menghasilkan tiga pasangan —Riantidak muncul karena range terpendek menentukan panjang hasil. - Baris 20 (enumerate):
indeksbertipe tanda beda rentang (std::ptrdiff_t) dannamamerujuk elemen asli, sehingga nomor urut dan isi terbaca tanpa variabel counter. - Baris 26-27 (pipeline + ranges::to): pipeline
filternilai minimal 70 langsung dikumpulkan menjadi vectorlulustanpastd::ranges::copydan tanpa deklarasi vector kosong terlebih dahulu. - Baris 29-34 (pencetak pemisah-depan): pola
if (!pertama)menampilkan spasi hanya antar elemen, sehingga barislulus:tidak berakhir dengan spasi — masalah pembatas yang kita temui pada ostream_iterator. - Baris 35 (ukuran hasil):
lulus.size()bernilai 3, membuktikanranges::tomenghasilkan container sungguhan yang bisa dihitung, diakses, dan disimpan.
Penutup Section: dari begin() hingga C++23 #
Section Iterators dan Algorithms kini benar-benar tuntas. Kita berangkat dari konsep iterator dan lima kategorinya, melewati begin()/end(), lalu memakai seluruh algoritma inti — sort, find, binary search, min/max, count, copy/move, transform, idiom remove-erase, partition, accumulate, dan algoritma <numeric> — sebelum menyempurnakan perbandingan lewat custom comparator dan function objects. Ranges dan views merapikan boilerplate, dan chapter ini menutup sisanya: projection untuk kunci, ranges::to untuk hasil, serta zip dan enumerate untuk penggabungan. Fondasi iterator yang kita bangun tetap bekerja di balik semua fitur baru tersebut.
Langkah berikutnya natural: data yang kita olah selama ini selalu ada di memori, sedangkan program sungguhan menerima dan mengirim data lewat stream. Section berikutnya, Input-Output dan File, dibuka dengan Chapter Peta iostream yang membedah keluarga std::istream dan std::ostream — dua kelas yang sejak awal kita pakai melalui std::cin dan std::cout, bahkan lewat istream_iterator dan ostream_iterator pada Chapter ini.
Rujukan resmi: std::ranges::sort , std::ranges::to , dan std::views::zip .