Function template mencetak fungsi; class template mencetak class. Pola ini sebenarnya sudah kita pakai sejak lama tanpa disadari: std::vector<int> dan std::vector<std::string> adalah dua class berbeda yang lahir dari satu cetak biru std::vector<T>. Kali ini kita menulis cetak biru versi sendiri — sebuah Kotak<T> yang bisa menampung tipe apa pun — lalu membedahkan cara mendefinisikan member function di dalam dan di luar class, serta bagaimana compiler membuat class sebenarnya dari template. Setelah chapter ini, setiap std::vector, std::array, atau std::optional yang kita tulis akan terasa jauh lebih transparan.
Class Template Pertama: Kotak<T> #
Struktur dasarnya mirip class biasa; yang berubah hanyalah baris pembuka template <typename T> dan pemakaian T sebagai tipe di dalam class. Tipe sebenarnya baru ditentukan saat kita menuliskan Kotak<int>, Kotak<std::string>, dan seterusnya — di situlah compiler menginstansiasi class.
#include <iostream>
#include <string>
#include <vector>
template <typename T>
class Kotak {
public:
Kotak() : isi_{} {}
explicit Kotak(const T& isi) : isi_{isi} {}
// Member function didefinisikan di dalam class (implisit inline)
void setIsi(const T& isi) { isi_ = isi; }
const T& isi() const { return isi_; }
// Dideklarasikan di dalam, didefinisikan di luar class
void cetak() const;
private:
T isi_;
};
// Definisi di luar: wajib menulis Kotak<T>:: sebelum nama fungsinya
template <typename T>
void Kotak<T>::cetak() const {
std::cout << "Kotak berisi: " << isi_ << "\n";
}
// Instansiasi eksplisit: paksa compiler membuat semua member Kotak<double>
template class Kotak<double>;
int main() {
Kotak<int> angka{42};
Kotak<std::string> teks{"Buku Catatan"};
Kotak<std::vector<int>> daftar;
angka.cetak();
teks.cetak();
std::cout << "daftar kosong: " << std::boolalpha << daftar.isi().empty() << "\n";
angka.setIsi(100);
std::cout << "isi angka sekarang: " << angka.isi() << "\n";
return 0;
}Output yang Dihasilkan:
Kotak berisi: 42
Kotak berisi: Buku Catatan
daftar kosong: true
isi angka sekarang: 100Mari kita bedah bagian pentingnya:
- Baris 5-20 (
template <typename T> class Kotak): cetak biru class dengan satu data member bertipeT. Dua constructor di baris 8-9 menerimaconst T&sehingga isi kotak dikirim tanpa penyalinan berlebih. - Baris 24 (
void Kotak<T>::cetak() const): definisi member function di luar class. Sintaksnya berlapis dua:template <typename T>menyatakan bahwa kita mendefinisikan anggota dari template, laluKotak<T>::menyatakan class milik siapa. - Baris 29 (
template class Kotak<double>;): instansiasi eksplisit — kita memerintahkan compiler membuat classKotak<double>lengkap dengan semua membernya di titik ini, walau belum ada satu pun object yang memakainya. - Baris 32-34: tiga instansiasi implisit yang berbeda:
Kotak<int>,Kotak<std::string>, danKotak<std::vector<int>>. Perhatikan baris 34 — penutup>>tanpa spasi sah sejak C++11, sehingga template bisa bersarang dengan rapi. - Baris 38:
daftar.isi().empty()merangkai dua pemanggilan:isi()mengembalikanconst std::vector<int>&, laluempty()milik vector memeriksa kekosongannya. Template berlapis seperti ini sehari-hari di kode nyata.
Member Function: di Dalam vs di Luar Class #
Kedua gaya menghasilkan program yang sama; pilihan biasanya soal keterbacaan. Definisi di dalam class otomatis inline dan ringkas — cocok untuk fungsi satu-dua baris. Definisi di luar menjaga badan class tetap sekilas pandang seperti daftar isi — cocok untuk fungsi panjang, dan inilah gaya yang dipakai standard library di file header-nya.
| Gaya | Sintaks pembuka | Catatan |
|---|---|---|
| Di dalam class | void setIsi(const T& isi) { ... } | implisit inline, tanpa awalan apa pun |
| Di luar class | template <typename T> void Kotak<T>::cetak() const { ... } | wajib ada template <typename T> dan Kotak<T>:: |
Lupa awalan template <typename T> pada definisi luar adalah kesalahan klasik — compiler akan mencari class Kotak yang bukan template dan menolak kode kita. Sementara itu, Kotak<T>:: tanpa template <typename T> di atasnya juga ditolak karena T dianggap nama yang belum dikenal.
Instansiasi Eksplisit #
Deklarasi template class Kotak<double>; terdengar teknis, tetapi dampaknya konkret: compiler langsung membuat semua member Kotak<double> — termasuk cetak(), setIsi(), dan kedua constructor — walau belum ada yang memanggilnya. Bila ada satu saja member yang gagal dikompilasi untuk double, file ini ditolak di baris 29, bukan di baris pemanggilan. Sebaliknya, tanpa instansiasi eksplisit, class Kotak<double> baru lahir saat pertama dipakai, dan hanya member yang dipanggil yang dibuat.
Kapan fitur ini berguna? Dalam proyek besar dengan pemisahan header dan source file, template biasanya harus ditulis utuh di header (karena compiler harus melihat definisinya saat instansiasi). Instansiasi eksplisit memberi jalan keluar: definisi template ditaruh di file .cpp, lalu semua tipe yang dibutuhkan diinstansiasi eksplisit di sana — pola yang kita bedah tuntas pada Section Build System dan Project Structure. Untuk sekarang, cukup kenali sintaksnya dan makna “buatkan sekarang juga” yang dibawanya.
CTAD Lebih Dalam: Deduction Guide #
Catatan C++17 di awal chapter menyebut CTAD (Kotak kotak{42}; tanpa <int>); sekarang kita lihat bagaimana deduksinya bekerja dan kapan ia butuh bantuan. Compiler mencari acuan deduksi dari constructor yang dipanggil: Kotak{42} memanggil Kotak(const int&), dan dari situ tipe T ditebak sebagai int. Selama constructor memberi petunjuk yang jelas, CTAD bekerja tanpa kita tulis apa pun.
Kasus berubah ketika tipe yang harus ditebak tidak muncul di parameter constructor mana pun. Di situ kita menulis deduction guide — baris tanda tangan palsu yang memberi tahu compiler “dari argumen-argumen ini, tebak template sebegini”:
#include <iostream>
#include <string>
template <typename T>
struct Kotak {
T isi;
explicit Kotak(const T& nilai) : isi(nilai) {}
};
// Pasangan<A, B> tidak punya constructor berparameter yang bisa jadi acuan,
// sehingga kita memberi deduction guide eksplisit.
template <typename A, typename B>
struct Pasangan {
A awal;
B akhir;
};
template <typename A, typename B>
Pasangan(A, B) -> Pasangan<A, B>;
int main() {
Kotak kotakInt{42}; // CTAD: Kotak<int>
Kotak kotakTeks{std::string("halo")}; // CTAD: Kotak<std::string>
Pasangan p{1, 2.5}; // lewat deduction guide
std::cout << kotakInt.isi << " " << kotakTeks.isi << "\n";
std::cout << p.awal << " " << p.akhir << "\n";
return 0;
}Output yang Dihasilkan:
42 halo
1 2.5Baris yang perlu dibedakan ada dua. Kotak kotakInt{42} sah tanpa guide karena constructor-nya membawa T di parameternya — compiler cukup membaca tanda tangan. Pasangan p{1, 2.5} baru sah setelah guide Pasangan(A, B) -> Pasangan<A, B> ditulis: tanpa itu, compiler tidak punya cara mengaitkan {1, 2.5} dengan A dan B, dan instansiasi ditolak. Agregat sederhana yang bertipe murni (seperti Pasangan di atas) sebenarnya juga menerima deduksi agregat bawaan C++20; guide eksplisit tetap kita tulis karena bentuknya yang menjadi pola baku pada class template dengan constructor sendiri.
std::vector: Class Template Paling Terkenal #
Seluruh kegunaan Kotak<T> di atas sebenarnya sudah kita rasakan lewat std::vector<T> — class template yang paling sering ditulis programmer C++. std::vector<int> dan std::vector<double> adalah dua class berbeda yang tidak bisa dipertukarkan; itulah alasan std::vector<int> tidak mau menerima push_back(2.5) tanpa konversi yang jelas. Pada Section Standard Library kita memperlakukannya sebagai kotak hitam; kini kita tahu isi kotaknya: data member yang tumbuh dinamis, member function seperti push_back, size, dan empty — semuanya definisi template yang diinstansiasi per tipe.
Pola yang sama berlaku untuk std::array<T, N> yang menaruh satu parameter bukan tipe — angka N — di daftar templatenya. Parameter semacam itu adalah topik chapter berikutnya: non-type template parameter, pintu masuk ke template yang membawa nilai, bukan hanya tipe.
Referensi resmi: cppreference — Class template dan cppreference — std::vector .