Full specialization menolong hanya bila tipe yang butuh pengecualian sudah pasti — satu tipe, satu definisi. Yang lebih sering dibutuhkan adalah pengecualian untuk keluarga tipe: semua pointer, semua const T, semua std::pair<A, B>. Di sinilah partial specialization bekerja: kita tetap menyisakan parameter template, tetapi mempersempit pola argumennya. Fitur ini hanya tersedia untuk class template — dan menjelaskan kenapa, sekaligus solusi khasnya untuk function template, adalah inti chapter ini.

Partial Specialization pada Class Template #

Deklarasinya terlihat seperti full specialization, tetapi nama class belum diisi penuh: Kotak<T*> masih menyisakan T. Compiler memilih versi yang paling cocok dengan argumen pemanggil — kalau tidak ada yang cocok, template umum yang dipakai.

cpp
#include <iostream>
#include <string>
#include <utility>

// Template umum: satu parameter
template <typename T>
class Kotak {
public:
    explicit Kotak(const T& isi) : isi_{isi} {}
    void perkenalan() const {
        std::cout << "Kotak<T> umum\n";
    }

private:
    T isi_;
};

// Partial specialization 1: T boleh apa pun, asal berupa pointer
template <typename T>
class Kotak<T*> {
public:
    explicit Kotak(T* isi) : isi_{isi} {}
    T& dereferensi() const { return *isi_; }
    void perkenalan() const {
        std::cout << "Kotak<T*> (pointer), tipe yang ditunjuk berukuran "
                  << sizeof(T) << " byte\n";
    }

private:
    T* isi_;
};

// Partial specialization 2: argumennya std::pair
template <typename A, typename B>
class Kotak<std::pair<A, B>> {
public:
    explicit Kotak(const std::pair<A, B>& isi) : isi_{isi} {}
    void perkenalan() const {
        std::cout << "Kotak<pair>, pasangan " << isi_.first
                  << " dan " << isi_.second << "\n";
    }

private:
    std::pair<A, B> isi_;
};

int main() {
    Kotak<int> biasa{7};
    biasa.perkenalan();

    int angka = 21;
    Kotak<int*> p{&angka};
    p.perkenalan();
    std::cout << "dereferensi: " << p.dereferensi() << "\n";

    Kotak<std::pair<int, std::string>> pasangan{std::make_pair(5, std::string("bintang"))};
    pasangan.perkenalan();

    return 0;
}

Output yang Dihasilkan:

text
Kotak<T> umum
Kotak<T*> (pointer), tipe yang ditunjuk berukuran 4 byte
dereferensi: 21
Kotak<pair>, pasangan 5 dan bintang

Bedah baris demi baris:

  1. Baris 6-16: template umum dengan satu parameter. Kotak<int> pada baris 48 tidak cocok dengan pola pointer maupun std::pair, sehingga jatuh ke sini.
  2. Baris 19-20 (template <typename T> class Kotak<T*>): partial specialization pertama. T tetap bebas, tetapi pola T* mengunci bentuk argumen: setiap Kotak<int*>, Kotak<double*>, bahkan Kotak<const char*> jatuh ke sini. Catatan menarik: untuk Kotak<const int*>, yang terpencil adalah T = const int — pola T* tetap terpenuhi.
  3. Baris 23: member dereferensi() hanya masuk akal untuk pointer; inilah keuntungan spesialisasi — class versi pointer boleh punya antarmuka sendiri tanpa mengganggu versi lain.
  4. Baris 34-35 (template <typename A, typename B> class Kotak<std::pair<A, B>>): partial specialization kedua membuktikan bahwa daftar parameter boleh berbeda dari template asalnya — dari satu parameter menjadi dua, karena pola std::pair<A, B> butuh dua “slot”.
  5. Baris 52 dan 56: dua instansiasi yang masing-masing terpilih ke spesialisasi yang cocok; sizeof(int) menghasilkan 4 byte di platform ini.

Ketika lebih dari satu spesialisasi cocok (misalnya bila kita menambahkan Kotak<T**> lalu memanggil dengan int**), compiler memilih yang paling spesifik; kalau dua kandidat sama-sama spesifik, kompilasi ditolak karena ambigu.

Member Specialization #

