Sebagian besar implementasi generik (atau lebih tepatnya: polimorfisme parametrik) memang menggunakan penghapusan tipe. Ini sangat menyederhanakan masalah mengkompilasi kode generik, tetapi hanya bekerja untuk tipe kotak: karena setiap argumen secara efektif adalah pointer buram, kita membutuhkan mekanisme pengiriman VTable atau serupa untuk melakukan operasi pada argumen. Di Jawa:
<T extends Addable> T add(T a, T b) { … }
dapat dikompilasi, ketik-diperiksa, dan dipanggil dengan cara yang sama
Addable add(Addable a, Addable b) { … }
kecuali bahwa obat generik memberikan pemeriksa tipe dengan informasi yang jauh lebih banyak di situs panggilan. Informasi tambahan ini dapat ditangani dengan variabel tipe , terutama ketika tipe generik disimpulkan. Selama pemeriksaan tipe, setiap tipe generik dapat diganti dengan variabel, sebut saja $T1
:
$T1 add($T1 a, $T1 b)
Variabel tipe kemudian diperbarui dengan lebih banyak fakta ketika mereka diketahui, hingga dapat diganti dengan tipe konkret. Algoritma pengecekan tipe harus ditulis dengan cara yang mengakomodasi variabel tipe ini bahkan jika mereka belum diselesaikan ke tipe yang lengkap. Di Jawa sendiri hal ini biasanya dapat dilakukan dengan mudah karena jenis argumen sering dikenal sebelum jenis pemanggilan fungsi perlu diketahui. Pengecualian penting adalah ekspresi lambda sebagai argumen fungsi, yang mengharuskan penggunaan variabel tipe tersebut.
Jauh kemudian, pengoptimal mungkin melakukannya menghasilkan kode khusus untuk serangkaian argumen tertentu, ini kemudian secara efektif akan menjadi semacam inlining.
VTable untuk argumen yang diketik secara umum dapat dihindari jika fungsi generik tidak melakukan operasi apa pun pada tipe tersebut, tetapi hanya meneruskannya ke fungsi lain. Misalnya fungsi Haskell call :: (a -> b) -> a -> b; call f x = f x
tidak perlu mengotak-kotak x
argumen. Namun, ini memang membutuhkan konvensi pemanggilan yang dapat melewati nilai-nilai tanpa mengetahui ukurannya, yang pada dasarnya membatasi itu ke pointer.
C ++ sangat berbeda dari kebanyakan bahasa dalam hal ini. Kelas atau fungsi templated (saya hanya akan membahas fungsi templated di sini) tidak bisa dipanggil sendiri. Alih-alih, templat harus dipahami sebagai fungsi meta waktu kompilasi yang mengembalikan fungsi sebenarnya. Mengabaikan inferensi argumen templat sesaat, pendekatan umum kemudian mengarah pada langkah-langkah ini:
Terapkan templat ke argumen templat yang disediakan. Misalnya memanggil template<class T> T add(T a, T b) { … }
sebagai add<int>(1, 2)
akan memberi kita fungsi yang sebenarnya int __add__T_int(int a, int b)
(atau apa pun pendekatan nama-mangling digunakan).
Jika kode untuk fungsi tersebut telah dihasilkan di unit kompilasi saat ini, lanjutkan. Jika tidak, buat kode seolah-olah suatu fungsi int __add__T_int(int a, int b) { … }
telah ditulis dalam kode sumber. Ini melibatkan mengganti semua kemunculan argumen templat dengan nilainya. Ini mungkin merupakan transformasi AST → AST. Kemudian, lakukan pengecekan jenis pada AST yang dihasilkan.
Kompilasi panggilan seolah-olah kode sumber sudah __add__T_int(1, 2)
.
Perhatikan bahwa templat C ++ memiliki interaksi yang kompleks dengan mekanisme resolusi kelebihan beban, yang tidak ingin saya uraikan di sini. Juga catat bahwa pembuatan kode ini tidak memungkinkan untuk memiliki metode templated yang juga virtual - pendekatan berbasis tipe penghapusan tidak menderita dari pembatasan substansial ini.
Apa artinya ini bagi kompiler dan / atau bahasa Anda? Anda harus berpikir hati-hati tentang jenis obat generik yang ingin Anda tawarkan. Penghapusan tipe tanpa adanya inferensi tipe adalah pendekatan yang paling sederhana jika Anda mendukung tipe kotak. Spesialisasi template tampaknya cukup sederhana, tetapi biasanya melibatkan pembuatan nama dan (untuk beberapa unit kompilasi) duplikasi substansial dalam output, karena template dipakai di situs panggilan, bukan situs definisi.
Pendekatan yang Anda tunjukkan pada dasarnya adalah pendekatan template mirip C ++. Namun, Anda menyimpan templat khusus / instantiated sebagai "versi" dari templat utama. Ini menyesatkan: mereka tidak sama secara konseptual, dan instansi yang berbeda dari suatu fungsi dapat memiliki tipe yang sangat berbeda. Ini akan mempersulit hal-hal dalam jangka panjang jika Anda juga mengizinkan kelebihan fungsi. Sebagai gantinya, Anda akan memerlukan gagasan tentang kumpulan kelebihan yang berisi semua fungsi dan templat yang mungkin berbagi nama. Kecuali untuk mengatasi kelebihan beban, Anda dapat mempertimbangkan berbagai templat instantiated yang berbeda satu sama lain.