Dua pilar C++20 yang telah kita pelajari terpisah — concepts di Section Generic Programming dan Templates, serta ranges di Section Iterators dan Algorithms — baru terasa penuh kekuatannya ketika digabungkan. Concepts memberi compiler cara memeriksa syarat tipe sebelum kode diinstansiasi; ranges memberi algoritma yang pada dasarnya sudah ter-constrain oleh concepts bawaan standar. Ketika fungsi template kita memakai concept, dan algoritma std::ranges memakai concept internal seperti std::sortable, seluruh rantai pemanggilan divalidasi di waktu kompilasi dengan pesan error yang jauh lebih manusiawi. Chapter ini merangkum keduanya dalam satu program utuh: fungsi ter-constrain, algoritma dengan projection, dan pipeline view yang komposabel.
Satu Program, Dua Fitur #
Program berikut mendefinisikan satu concept sendiri (Angka), memakainya untuk mengunci sebuah fungsi rataRata, lalu menunjukkan algoritma std::ranges bekerja dengan projection dan pipeline view.
// concepts-ranges.cpp — concepts + ranges: algoritma dan view yang tervalidasi tipe
// Kompilasi: g++ -std=c++23 -Wall -Wextra concepts-ranges.cpp -o concepts-ranges
#include <algorithm>
#include <concepts>
#include <print>
#include <ranges>
#include <string>
#include <vector>
// Concept: syarat tipe yang diperiksa compiler saat kompilasi
template <typename T>
concept Angka = std::integral<T> || std::floating_point<T>;
// Fungsi ter-constrain: hanya menerima tipe yang memenuhi Angka
template <Angka T>
double rataRata(const std::vector<T>& data) {
if (data.empty()) {
return 0.0;
}
return std::ranges::fold_left(data, 0.0, std::plus{}) / data.size();
}
struct Produk {
std::string nama;
int harga;
};
int main() {
std::vector<int> skor{88, 95, 72, 60, 91};
std::print("rata-rata int = {:.2f}\n", rataRata(skor));
std::vector<double> tinggi{165.5, 170.0, 158.25};
std::print("rata-rata double = {:.2f}\n", rataRata(tinggi));
// rataRata(std::vector<std::string>{}); // TOLAK: string bukan Angka
// Constrained algorithm + projection: sort berdasarkan anggota harga
std::vector<Produk> katalog{
{"Pena", 3500}, {"Buku", 12500}, {"Penghapus", 2000}, {"Penggaris", 4800}
};
std::ranges::sort(katalog, {}, &Produk::harga);
// Pipeline views: filter -> transform -> take, dirangkai lewat operator |
auto namaMahal = katalog
| std::views::filter([](const Produk& p) { return p.harga >= 3500; })
| std::views::transform(&Produk::nama)
| std::views::take(2);
for (const std::string& nama : namaMahal) {
std::print("- {}\n", nama);
}
// Algoritma constrained bekerja langsung pada hasil pipeline
auto semuaHarga = katalog | std::views::transform(&Produk::harga);
std::print("harga tertinggi = {}\n", std::ranges::max(semuaHarga));
std::print("total harga = {}\n",
std::ranges::fold_left(semuaHarga, 0, std::plus{}));
return 0;
}Output yang Dihasilkan:
rata-rata int = 81.20
rata-rata double = 164.58
- Pena
- Penggaris
harga tertinggi = 12500
total harga = 22800Mari kita bedah:
- Baris 12-13 (concept Angka):
conceptmendeklarasikan syarat tipe sebagai ekspresi boolean waktu-kompilasi —std::integral<T> || std::floating_point<T>berarti tipe harus bilangan bulat atau pecahan. Concept bisa dirangkai dari traits, requires expression, atau concept lain. - Baris 16-17 (template
<Angka T>): menaruh concept langsung di deklarasi template sama maknanya dengantemplate <typename T> requires Angka<T>. KinirataRatamenerimavector<int>maupunvector<double>, tetapi compiler menolakvector<string>sebelum masuk ke badan fungsi. - Baris 21 (fold_left):
std::ranges::fold_left(C++23) menjumlahkan seluruh elemen range mulai dari0.0— versi range daristd::accumulateklasik, sehingga kita tidak perlu pasangan iteratorbegin()/end(). - Baris 42 (ranges::sort + projection): argumen kedua
{}berarti komparator default, argumen ketiga&Produk::hargaadalah projection — setiap elemen dipetakan ke anggotanya sebelum dibandingkan, tanpa lambda tulisan panjang. - Baris 45-48 (pipeline):
katalog | filter | transform | takemembentuk view — bukan salinan.filtermemilih produk dengan harga setidaknya 3500,transformmemetakan ke nama (string),take(2)memotong dua elemen pertama; hasilnya diiterasi lazily di baris 49-51. - Baris 54-57: view tidak hanya untuk iterasi —
std::ranges::maxdanstd::ranges::fold_leftmenerima hasil pipeline sebagai range biasa, jadi logika “ambil, petakan, hitung” mengalir dalam satu gaya penulisan.
Ketika Syarat Tipe Dilanggar #
Baris 36 sengaja dikomentari. Jika kita melepas komentar dan memanggil rataRata dengan vector<string>, compiler langsung menunjuk masalahnya pada concept yang kita tulis — bukan pada ratusan baris instansiasi template seperti era SFINAE:
probe4.cpp:13:24: error: no matching function for call to 'rataRata(...)'
probe4.cpp:9:8: note: candidate: 'template<class T> requires Angka<T>
double rataRata(const std::vector<T>&)'
probe4.cpp:9:8: note: template argument deduction/substitution failed:
probe4.cpp:9:8: note: constraints not satisfied
probe4.cpp:6:34: note: no operand of the disjunction is satisfied
6 | concept Angka = std::integral<T> || std::floating_point<T>;Pesan kuncinya satu kalimat: constraints not satisfied, disusul catatan baris mana dari disjungsi yang gagal. Dengan SFINAE lama, pelanggaran yang sama biasanya muncul sebagai deretan error “no match for operator+” jauh di dalam header standar. Di sinilah nilai praktis concepts: syarat yang ditulis eksplisit memberi dokumentasi sekaligus diagnostik.
Mengapa Algoritma Ranges Disebut Ter-constrain #
Semua algoritma std::ranges didefinisikan dengan concepts bawaan: std::ranges::sort menuntut range-nya std::sortable, std::ranges::max menuntut std::indirect_strict_weak_order, dan seterusnya. Konsekuensinya, kesalahan tipe terdeteksi di batas panggilan — misalnya mencoba std::ranges::sort atas container yang elemennya tidak bisa dibandingkan langsung menghasilkan pesan tentang sortable, bukan error samar di dalam implementasi. Pattern yang kita lihat di program utama adalah pola resminya: concept menutup pintu masuk yang salah, ranges menyediakan operasi yang aman, dan pipeline view menyusun keduanya tanpa salinan data.
Dengan syarat tipe dan pipeline data beres, satu gaya penulisan template yang tersisa adalah fungsi yang “menahan” eksekusi sampai nilainya diminta. Chapter berikutnya memperkenalkan coroutines — dan std::generator C++23 yang membuatnya praktis tanpa menyentuh promise type.
Rujukan resmi: cppreference — constraints and concepts , cppreference — ranges , cppreference — ranges::sort .