Tidak selalu perlu mengganti seluruh class. Seorang member function dari template bisa dispesialisasi sendirian — sisanya tetap mengikuti template umum. Pola ini hemat dan jarang diketahui pemula:

cpp
#include <iostream>
#include <string>

template <typename T>
class Laci {
public:
    void isiDengan(const T& nilai) { data_ = nilai; }
    void laporkan() const;  // member ini akan dispesialisasi untuk satu tipe

private:
    T data_;
};

// Definisi umum member di luar class
template <typename T>
void Laci<T>::laporkan() const {
    std::cout << "Laci umum, isi: " << data_ << "\n";
}

// Member specialization: hanya Laci<bool>::laporkan yang berbeda perilaku
template <>
void Laci<bool>::laporkan() const {
    std::cout << "Laci<bool>, isi: " << (data_ ? "AKTIF" : "NONAKTIF") << "\n";
}

int main() {
    Laci<int> laciAngka;
    laciAngka.isiDengan(9);
    laciAngka.laporkan();

    Laci<std::string> laciTeks;
    laciTeks.isiDengan("catatan");
    laciTeks.laporkan();

    Laci<bool> laciStatus;
    laciStatus.isiDengan(true);
    laciStatus.laporkan();

    return 0;
}

Output yang Dihasilkan:

text
Laci umum, isi: 9
Laci umum, isi: catatan
Laci<bool>, isi: AKTIF

Baris 21-24 adalah intinya: template <> void Laci<bool>::laporkan() mengganti perilaku satu member untuk satu tipe saja. Class Laci<bool> secara keseluruhan tetap dibangun dari template umum — data member, constructor implisit, dan isiDengan() semuanya sama seperti Laci<int>. Bandingkan dengan full specialization class, yang memaksa kita menulis ulang seluruh isi class.

Function Template Tidak Bisa Partial #

Standar menutup satu pintu: function template tidak mendukung partial specialization. Percobaan berikut ditolak compiler:

cpp Percobaan partial specialization function template (tidak dikompilasi)
template <typename T>
void proses(T nilai) {}

// dicoba: partial specialization untuk semua pointer — TIDAK SAH
template <typename T>
void proses<T*>(T nilai) {}

Pesan nyata dari g++ 14.2:

text
pf.cpp:6:6: error: non-class, non-variable partial specialization 'proses<T*>' is not allowed
    6 | void proses<T*>(T nilai) {}
      |      ^~~~~~~~~~
pf.cpp:6:6: error: redefinition of 'template<class T> void proses(T)'
pf.cpp:2:6: note: 'template<class T> void proses(T)' previously declared here
    2 | void proses(T nilai) {}
      |      ^~~~~~

Alasannya historis sekaligus teknis: mekanisme resolusi function template sudah memiliki overloading yang mengerjakan tugas yang sama, dan menggabungkan keduanya membuat aturan pemilihan berbelit. Jadi solusinya jelas — tulis overload:

cpp
#include <iostream>

// Solusi untuk function template: overloading, bukan partial specialization
template <typename T>
void proses(T nilai) {
    std::cout << "versi umum: " << nilai << "\n";
}

// Overload khusus pointer: signature berbeda, bukan spesialisasi
template <typename T>
void proses(T* nilai) {
    std::cout << "versi pointer, isi yang ditunjuk: " << *nilai << "\n";
}

int main() {
    int x = 7;
    proses(x);   // pilih versi umum
    proses(&x);  // pilih versi pointer

    return 0;
}

Output yang Dihasilkan:

text
versi umum: 7
versi pointer, isi yang ditunjuk: 7

proses(T* nilai) pada baris 10-13 bukan spesialisasi — ia template kedua yang berdiri sendiri dengan signature berbeda. Proses resolusi overloading lalu memilih versi pointer bila argumennya pointer, versi umum bila bukan. Hasil akhirnya identik dengan partial specialization yang kita inginkan, tanpa melanggar aturan.

Dengan class yang kini bisa “menyesuaikan diri” per keluarga tipe, alat generik kita sudah lengkap untuk kasus tunggal. Persoalan berikutnya: bagaimana bila jumlah argumen tak terbatas — nol, satu, atau sepuluh tipe sekaligus? Chapter Variadic Templates membuka jawabannya.

Referensi resmi: cppreference — Partial specialization dan cppreference — Function template .