Bagikan melalui


Templat (C++)

Templat adalah dasar untuk pemrograman generik di C++. Sebagai bahasa yang sangat diketik, C++ mengharuskan semua variabel memiliki jenis tertentu, baik secara eksplisit dideklarasikan oleh programmer atau disimpulkan oleh kompilator. Namun, banyak struktur dan algoritma data terlihat sama tidak peduli jenis apa yang mereka operasikan. Templat memungkinkan Anda menentukan operasi kelas atau fungsi, dan membiarkan pengguna menentukan jenis konkret apa yang harus dikerjakan operasi tersebut.

Menentukan dan menggunakan templat

Templat adalah konstruksi yang menghasilkan jenis atau fungsi biasa pada waktu kompilasi berdasarkan argumen yang disediakan pengguna untuk parameter templat. Misalnya, Anda dapat menentukan templat fungsi seperti ini:

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Kode di atas menjelaskan templat untuk fungsi generik dengan parameter jenis tunggal T, yang nilai pengembalian dan parameter panggilannya (lh dan rhs) semuanya dari jenis ini. Anda dapat memberi nama parameter jenis apa pun yang Anda suka, tetapi berdasarkan konvensi huruf besar tunggal paling umum digunakan. T adalah parameter templat; typename kata kunci mengatakan bahwa parameter ini adalah tempat penampung untuk jenis. Ketika fungsi dipanggil, pengkompilasi akan mengganti setiap instans T dengan argumen jenis konkret yang ditentukan oleh pengguna atau disimpulkan oleh pengkompilasi. Proses di mana pengkompilasi menghasilkan kelas atau fungsi dari templat disebut sebagai instans templat; minimum<int> adalah instans templat minimum<T>.

Di tempat lain, pengguna dapat mendeklarasikan instans templat yang dikhususkan untuk int. Asumsikan bahwa get_a() dan get_b() adalah fungsi yang mengembalikan int:

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

Namun, karena ini adalah templat fungsi dan pengkompilasi dapat menyimpulkan jenis T dari argumen a dan b, Anda dapat menyebutnya seperti fungsi biasa:

int i = minimum(a, b);

Ketika pengkompilasi menemukan pernyataan terakhir itu, ia menghasilkan fungsi baru di mana setiap kemunculan T dalam templat diganti dengan int:

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Aturan tentang bagaimana pengkompilasi melakukan pengurangan jenis dalam templat fungsi didasarkan pada aturan untuk fungsi biasa. Untuk informasi selengkapnya, lihat Resolusi Kelebihan Beban Panggilan Templat Fungsi.

Parameter jenis

Dalam templat di minimum atas, perhatikan bahwa parameter jenis T tidak memenuhi syarat dengan cara apa pun sampai digunakan dalam parameter panggilan fungsi, di mana konstan dan kualifikasi referensi ditambahkan.

Tidak ada batas praktis untuk jumlah parameter jenis. Pisahkan beberapa parameter dengan koma:

template <typename T, typename U, typename V> class Foo{};

Kata kunci class setara dengan typename dalam konteks ini. Anda dapat mengekspresikan contoh sebelumnya sebagai:

template <class T, class U, class V> class Foo{};

Anda dapat menggunakan operator elipsis (...) untuk menentukan templat yang mengambil jumlah parameter nol atau lebih jenis arbitrer:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

Jenis bawaan atau yang ditentukan pengguna dapat digunakan sebagai argumen jenis. Misalnya, Anda dapat menggunakan std::vector di Pustaka Standar untuk menyimpan variabel jenis int, double, std::string, MyClass, constMyClass*, MyClass&, dan sebagainya. Pembatasan utama saat menggunakan templat adalah bahwa argumen jenis harus mendukung operasi apa pun yang diterapkan ke parameter jenis. Misalnya, jika kita memanggil minimum menggunakan MyClass seperti dalam contoh ini:

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

Kesalahan kompilator akan dihasilkan karena MyClass tidak memberikan kelebihan beban untuk < operator.

