Topik ini membahas specialization, yaitu penyediaan definisi khusus untuk argumen template tertentu di samping definisi umumnya. Pembahasan mencakup specialization penuh pada function template, specialization penuh dan parsial pada class template, beserta alasan mengapa fitur ini diperlukan. Contohnya menggunakan satu program utuh yang memuat kedua bentuk tersebut beserta keluaran aslinya.
Mengapa specialization diperlukan#
Sebuah template ditulis untuk kelompok tipe yang luas, tetapi tidak semua tipe dilayani dengan cara yang sama. Fungsi sebutkan pada contoh di bawah mengembalikan deskripsi singkat sebuah nilai, dan perilaku generiknya sengaja dibuat polos, yaitu mengembalikan kata umum untuk tipe apa pun. Tipe bool lebih informatif bila dijawab dengan ya atau tidak, sedangkan std::string memuat teks yang layak ditampilkan. Menyalin seluruh template untuk setiap tipe semacam itu berarti menduplikasi kode, sedangkan specialization memberi tahu compiler bahwa satu argumen tertentu memiliki definisinya sendiri.
#include <iostream>
#include <string>
template <typename T>
std::string sebutkan(const T& nilai) {
return "umum";
}
template <>
std::string sebutkan<bool>(const bool& nilai) {
return nilai ? "ya" : "tidak";
}
template <>
std::string sebutkan<std::string>(const std::string& nilai) {
return "teks: " + nilai;
}
template <typename T>
class Kotak {
public:
explicit Kotak(const T& isi) : isi_(isi) {}
T ambil() const {
return isi_;
}
private:
T isi_;
};
template <typename T>
class Kotak<T*> {
public:
explicit Kotak(T* isi) : isi_(isi) {}
bool kosong() const {
return isi_ == nullptr;
}
private:
T* isi_;
};
int main() {
std::cout << sebutkan(true) << "\n";
std::cout << sebutkan(false) << "\n";
std::cout << sebutkan(std::string("catatan")) << "\n";
std::cout << sebutkan(42) << "\n";
Kotak<int> biasa(7);
std::cout << biasa.ambil() << "\n";
int nilai = 24;
Kotak<int*> penunjuk(&nilai);
std::cout << std::boolalpha << penunjuk.kosong() << "\n";
Kotak<int*> kosong(nullptr);
std::cout << kosong.kosong() << "\n";
return 0;
}ya
tidak
teks: catatan
umum
7
false
trueKeluaran menunjukkan pemilihan definisinya. Pemanggilan sebutkan(true) dan sebutkan(false) memakai specialization untuk bool, pemanggilan dengan std::string memakai specialization miliknya, sedangkan sebutkan(42) tetap memakai template utama dan menghasilkan umum. Specialization dipilih saat kompilasi berdasarkan argumen template yang cocok persis, bukan berdasarkan nilai yang dikirim.
Specialization penuh#
Specialization penuh menggantikan seluruh template untuk satu argumen yang disebut secara eksplisit, dan bentuknya selalu diawali template <> tanpa parameter apa pun. Pada contoh tersebut, template <> std::string sebutkan<bool>(const bool& nilai) mengikat specialization untuk T bertipe bool, sedangkan pada class template, bentuk hipotetis Kotak<int> akan mengikat kelas Kotak untuk int. Definisi specialization harus ditulis setelah template utamanya dan sebelum dipakai, karena compiler baru dapat mencocokkannya ketika keduanya sudah terlihat.
Specialization penuh bebas menulis anggota yang berbeda dari template utamanya. Kelas yang dispesialisasikan tidak wajib meniru daftar method kelas umumnya, sebab keduanya hanya berbagi nama. Kelonggaran inilah yang dipakai pustaka standar, misalnya std::vector<bool> yang penyimpanannya dipadatkan menjadi satu bit per nilai dan std::hash yang menyediakan spesialisasi untuk tipe-tipe dasar.
Specialization juga dapat menyasar satu member function dari suatu instans tanpa menggantikan seluruh kelasnya. Dengan memakai Kotak dari program di atas, bentuk pada potongan berikut menggantikan hanya method ambil milik Kotak<bool>, sedangkan anggota lainnya tetap berasal dari template utamanya. Aturan urutannya sama seperti specialization lainnya, yaitu ditulis setelah definisi class template dan sebelum pemakaian pertama.
// tidak-dijalankan (fragmen yang merujuk Kotak pada program utama)
template <>
bool Kotak<bool>::ambil() const {
return isi_;
}Teknik ini berguna ketika satu instans memerlukan perlakuan khusus pada satu operasi saja, misalnya perilaku pencetakan yang berbeda untuk satu tipe tertentu. Seluruh definisi kelas tidak perlu disalin, sehingga kode yang dipelihara tetap satu. Kebutuhan yang lebih luas daripada satu operasi biasanya lebih tepat dijawab dengan specialization penuh atas kelasnya.
Specialization parsial untuk class template#
Specialization parsial mempertahankan sebagian parameternya dan hanya menambatkan pola tertentu. Pada contoh tersebut, template <typename T> class Kotak<T*> menambatkan pola pointer tanpa menentukan tipe yang ditunjuk, sehingga Kotak<int*>, Kotak<double*>, dan bentuk pointer lainnya semuanya dilayani. Kelas ini menawarkan method kosong yang relevan untuk pointer, sedangkan Kotak<int> pada main tetap memakai kelas umumnya karena tipenya bukan pointer.
Specialization parsial hanya tersedia untuk class template. Function template tidak mendukungnya, dan kebutuhan semacam itu dijawab dengan overloading, misalnya menulis fungsi biasa sebutkan(const std::string&) yang dipilih compiler sebelum template yang lebih umum. Pembedaan ini penting diingat karena sintaks kedua fitur mirip, tetapi aturan pemilihannya berbeda.
Pemilihan specialization parsial yang paling cocok#
Beberapa specialization parsial dapat cocok dengan satu argumen template yang sama, dan compiler memilih yang paling terspesialisasi, yaitu pola yang paling sempit cakupannya. Program berikut memuat dua specialization parsial atas template Nama, yaitu Nama<T*> untuk semua pointer dan Nama<const T*> yang lebih sempit karena hanya menangkap pointer ke konstanta. Aturan ini menjawab pertanyaan yang wajar muncul begitu lebih dari satu pola diperbolehkan, yaitu pola mana yang diutamakan ketika kecocokannya tumpang tindih.
#include <iostream>
template <typename T>
struct Nama {
static const char* nilai() { return "umum"; }
};
template <typename T>
struct Nama<T*> {
static const char* nilai() { return "pointer"; }
};
template <typename T>
struct Nama<const T*> {
static const char* nilai() { return "pointer ke konstanta"; }
};
int main() {
std::cout << Nama<int>::nilai() << "\n";
std::cout << Nama<int*>::nilai() << "\n";
std::cout << Nama<const int*>::nilai() << "\n";
return 0;
}umum
pointer
pointer ke konstantaUntuk int*, hanya pola Nama<T*> yang cocok, sedangkan untuk const int* kedua pola sama-sama cocok dan pola yang lebih spesifik dipilih. Apabila dua specialization sama-sama cocok dan tidak ada yang lebih spesifik satu sama lain, compiler melaporkan error ambiguitas alih-alih memilih diam-diam. Kondisi semacam itu lazim terjadi ketika dua pola saling menyilang, misalnya satu pola menambatkan parameter pertama dan pola lain menambatkan parameter kedua pada template dua parameter. Perbaikannya menyusun ulang pola sehingga urutan kecocokannya jelas atau menambahkan satu specialization lagi untuk kombinasi yang dimaksud.
Kesalahan umum#
Empat kekeliruan berikut paling sering muncul saat menulis specialization.
- Mencoba specialization parsial pada function template dengan sintaks
template <>yang disalin dari specialization penuh. Compiler menolaknya karena bentuk tersebut memang tidak ada pada bahasanya. Perbaikannya menulis overloading biasa dengan tipe parameter yang lebih spesifik. - Melupakan
template <>di depan definisi specialization. Tanpa bagian itu, compiler membaca barisnya sebagai deklarasi fungsi atau kelas baru yang bertabrakan dengan template utamanya. Awali setiap specialization penuh dengantemplate <>. - Menulis daftar parameter specialization yang tidak cocok dengan template utamanya. Argumen yang disebut harus mengisi seluruh parameter utama, dan tanda tangan definisinya harus selaras dengan aslinya. Cocokkan bentuk parameternya dengan template utama sebelum menyesuaikan badannya.
- Mengira specialization yang ditulis untuk
std::stringikut melayani string literal seperti"catatan". Deduksi pada pemanggilan semacam itu menghasilkan tipe array karakter, bukanstd::string, sehingga template utamanya yang dipilih. Kirim argumen yang sudah berupastd::stringatau tulis overloading dengan parameterconst char*bila bentuk literalnya perlu dilayani.
Ringkasan#
- Specialization memberi definisi khusus untuk argumen template tertentu, dan pemilihannya dilakukan saat kompilasi berdasarkan kecocokan argumen.
- Specialization penuh ditulis dengan
template <>untuk satu argumen eksplisit, sedangkan specialization parsial mempertahankan parameter dan menambatkan pola sepertiT*. - Specialization parsial hanya tersedia untuk class template, sedangkan function template memakai overloading untuk kebutuhan yang sama.
- Ketika beberapa specialization parsial cocok, yang paling terspesialisasi dipilih, dan kecocokan yang tidak dapat diperbandingkan dilaporkan sebagai error ambiguitas.
Referensi#
- Template specialization — cppreference — Aturan specialization penuh untuk template fungsi dan kelas.
- Partial specialization — cppreference — Aturan specialization parsial pada class template.
- Function template specialization — LearnCpp — Tutorial specialization function template beserta kaitannya dengan overloading.