Chapter sebelumnya menutup dengan janji: concept buatan sendiri yang mengecek keberadaan method dan operator ditulis dengan satu alat bernama requires expression. Alat inilah topik kita sekarang. requires expression adalah ekspresi boolean tentang tipe — kita mendaftar operasi yang harus sah, seperti t.size() atau a + b, lalu compiler memeriksa semuanya pada saat kompilasi tanpa menjalankan satu baris pun. Hasilnya bisa diberi nama menjadi concept, dipakai seketika di dalam if constexpr, atau ditempel langsung ke template lewat pola requires requires. Setelah chapter ini, kita tidak lagi bergantung pada concept baku untuk mengekspresikan kendala.
Anatomi requires Expression #
Bentuk dasarnya seperti ini:
requires (daftar-parameter) {
// daftar requirement, satu per baris
}Compiler membuat “dunia hipotesis” di dalamnya: setiap parameter di daftar menjadi object fiktif, lalu semua requirement diuji satu per satu. Bila semuanya sah, hasilnya true; satu saja gagal, hasilnya false — dan yang terpenting, kegagalan itu bukan error, hanya jawaban bernilai false. Ada empat jenis requirement yang bisa kita daftarkan:
| Jenis | Bentuk | Yang diperiksa |
|---|---|---|
| simple | t.size(); | ekspresi sah untuk dikompilasi |
| type | typename T::value_type; | tipe nested bernama itu ada |
| compound | { t.size() } -> std::convertible_to<std::size_t>; | ekspresi sah dan hasilnya memenuhi concept |
| nested | requires std::default_initializable<T>; | concept lain terpenuhi untuk T |
Contoh berikut memakai requires expression di dua peran: sebagai badan concept bernama, dan sebagai kendala seketika di dalam if constexpr:
#include <concepts>
#include <iostream>
#include <string>
#include <vector>
// Concept: T punya method size() yang hasilnya bilangan asli
template <typename T>
concept PunyaUkuran = requires(T t) {
t.size();
{ t.size() } -> std::convertible_to<std::size_t>;
};
// Concept: dua nilai bertipe T bisa dijumlah
template <typename T>
concept BisaDitambah = requires(T a, T b) {
a + b;
};
template <PunyaUkuran T>
void laporan(const T& wadah) {
std::cout << "ukuran: " << wadah.size() << "\n";
}
template <BisaDitambah T>
T gabung(const T& a, const T& b) {
return a + b;
}
// requires expression di dalam if constexpr: pilih cabang berdasar kemampuan
template <typename T>
void uji(const T& nilai) {
if constexpr (requires { nilai.size(); }) {
std::cout << "punya size(): " << nilai.size() << "\n";
} else {
std::cout << "tidak punya size()\n";
}
}
int main() {
laporan(std::vector<int>{1, 2, 3});
laporan(std::string("lima"));
std::cout << "gabung angka : " << gabung(10, 32) << "\n";
std::cout << "gabung string: " << gabung(std::string("bulan"), std::string("sabit")) << "\n";
uji(std::vector<int>{9});
uji(42);
std::cout << std::boolalpha
<< "int memenuhi PunyaUkuran? " << PunyaUkuran<int> << "\n"
<< "vector memenuhi PunyaUkuran? " << PunyaUkuran<std::vector<int>> << "\n";
return 0;
}Output yang Dihasilkan:
ukuran: 3
ukuran: 4
gabung angka : 42
gabung string: bulansabit
punya size(): 1
tidak punya size()
int memenuhi PunyaUkuran? false
vector memenuhi PunyaUkuran? trueBedah baris demi baris:
- Baris 8 (
concept PunyaUkuran = requires(T t) { ... }): requires expression dengan satu parameter fiktiftbertipeT. Objectttidak pernah dibuat — ia hanya alat peraga bagi compiler untuk mengetes ekspresi. - Baris 9 (
t.size();): simple requirement. Cukup meminta ekspresi sah; hasil kembaliannya tidak dituntut apa pun. - Baris 10 (
{ t.size() } -> std::convertible_to<std::size_t>;): compound requirement. Selain ekspresi harus sah, tipe hasilnya juga wajib memenuhi conceptstd::convertible_to<std::size_t>— jadi method yang mengembalikan sembarang tipe tidak otomatis lolos. - Baris 15-16 (
a + b;): conceptBisaDitambahmengecek operator+antara dua nilai bertipeT.gabung(10, 32)menghasilkan42, sedangkangabungdua string menyambung keduanya — satu template, perilaku sesuai tipe. - Baris 32 (
if constexpr (requires { nilai.size(); })): requires expression dipakai ad-hoc, tanpa nama, langsung sebagai kondisi.ujimenghasilkan instansiasi berbeda untuk vector dan untukint— cabang yang tidak terpilih tidak dikompilasi (materi penuhnya di chapter berikutnya). - Baris 49-51: requires expression yang sudah dibungkus concept ternyata bisa dipakai sebagai nilai boolean biasa.
PunyaUkuran<int>bernilaifalsekarenainttidak punya methodsize();PunyaUkuran<std::vector<int>>bernilaitrue.
Dari requires Expression ke Concept #
Concept dan requires expression adalah dua lapisan yang saling melengkapi. requires expression menjawab “apakah tipe ini mampu melakukan ini?” untuk satu tipe; concept memberi nama pada pertanyaan itu agar bisa dipakai ulang di banyak tempat. Secara sintaks, concept Nama = requires(...) { ... }; hanyalah penugasan ekspresi ke sebuah nama — baris 8 pada requires.cpp di atas adalah contoh utuhnya.
Karena hasilnya boolean, concept buatan kita juga bisa digabung seperti logika biasa. concept BisaDiukurDanDitambah = PunyaUkuran<T> && BisaDitambah<T>; sah dan terbaca seperti kalimat. Anda juga boleh mencampur concept baku dan buatan dalam satu gabungan, misalnya std::integral<T> || PunyaUkuran<T>. Pola kombinasi inilah yang membuat kendala panjang tetap terorganisasi: pecah menjadi concept kecil yang teruji, lalu susun.
requires requires: Kendala Tanpa Nama #
Ada kalanya kita butuh kendala sekali pakai tetapi tidak mau mendefinisikan concept. Untuk itulah ada pola requires requires: requires clause (kendala pada template) yang isinya requires expression (pemeriksaan kemampuan). Contoh berikut memakai pola itu sekaligus memamerkan keempat jenis requirement dalam satu concept:
#include <concepts>
#include <iostream>
#include <string>
#include <vector>
// Concept dengan empat jenis requirement sekaligus
template <typename T>
concept Wadah = requires(T t) {
// 1. simple requirement: ekspresi harus sah (hasilnya tidak dituntut)
t.clear();
// 2. type requirement: tipe nested bernama value_type harus ada
typename T::value_type;
// 3. compound requirement: ekspresi sah DAN hasilnya memenuhi concept
{ t.size() } -> std::convertible_to<std::size_t>;
// 4. nested requirement: concept lain harus terpenuhi untuk T
requires std::default_initializable<T>;
};
// requires requires: kendala ad-hoc tanpa memberi nama concept
template <typename T>
requires requires(T t) { t.size(); }
std::size_t jumlahElemen(const T& wadah) {
return wadah.size();
}
// Overload tanpa kendala: dipakai hanya bila kandidat terkendali tidak cocok
template <typename T>
std::size_t jumlahElemen(const T&) {
return 1;
}
int main() {
static_assert(Wadah<std::vector<int>>);
static_assert(Wadah<std::string>);
std::cout << "vector : " << jumlahElemen(std::vector<int>{1, 2, 3}) << "\n";
std::cout << "string : " << jumlahElemen(std::string("kopi")) << "\n";
std::cout << "angka tunggal : " << jumlahElemen(7) << "\n";
return 0;
}Output yang Dihasilkan:
vector : 3
string : 4
angka tunggal : 1Bedah baris demi baris:
- Baris 8-20 (
concept Wadah): satu requires expression berisi keempat jenis requirement sekaligus — setiap jenisnya menutup celah yang tidak ditangkap jenis lain. - Baris 13 (
typename T::value_type;): type requirement.std::vector<int>danstd::stringsama-sama punya tipe nestedvalue_type;inttidak, sehinggaWadah<int>langsungfalsetanpa perlu menilai requirement lain. - Baris 16: compound requirement menuntut
t.size()sah dan hasilnya bisa dikonversi kestd::size_t— versi ketat dari simple requirement pada chapter-concept kita sebelumnya. - Baris 19 (
requires std::default_initializable<T>;): nested requirement. Katarequiresdi dalam badan menandai “kendala lain diperiksa di sini” — tanpa itu,std::default_initializable<T>hanya ekspresi yang dinilai, bukan syarat yang diuji. - Baris 24 (
requires requires(T t) { t.size(); }): pola dua kata kunci.requirespertama adalah requires clause milik template;requireskedua membuka requires expression sebagai isinya. Keduanya punya tugas berbeda, meski namanya sama. - Baris 30-33: overload tanpa kendala menjadi jalan keluar bila kandidat terkendali tidak cocok. Untuk
jumlahElemen(7), versi terkendali ditolak (int tidak punyasize()), sehingga compiler jatuh ke versi ini dan mengembalikan1. - Baris 36-41:
static_assertmembuktikanWadahbenar untuk vector dan string, lalu tiga pemanggilan menunjukkan pemilihan overload: vector menghasilkan3, string4, sedangkan angka tunggal lewat jalur fallback.
Pesan Error yang Menunjuk Requirement #
Bagaimana bila kendala dilanggar? Compiler modern tidak berhenti di “tidak cocok” — ia menunjuk requirement mana yang gagal. Aktifkan static_assert(Wadah<int>, "int bukan Wadah"); pada baris 36:
#include <concepts>
template <typename T>
concept Wadah = requires(T t) {
t.clear();
typename T::value_type;
{ t.size() } -> std::convertible_to<std::size_t>;
requires std::default_initializable<T>;
};
static_assert(Wadah<int>, "int bukan Wadah");
int main() { return 0; }Potongan pesan nyata dari g++ 14.2 (dipersingkat):
wadah-int.cpp:13:15: error: static assertion failed: int bukan Wadah
13 | static_assert(Wadah<int>, "int bukan Wadah");
| ^~~~~~~~~~
wadah-int.cpp:13:15: note: constraints not satisfied
wadah-int.cpp:6:17: in requirements with 'T t' [with T = int]
wadah-int.cpp:7:12: note: the required expression 't.clear()' is invalid
7 | t.clear();
| ~~~~~~~^~
wadah-int.cpp:8:14: note: the required type 'typename T::value_type' is invalid
8 | typename T::value_type;
| ~~~~~~~~~^~~~~~~~~~~~~~
wadah-int.cpp:9:13: note: the required expression 't.size()' is invalid
9 | { t.size() } -> std::convertible_to<std::size_t>;
| ~~~~~~^~Compiler menelusuri daftar requirement satu per satu dan melaporkan setiap yang invalid: t.clear() tidak sah, value_type tidak ada, t.size() tidak sah. Pembaca pesan bisa langsung melihat kontrak yang dituntut, bukan menebak-nebak dari “no matching function”. Catatan kecil: baris terakhir pesan menyarankan -fconcepts-diagnostics-depth=2 bila kita ingin compiler mencantumkan detail lebih dalam lagi.
Sekarang semua alat kendala sudah lengkap: requires expression untuk menguji kemampuan, concept untuk memberi nama, dan requires clause untuk menempelkannya. Yang belum kita bahas adalah menegakkan aturan dengan pesan sendiri dan memilih perilaku berbeda per tipe pada saat kompilasi — dua tugas itu menjadi topik Chapter Compile-time Constraints.
Referensi resmi: cppreference — requires expression dan cppreference — Constraints and concepts .