Topik ini membahas tiga bentuk parameter yang melengkapi daftar template, yaitu lebih dari satu parameter tipe, parameter non-tipe yang menerima nilai konstanta, dan argumen default pada template. Ketiganya dipakai bersama dalam satu program kecil sehingga perannya dapat dibandingkan langsung. Setelah membaca topik ini, template dengan beberapa parameter dapat ditulis dan dibaca dengan benar.
Lebih dari satu parameter tipe#
Daftar parameter template boleh memuat lebih dari satu parameter tipe, dan setiap parameter diturunkan atau diisi secara terpisah. Bentuk ini berguna ketika satu template berurusan dengan dua tipe yang tidak harus sama, misalnya kunci bertipe std::string dan nilai bertipe int pada satu pasangan data. Konflik deduksi yang muncul pada template satu parameter tidak terjadi di sini, karena setiap parameter menampung satu tipe tersendiri.
#include <iostream>
#include <string>
// dua parameter tipe: T untuk kunci, U untuk nilai
template <typename T, typename U>
struct Pasangan {
T kunci;
U nilai;
};
// parameter non-tipe: N ditetapkan saat kompilasi
// argumen default: tanpa argumen, T menjadi int dan N menjadi 4
template <typename T = int, std::size_t N = 4>
class Baris {
public:
void isi(std::size_t posisi, const T& nilai) {
data[posisi] = nilai;
}
const T& ambil(std::size_t posisi) const {
return data[posisi];
}
static constexpr std::size_t panjang() {
return N;
}
private:
T data[N] {};
};
int main() {
Pasangan<std::string, int> umur{"Ali", 20};
std::cout << umur.kunci << ": " << umur.nilai << "\n";
Baris<double, 3> suhu;
suhu.isi(0, 24.5);
suhu.isi(1, 26.0);
suhu.isi(2, 25.5);
std::cout << "Suhu: " << suhu.ambil(0) << " " << suhu.ambil(1) << " " << suhu.ambil(2) << "\n";
std::cout << "Panjang: " << suhu.panjang() << "\n";
Baris<> bawaan;
bawaan.isi(2, 99);
std::cout << "Bawaan: " << bawaan.ambil(2) << "\n";
return 0;
}Ali: 20
Suhu: 24.5 26 25.5
Panjang: 3
Bawaan: 99Pasangan<std::string, int> mengisi T dengan std::string dan U dengan int, sehingga satu struktur mampu menampung dua tipe yang berbeda tanpa konversi apa pun. Struct sederhana seperti ini menjadi dasar bagi std::pair pada pustaka standar, yang juga mengikat dua tipe dalam satu template.
Parameter non-tipe#
Parameter non-tipe menerima nilai, bukan tipe, dan nilai itu wajib diketahui saat kompilasi. Pada Baris<double, 3>, angka 3 mengisi parameter N yang bertipe std::size_t, sehingga panjang array anggota data sudah tertentu sebelum program berjalan. Nilai N kemudian dapat dipakai di dalam kelas, misalnya oleh method panjang yang mengembalikannya tanpa menyimpan variabel tambahan.
Perbedaan parameter non-tipe dengan parameter constructor bersifat mendasar. Nilai yang dikirim ke constructor hidup saat program berjalan dan tersimpan di dalam objek, sedangkan nilai N menjadi bagian dari tipe kelasnya, sehingga Baris<double, 3> dan Baris<double, 5> adalah dua tipe yang berbeda. Karena nilainya diketahui saat kompilasi, compiler dapat menyiapkan array seukuran itu langsung di dalam objek tanpa alokasi dinamis. Sebelum C++20, tipe parameter non-tipe dibatasi pada tipe bilangan bulat, enumerasi, pointer, reference, dan std::nullptr_t, sedangkan C++20 memperluas daftar itu dengan tipe pecahan dan sebagian tipe kelas.
Parameter non-tipe dengan auto#
Sejak C++17, parameter non-tipe dapat dideklarasikan dengan auto, sehingga tipenya diturunkan dari nilai argumennya. Bentuk template <auto N> menerima 24 sebagai int dan 'z' sebagai char tanpa menuliskan tipe parameternya secara terpisah. Tipe yang diturunkan ikut menjadi bagian identitas instans, sehingga pemanggilan dengan 24 dan dengan 'z' menghasilkan dua instans yang berbeda meskipun keduanya dapat disimpan sebagai angka.
Program berikut membungkus pemanggilan bentuk tersebut. Argumennya berupa literal maupun variabel constexpr, dan keduanya diterima karena nilainya diketahui saat kompilasi.
#include <iostream>
template <auto N>
void tampilkanNilai() {
std::cout << N << "\n";
}
int main() {
tampilkanNilai<24>();
tampilkanNilai<'z'>();
constexpr int batasWaktu = 30;
tampilkanNilai<batasWaktu>();
return 0;
}24
z
30Bentuk auto mengurangi duplikasi ketika nilai konstanta yang dipakai beragam tipenya, misalnya kombinasi ukuran, batas waktu, dan karakter penanda pada satu template yang sama. Tanpa auto, setiap tipe nilai menuntut parameter tersendiri, sedangkan dengan auto satu template melayani semuanya. Batasan argumennya tetap sama dengan parameter non-tipe lainnya, yaitu wajib berupa ekspresi konstanta yang diketahui saat kompilasi.
Argumen default pada template#
Parameter template boleh diberi argumen default. Pada class template, aturan urutannya sama dengan argumen default pada fungsi, yaitu parameter yang memiliki default harus berada di belakang. Deklarasi template <typename T = int, std::size_t N = 4> memberi nilai bawaan untuk keduanya, sehingga penulisan Baris<> menghasilkan Baris<int, 4>. Pada program di atas, Baris<> bawaan; tidak menerima satu pun argumen template, tetapi tipenya tetap utuh dan panjangnya bisa dibaca lewat panjang.
Argumen default mengurangi kebisingan pada kode yang memakai nilai bawaan paling sering. Kelas yang sama tetap dapat diinstansiasi dengan argumen eksplisit kapan pun dibutuhkan, misalnya Baris<double, 3>, tanpa mengubah definisinya. Kebiasaan yang baik menetapkan default pada kombinasi yang benar-benar dominan, karena default yang jarang dipakai justru menyembunyikan konfigurasi yang seharusnya tertulis.
Argumen default juga mengatasi parameter template yang tidak dapat dideduksi dari argumen fungsinya. Template bagi di bawah menempatkan parameter Hasil di depan karena parameter itu hanya menentukan tipe kembalian dan tidak muncul pada daftar parameter fungsi. Tanpa default, setiap pemanggilan wajib menuliskan argumennya secara eksplisit, sedangkan dengan default pemanggilan biasa sudah cukup dan bentuk eksplisitnya tetap tersedia ketika hasil int memang diinginkan.
#include <iostream>
// Hasil tidak ikut dideduksi, sehingga diberi argumen default
template <typename Hasil = double, typename T>
Hasil bagi(T a, T b) {
return static_cast<Hasil>(a) / b;
}
int main() {
std::cout << bagi(7, 2) << "\n";
std::cout << bagi<int>(7, 2) << "\n";
return 0;
}3.5
3bagi(7, 2) menerima hasil 3.5 karena Hasil mengambil defaultnya, sedangkan bagi<int>(7, 2) mengisi Hasil dengan int sehingga pembagiannya dilakukan dalam bilangan bulat dan menghasilkan 3. Argumen eksplisit diisi dari kiri, sehingga penulisan bagi<int> hanya menyentuh parameter pertama. Parameter T tetap diturunkan dari argumen fungsinya sebagaimana biasa.
Kesalahan umum#
Empat kekeliruan berikut paling sering muncul pada pemakaian parameter template.
- Mengisi parameter non-tipe dengan nilai yang baru diketahui saat program berjalan, misalnya dari masukan pengguna. Nilainya wajib berupa ekspresi konstanta, sehingga compiler menolaknya dengan pesan yang menyebut konstanta yang tidak valid. Perbaikannya menggunakan variabel anggota yang diisi saat program berjalan atau memakai container dinamis seperti
std::vector. - Menempatkan parameter yang memiliki default di depan parameter wajib pada class template. Urutan semacam itu ditolak compiler karena parameter wajib tidak mungkin dituliskan setelah default. Susun ulang daftarnya sehingga parameter dengan default berada di belakang.
- Mengira
Baris<double, 3>danBaris<double, 5>dapat saling diisi karenaT-nya sama. Keduanya adalah dua tipe yang berbeda karenaNmenjadi bagian tipenya. Pindahkan data antar keduanya melalui method atau perulangan eksplisit bila memang diperlukan. - Mengira argumen default membuat seluruh pemakaian template bermuara pada satu tipe.
Baris<>danBaris<double, 3>tetap menghasilkan dua instans yang berbeda, dan setiap kombinasi argumen yang dipakai menghasilkan kode tersendiri. Perhatikan konsekuensi ini pada template yang didefinisikan di header, karena setiap kombinasi menambah ukuran program yang dikompilasi.
Ringkasan#
- Daftar parameter template boleh memuat beberapa parameter tipe, dan setiap parameter diisi atau diturunkan secara terpisah.
- Parameter non-tipe menerima nilai konstanta yang diketahui saat kompilasi, dan nilai itu menjadi bagian dari tipe instansnya.
- Argumen default pada template mengikuti aturan urutan yang sama dengan fungsi, yaitu ditempatkan di belakang, dan kurung sudut tetap ditulis meskipun semua parameter bertdefault.
- Parameter non-tipe dapat dideklarasikan dengan
autosejak C++17, sehingga tipenya diturunkan dari nilai konstanta yang dikirim.
Referensi#
- Template parameters and template arguments — cppreference — Aturan lengkap parameter tipe, parameter non-tipe, dan argumen default template.
- Non-type template parameters — LearnCpp — Tutorial parameter non-tipe beserta batas-batasnya.
- Templates — ISO C++ — Kumpulan tanya jawab resmi komunitas C++ tentang perilaku template.