enable_if pada chapter sebelumnya bekerja seperti pengawas yang menutup pintu tanpa bicara: fungsi tidak cocok, selesai. C++20 menggantinya dengan concepts — kendala bernama yang dinyatakan langsung pada signature template. template <std::integral T> membaca seperti kalimat: “T harus tipe integral”. Bila pemanggil melanggar, compiler menunjuk kendala mana yang gagal dan mengapa. Chapter ini membawa kita dari trait yang dipakai diam-diam ke kendala yang dipakai terbuka — perubahan gaya terbesar pada pemrograman generik dalam satu dekade terakhir.

Concept: Kendala yang Bisa Dibaca #

Concept adalah predikat tentang tipe yang bernama dan bisa dipakai ulang. Standard library menyediakannya di header <concepts>; kita juga bisa menulis sendiri dengan menggabungkan yang sudah ada. Contoh berikut memakai std::integral sebagai kendala parameter, lalu mendefinisikan concept buatan bernama Numerik:

cpp
#include <concepts>
#include <iostream>
#include <string>

// Concept baku std::integral sebagai kendala parameter tipe
template <std::integral T>
T gandakan(T nilai) {
    return nilai * 2;
}

// Concept buatan sendiri: gabungan dua concept baku
template <typename T>
concept Numerik = std::integral<T> || std::floating_point<T>;

template <Numerik T>
T setengah(T nilai) {
    return nilai / 2;
}

// Kendala langsung pada parameter: std::integral auto
void tampilkan(std::integral auto nilai) {
    std::cout << "tampilkan: " << nilai << "\n";
}

// Concept pada tipe kembalian
std::integral auto bacaInt() {
    return 21;
}

int main() {
    std::cout << "gandakan(21) = " << gandakan(21) << "\n";
    std::cout << "setengah(9) = " << setengah(9) << "\n";
    std::cout << "setengah(2.5) = " << setengah(2.5) << "\n";

    tampilkan(7);
    tampilkan('A');

    std::integral auto nilai = bacaInt();
    static_assert(std::same_as<decltype(nilai), int>);
    std::cout << "nilai = " << nilai << "\n";

    // gandakan(2.5);               // error: tidak memenuhi std::integral
    // setengah(std::string("x"));  // error: std::string bukan Numerik

    return 0;
}

Output yang Dihasilkan:

text
gandakan(21) = 42
setengah(9) = 4
setengah(2.5) = 1.25
tampilkan: 7
tampilkan: A
nilai = 21

Bedah baris demi baris:

  1. Baris 6 (template <std::integral T>): kendala ditempel pada parameter tipe. gandakan(21) sah karena int integral; gandakan(2.5) akan ditolak — dan pesannya jauh lebih jelas daripada enable_if, sebagaimana kita buktikan sebentar lagi.
  2. Baris 12-13 (concept Numerik = std::integral<T> || std::floating_point<T>;): concept buatan sendiri sebagai ekspresi boolean tentang T. Concept bisa menggabungkan concept lain dengan && dan ||, persis seperti logika biasa.
  3. Baris 16-17: setengah menerima tipe Numerik. Pemanggilan setengah(9) menghasilkan 4 (pembagian integer), sedangkan setengah(2.5) menghasilkan 1.25 — satu template, dua perilaku yang sesuai tipe.
  4. Baris 21 (void tampilkan(std::integral auto nilai)): bentuk ringkas tanpa template <...> — parameter bertanda auto yang diberi kendala. tampilkan(7) dan tampilkan('A') sama-sama sah karena char juga integral.
  5. Baris 26-27 dan 38-39: std::integral auto sebagai tipe kembalian memaksa fungsi mengembalikan tipe integral, dan variabel bertanda sama juga terkendala. static_assert(std::same_as<decltype(nilai), int>) membuktikan deduksinya tepat menghasilkan int.

Concept Baku yang Perlu Dikenal #

Header <concepts> memuat puluhan concept; empat ini muncul hampir di semua kode modern:

ConceptPertanyaan yang dijawab
std::integral<T>apakah T tipe integer?
std::floating_point<T>apakah T tipe floating point?
std::same_as<A, B>apakah A dan B tipe yang sama persis?
std::convertible_to<From, To>apakah From bisa dikonversi ke To?

Perhatikan hubungannya dengan type traits: std::integral didefinisikan berdasarkan std::is_integral_v — kita melihatnya langsung dalam pesan error di akhir chapter. Concept pada dasarnya adalah lapisan bahasa yang lebih ekspresif di atas alat pemeriksaan yang sudah kita kuasai.

Mendefinisikan concept dari nol memakai requires expression — misalnya kendala “T punya method size()” atau “a + b sah” — dan karena mekanismenya layak dibedah pelan-pelan, ia mendapat chapter tersendiri: requires Expressions.

auto dengan Concept #

Tiga posisi penulisan kendala yang kita pakai di atas punya nama masing-masing:

  • template <std::integral T> void f(T x) — kendala pada parameter template, lalu tipe dideklarasikan dari T.
  • void f(std::integral auto x) — bentuk ringkas; compiler diam-diam membuat parameter template sendiri. Keduanya identik hasilnya.
  • std::integral auto y = f(); — kendala pada deklarasi variabel atau tipe kembalian; bila fungsi mengembalikan double, pemanggil ditolak.

Bentuk auto-berkendala ini paling praktis untuk fungsi biasa dan lambda, sedangkan bentuk template <std::integral T> wajib bila T perlu dipakai lebih dari satu kali atau dijadikan tipe kembalian.

Pesan Error yang Menjelaskan Diri Sendiri #

Sekarang saatnya membuktikan klaim utama chapter ini. Aktifkan baris yang tadi kita komentar:

cpp Pemanggilan gandakan(2.5) (tidak dikompilasi)
#include <concepts>

template <std::integral T>
T gandakan(T nilai) {
    return nilai * 2;
}

int main() {
    gandakan(2.5);
}

Potongan pesan nyata dari g++ 14.2 (dipersingkat):

text
concepterr.cpp: In function 'int main()':
concepterr.cpp:9:13: error: no matching function for call to 'gandakan(double)'
    9 |     gandakan(2.5);
      |     ~~~~~~~~^~~~~
concepterr.cpp:4:3: note: candidate: 'template<class T>  requires  integral<T> T gandakan(T)'
    4 | T gandakan(T nilai) {
      |   ^~~~~~~~
concepterr.cpp:4:3: note:   template argument deduction/substitution failed:
concepterr.cpp:4:3: note: constraints not satisfied
concepterr.cpp:9:13:   required from here
    9 |     gandakan(2.5);
      |     ~~~~~~~~^~~~~
/usr/include/c++/14/concepts:107:13:   required for the satisfaction of 'integral<T>' [with T = double]
/usr/include/c++/14/concepts:107:24: note: the expression 'is_integral_v<_Tp> [with _Tp = double]' evaluated to 'false'
  107 |     concept integral = is_integral_v<_Tp>;
      |                        ^~~~~~~~~~~~~~~~~~

Bandingkan dengan pesan enable_if yang berhenti di “no matching function”. Kini compiler menelusuri rantai kendala sampai akarnya: candidate gandakan menuntut integral<T>, dan untuk T = double ekspresi is_integral_v bernilai false. Pemula bisa membaca tiga baris terakhir dan langsung paham. Tidak ada kesalahan pengetikan yang harus dicari — kendala yang tidak terpenuhi memang tidak terpenuhi.

Concept mengubah cara kita mengumumkan syarat. Langkah berikutnya adalah menulis syarat yang lebih spesifik: bukan “tipe integral”, melainkan “tipe yang punya method ini dan operasi itu”. Alatnya requires expression — topik chapter berikutnya.

Referensi resmi: cppreference — Constraints and concepts dan cppreference — std::integral .