Tidak ada persyaratan yang melekat bahwa argumen jenis untuk templat tertentu semuanya termasuk dalam hierarki objek yang sama, meskipun Anda dapat menentukan templat yang memberlakukan pembatasan tersebut. Anda dapat menggabungkan teknik berorientasi objek dengan templat; misalnya, Anda dapat menyimpan Turunan* di Basis vektor<*>. Perhatikan bahwa argumen harus penunjuk

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

Persyaratan dasar yang std::vector dan kontainer pustaka standar lainnya memaksakan pada elemen T adalah yang T dapat disalin dan dapat dikonstruksi salinan.

Parameter non-jenis

Tidak seperti jenis generik dalam bahasa lain seperti C# dan Java, templat C++ mendukung parameter non-jenis, juga disebut parameter nilai. Misalnya, Anda dapat memberikan nilai integral konstan untuk menentukan panjang array, seperti contoh ini yang mirip dengan kelas std::array di Pustaka Standar:

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

Perhatikan sintaks dalam deklarasi templat. Nilai size_t diteruskan sebagai argumen templat pada waktu kompilasi dan harus atau constconstexpr ekspresi. Anda menggunakannya seperti ini:

MyArray<MyClass*, 10> arr;

Jenis nilai lain termasuk pointer dan referensi dapat diteruskan sebagai parameter non-jenis. Misalnya, Anda dapat meneruskan penunjuk ke fungsi atau objek fungsi untuk menyesuaikan beberapa operasi di dalam kode templat.

Ketik pengurangan untuk parameter templat non-jenis

Di Visual Studio 2017 dan yang lebih baru, dan dalam /std:c++17 mode atau yang lebih baru, kompilator menyimpulkan jenis argumen templat non-jenis yang dideklarasikan dengan auto:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

Templat sebagai parameter templat

Templat bisa menjadi parameter templat. Dalam contoh ini, MyClass2 memiliki dua parameter templat: parameter typename T dan parameter templat Arr:

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

Karena parameter Arr sendiri tidak memiliki isi, nama parameternya tidak diperlukan. Bahkan, ini adalah kesalahan untuk merujuk ke nama jenis Arr atau nama parameter kelas dari dalam isi MyClass2. Untuk alasan ini, nama parameter jenis Arr dapat dihilangkan, seperti yang ditunjukkan dalam contoh ini:

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

Argumen templat default

Templat kelas dan fungsi dapat memiliki argumen default. Saat templat memiliki argumen default, Anda dapat membiarkannya tidak ditentukan saat Anda menggunakannya. Misalnya, templat std::vector memiliki argumen default untuk alokator:

template <class T, class Allocator = allocator<T>> class vector;

Dalam kebanyakan kasus, kelas std::allocator default dapat diterima, jadi Anda menggunakan vektor seperti ini:

vector<int> myInts;

Tetapi jika perlu, Anda dapat menentukan alokator kustom seperti ini:

vector<int, MyAllocator> ints;

Untuk beberapa argumen templat, semua argumen setelah argumen default pertama harus memiliki argumen default.

Saat menggunakan templat yang parameternya semuanya default, gunakan kurung sudut kosong:

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

Spesialisasi templat

Dalam beberapa kasus, templat tidak dimungkinkan atau diinginkan untuk menentukan kode yang sama persis untuk jenis apa pun. Misalnya, Anda mungkin ingin menentukan jalur kode yang akan dijalankan hanya jika argumen jenis adalah pointer, atau std::wstring, atau jenis yang berasal dari kelas dasar tertentu. Dalam kasus seperti itu, Anda dapat menentukan spesialisasi templat untuk jenis tertentu. Saat pengguna membuat instans templat dengan jenis tersebut, pengkompilasi menggunakan spesialisasi untuk menghasilkan kelas, dan untuk semua jenis lainnya, pengkompilasi memilih templat yang lebih umum. Spesialisasi di mana semua parameter khusus adalah spesialisasi lengkap. Jika hanya beberapa parameter yang dikhususkan, itu disebut spesialisasi parsial.

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

Templat dapat memiliki sejumlah spesialisasi selama setiap parameter jenis khusus unik. Hanya templat kelas yang mungkin dikhususkan sebagian. Semua spesialisasi lengkap dan parsial templat harus dideklarasikan dalam namespace yang sama dengan templat asli.

Untuk informasi selengkapnya, lihat Spesialisasi Templat.