Saya membaca tentang fungsi templat dan menjadi bingung oleh masalah ini:
#include <iostream>
void f(int) {
std::cout << "f(int)\n";
}
template<typename T>
void g(T val) {
std::cout << typeid(val).name() << " ";
f(val);
}
void f(double) {
std::cout << "f(double)\n";
}
template void g<double>(double);
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // d f(int), this is surprising
g(1); // i f(int)
}
Hasilnya sama jika saya tidak menulis template void g<double>(double);
.
Saya pikir g<double>
harus instantiated setelah f(double)
, dan karena itu panggilan f
masuk g
harus memanggil f(double)
. Anehnya, masih panggilan f(int)
di g<double>
. Adakah yang bisa membantu saya memahami ini?
Setelah membaca jawaban, saya mencari tahu apa sebenarnya kebingungan saya.
Ini adalah contoh yang diperbarui. Sebagian besar tidak berubah kecuali bahwa saya menambahkan spesialisasi untuk g<double>
:
#include <iostream>
void f(int){cout << "f(int)" << endl;}
template<typename T>
void g(T val)
{
cout << typeid(val).name() << " ";
f(val);
}
void f(double){cout << "f(double)" << endl;}
//Now use user specialization to replace
//template void g<double>(double);
template<>
void g<double>(double val)
{
cout << typeid(val).name() << " ";
f(val);
}
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // now d f(double)
g(1); // i f(int)
}
Dengan spesialisasi pengguna, g(1.0)
berperilaku seperti yang saya harapkan.
Haruskah kompiler tidak secara otomatis melakukan instantiasi yang sama ini g<double>
di tempat yang sama (atau bahkan setelah main()
, seperti yang dijelaskan dalam bagian 26.3.3 dari Bahasa Pemrograman C ++ , edisi ke-4)?
sumber
g(1)
,, memberii f(int)
saya. Anda menulisd f(double)
. Apakah ini salah cetak?Jawaban:
Nama
f
adalah nama dependen (tergantung padaT
argumenval
) dan akan diselesaikan menjadi dua langkah :void f(double)
tidak terlihat dari konteks definisi templat, dan ADL tidak akan menemukannya juga, karenaKami dapat sedikit mengubah contoh Anda:
Sekarang ADL akan menemukan
void f(Double)
di langkah kedua, dan hasilnya akan6Double f(Double)
. Kami dapat menonaktifkan ADL dengan menulis(f)(val)
(atau::f(val)
) alih-alihf(val)
. Maka hasilnya akan6Double f(Int)
, sesuai dengan contoh Anda.sumber
void f(double)
itu tidak terlihat - konteks ini berakhir sebelum deklarasi. Pada langkah 2, ADL tidak akan menemukan apa pun, sehingga konteks instantiasi templat tidak memainkan peran apa pun di sini.void f(double)
, jadi fungsi ini terlihat darinya. Sekarangf
bukan nama dependen. Jika ada kecocokan yang lebih baik untukf(val);
dideklarasikan setelah definisig<double>
, itu tidak akan ditemukan juga. Satu-satunya cara untuk "melihat ke depan" adalah ADL (atau kompiler lama yang tidak menerapkan pencarian dua fase dengan benar).main()
. Mereka tidak akan melihatf(double)
, karena ketika instantiasi terjadi, sudah terlambat: tahap satu pencarian telah dilakukan dan tidak ditemukanf(double)
.Masalahnya
f(double)
belum dinyatakan pada titik di mana Anda menyebutnya; jika Anda memindahkan deklarasi di depantemplate g
, itu akan dipanggil.Sunting: Mengapa seseorang menggunakan instantiasi manual?
(Saya akan berbicara tentang templat fungsi saja, argumentasi analog juga berlaku untuk templat kelas.) Penggunaan utama adalah untuk mengurangi waktu kompilasi dan / atau untuk menyembunyikan kode templat dari pengguna.
Program C ++ dibangun ke dalam binari dalam 2 langkah: kompilasi dan penautan. Untuk kompilasi panggilan fungsi untuk berhasil hanya header fungsi yang diperlukan. Agar tautan berhasil, file objek berisi tubuh yang dikompilasi dari fungsi diperlukan.
Sekarang ketika kompilator melihat panggilan dari fungsi templated , apa yang dilakukannya tergantung pada apakah ia mengetahui tubuh templat atau hanya header. Jika hanya melihat header, ia melakukan hal yang sama seperti jika fungsi tidak templated: menempatkan informasi tentang panggilan untuk linker ke file objek. Tetapi jika ia juga melihat tubuh templat, ia juga melakukan hal lain: ia membuat instance tubuh yang tepat, mengkompilasi tubuh ini dan memasukkannya ke file objek juga.
Jika beberapa file sumber memanggil instance yang sama dari fungsi templated, masing-masing file objeknya akan berisi versi yang dikompilasi dari instance fungsi. (Linker tahu tentang ini dan menyelesaikan semua panggilan ke fungsi terkompilasi tunggal, jadi hanya akan ada satu di biner terakhir dari program / perpustakaan.) Namun untuk mengkompilasi masing-masing file sumber fungsi harus instantiated dan dikompilasi, yang membutuhkan waktu.
Cukup bagi penghubung untuk melakukan tugasnya jika isi dari fungsi tersebut ada dalam satu file objek. Untuk secara manual instantiate templat dalam file sumber adalah cara untuk membuat kompilator memasukkan tubuh fungsi ke file objek dari file sumber yang dimaksud. (Sepertinya fungsi dipanggil, tetapi instantiasi ditulis di tempat di mana fungsi panggilan tidak valid.) Ketika ini dilakukan, semua file yang memanggil fungsi Anda dapat dikompilasi dengan mengetahui hanya header fungsi, dengan demikian menghemat waktu yang diperlukan untuk membuat instance dan menyusun tubuh fungsi dengan masing-masing panggilan.
Alasan kedua (implementasi menyembunyikan) mungkin masuk akal sekarang. Jika penulis perpustakaan ingin pengguna fungsi templatnya dapat menggunakan fungsi ini, ia biasanya memberi mereka kode templat, sehingga mereka dapat mengompilasinya sendiri. Jika dia ingin merahasiakan kode sumber template dia bisa secara manual instantiate template dalam kode yang dia gunakan untuk membangun perpustakaan dan memberikan pengguna versi objek yang diperoleh alih-alih sumber.
Apakah ini masuk akal?
sumber
template void g<double>(double);
disebut instantiasi manual (perhatikantemplate
tanpa tanda kurung sudut, itulah fitur yang membedakan sintaksis); yang memberitahu kompiler untuk membuat turunan dari metode. Di sini ia memiliki sedikit efek, jika tidak ada di sana, kompiler akan menghasilkan instance di tempat instance dipanggil. Instansiasi manual jarang digunakan fitur, saya akan mengatakan mengapa Anda mungkin ingin menggunakannya setelah Anda mengkonfirmasi hal itu sekarang lebih jelas :-)