Template kita selama ini menerima tipe apa pun lalu “berharap yang terbaik” — bila tipe tidak mendukung operasi yang dipakai, error baru muncul saat instansiasi, panjang dan sukar dibaca. Header <type_traits> memberi kita alat untuk berhenti berharap dan mulai memeriksa: serangkaian type trait, yaitu template yang menjawab pertanyaan tentang tipe — integral atau bukan? pointer atau bukan? sama atau berbeda? — semuanya pada saat kompilasi. Chapter ini mengenalkan trait yang paling sering dipakai dan menunjukkan cara menggunakannya untuk menyeleksi tipe yang boleh memanggil template kita.
Apa Itu Type Trait #
Type trait adalah template class atau template variable yang menghasilkan konstanta atau tipe berdasarkan tipe yang kita kirim. Dua akhiran mewakili dua kelompok: _v menghasilkan nilai bool (std::is_integral_v<int> → true), sedangkan _t menghasilkan tipe baru (std::decay_t<const char(&)[5]> → const char*). Trait yang kita pakai di chapter ini:
| Trait | Pertanyaan atau hasil |
|---|---|
std::is_integral_v<T> | apakah T tipe integer (int, long, char, …)? |
std::is_pointer_v<T> | apakah T pointer? |
std::is_same_v<A, B> | apakah A dan B tipe yang sama persis? |
std::remove_reference_t<T> | T tanpa & dan && |
std::decay_t<T> | T seperti yang dilihat compiler saat pass-by-value |
Semua trait murni compile time: tidak ada byte yang dihitung saat program berjalan.
Memeriksa Tipe di Dalam Template #
Contoh berikut memakai lima trait dalam satu program: dua static_assert sebagai pemeriksa tetap, satu template yang bersikap berbeda untuk pointer, dan pengujian decay:
#include <iostream>
#include <string>
#include <type_traits>
// static_assert: pemeriksaan tipe pada saat kompilasi
static_assert(std::is_integral_v<int>, "int harus integral");
static_assert(!std::is_pointer_v<std::string>, "std::string bukan pointer");
// Template yang bersikap beda untuk pointer dan bukan pointer
template <typename T>
void periksa(const T& nilai) {
if constexpr (std::is_pointer_v<T>) { // if constexpr: dibedah di chapter berikutnya
std::cout << "pointer ke tipe " << sizeof(*nilai) << " byte\n";
} else {
std::cout << "bukan pointer, ukuran " << sizeof(T) << " byte\n";
}
}
// remove_reference_t: kembalikan tipe asal dari sebuah reference
template <typename T>
void laporanTipe() {
using Bersih = std::remove_reference_t<T>;
std::cout << "setelah bersih, integral? " << std::boolalpha
<< std::is_integral_v<Bersih> << "\n";
}
// decay_t: tipe yang tersisa bila nilai dikirim by value
template <typename A, typename B>
void bandingkanTipe(A, B) { // nilai tidak dipakai; yang diperiksa hanya tipenya
using A_Bersih = std::decay_t<A>;
using B_Bersih = std::decay_t<B>;
std::cout << "sama setelah decay? " << std::boolalpha
<< std::is_same_v<A_Bersih, B_Bersih> << "\n";
}
// enable_if: template hanya tersedia untuk tipe integral (intro SFINAE)
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> gandakan(T nilai) {
return nilai * 2;
}
int main() {
int angka = 7;
int* penunjuk = &angka;
periksa(angka);
periksa(penunjuk);
laporanTipe<int&>();
laporanTipe<double>();
bandingkanTipe("halo", "hai"); // array beda ukuran -> decay ke const char*
bandingkanTipe(42, 42.0);
std::cout << "gandakan(21) = " << gandakan(21) << "\n";
return 0;
}Output yang Dihasilkan:
bukan pointer, ukuran 4 byte
pointer ke tipe 4 byte
setelah bersih, integral? true
setelah bersih, integral? false
sama setelah decay? true
sama setelah decay? false
gandakan(21) = 42Bedah baris demi baris:
- Baris 6-7 (
static_assert(...)): pernyataan yang diverifikasi compiler sebelum program dibuat. Bila kondisinya salah, kompilasi gagal dengan pesan yang kita tulis sendiri. Karenaintmemang integral danstd::stringbukan pointer, keduanya lolos tanpa suara. - Baris 11-17:
periksabertanyais_pointer_v<T>lalu memilih cabang pada waktu kompilasi lewatif constexpr— cabang lain tidak ikut dikompilasi, sehinggasizeof(*nilai)aman ditulis meskipun tipe non-pointer tidak bisa di-dereference. Detailnya jadi materi Chapter Compile Time Constraints. - Baris 22 (
using Bersih = std::remove_reference_t<T>):laporanTipe<int&>()mengirimint&; trait menghapus referensinya sehingga pemeriksaanis_integral_vjatuh padaintyang bersih — hasilnyatrue. - Baris 30-33:
decay_tmeniru pengurangan yang terjadi saat argumen dikirim by value: array meluruh menjadi pointer, referensi lepas,constatas nilai ikut lepas."halo"(const char[5]) dan"hai"(const char[4]) sama-sama meluruh keconst char*, sehingga dianggap tipe sama;intdandoubletetap berbeda. - Baris 38-39 (
std::enable_if_t<...>): return type yang bersyarat —enable_if_t<kondisi, T>menghasilkanTbila kondisi benar, dan tidak menghasilkan apa pun bila salah. Fungsigandakankini hanya tersedia untuk tipe integral;gandakan(21)menghasilkan 42.
decay_t: Tipe yang Dilihat Compiler #
decay_t layak didalami sendiri karena menjawab pertanyaan yang sering membingungkan: mengapa template menganggap "halo" bukan const char[5]? Jawabannya, parameter template diduksi dari ekspresi, dan ekspresi bertipe array otomatis meluruh (decay) menjadi pointer — persis aturan yang sama dengan pass-by-value pada fungsi biasa. Trait decay_t mengabstraksi aturan itu sehingga kita bisa menyimulasikan “bentuk tipe saat sampai di fungsi” tanpa menebak-nebak. Pasangan klasiknya adalah remove_reference_t (melepas &/&& saja) dan remove_cv_t (melepas const/volatile); decay_t kira-kira melakukan ketiganya sekaligus.
enable_if: Gerbang Seleksi Tipe #
Mekanisme di balik enable_if_t disebut SFINAE — Substitution Failure Is Not An Error: bila penggantian template gagal membentuk tipe yang sah, kandidat itu diabaikan tanpa menghentikan kompilasi; bila tidak ada kandidat lain yang tersisa, barulah error muncul. Coba kirim nilai non-integral ke gandakan:
#include <type_traits>
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> gandakan(T nilai) {
return nilai * 2;
}
int main() {
gandakan(2.5);
}Pesan nyata dari g++ 14.2:
enableif.cpp: In function 'int main()':
enableif.cpp:9:13: error: no matching function for call to 'gandakan(double)'
9 | gandakan(2.5);
| ~~~~~~~~^~~~~
enableif.cpp:4:44: note: candidate: 'template<class T> std::enable_if_t<((bool)is_integral_v<T>), T> gandakan(T)'
4 | std::enable_if_t<std::is_integral_v<T>, T> gandakan(T nilai) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^Error-nya tepat (“no matching function”) tetapi alasan mengapa fungsi itu tidak cocok tidak terlihat sama sekali. Di sinilah SFINAE mulai terasa melelahkan: kode yang rumit, pesan yang buntu. C++20 mengganti seluruh gaya ini dengan concepts — kendala yang ditulis sebagai bagian dari template dan menghasilkan pesan error yang menjelaskan dirinya. Itu persis judul chapter berikutnya.
Referensi resmi: cppreference — Type traits dan cppreference — std::decay .