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:

TraitPertanyaan 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:

cpp
#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:

text
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) = 42

Bedah baris demi baris:

  1. Baris 6-7 (static_assert(...)): pernyataan yang diverifikasi compiler sebelum program dibuat. Bila kondisinya salah, kompilasi gagal dengan pesan yang kita tulis sendiri. Karena int memang integral dan std::string bukan pointer, keduanya lolos tanpa suara.
  2. Baris 11-17: periksa bertanya is_pointer_v<T> lalu memilih cabang pada waktu kompilasi lewat if constexpr — cabang lain tidak ikut dikompilasi, sehingga sizeof(*nilai) aman ditulis meskipun tipe non-pointer tidak bisa di-dereference. Detailnya jadi materi Chapter Compile Time Constraints.
  3. Baris 22 (using Bersih = std::remove_reference_t<T>): laporanTipe<int&>() mengirim int&; trait menghapus referensinya sehingga pemeriksaan is_integral_v jatuh pada int yang bersih — hasilnya true.
  4. Baris 30-33: decay_t meniru pengurangan yang terjadi saat argumen dikirim by value: array meluruh menjadi pointer, referensi lepas, const atas nilai ikut lepas. "halo" (const char[5]) dan "hai" (const char[4]) sama-sama meluruh ke const char*, sehingga dianggap tipe sama; int dan double tetap berbeda.
  5. Baris 38-39 (std::enable_if_t<...>): return type yang bersyarat — enable_if_t<kondisi, T> menghasilkan T bila kondisi benar, dan tidak menghasilkan apa pun bila salah. Fungsi gandakan kini 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:

cpp Pemanggilan gandakan(2.5) (tidak dikompilasi)
#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:

text
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 .