Jika kelas-kelas di bawah ini bukan templat, saya hanya bisa memiliki x
di dalam derived
kelas. Namun, dengan kode di bawah ini, saya harus menggunakan this->x
. Mengapa?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
x
denganthis->
, yaitu: 1) Gunakan awalanbase<T>::x
, 2) Tambahkan pernyataanusing base<T>::x
, 3) Gunakan sakelar kompiler global yang memungkinkan mode permisif. Pro & kontra dari solusi ini dijelaskan dalam stackoverflow.com/questions/50321788/...Jawaban:
Jawaban singkat: untuk membuat
x
nama dependen, sehingga pencarian ditunda sampai parameter template diketahui.Jawaban panjang: ketika seorang kompiler melihat templat, ia seharusnya melakukan pemeriksaan tertentu segera, tanpa melihat parameter templat. Lainnya ditangguhkan hingga parameter diketahui. Ini disebut kompilasi dua fase, dan MSVC tidak melakukannya tetapi diperlukan oleh standar dan diimplementasikan oleh kompiler utama lainnya. Jika Anda suka, kompiler harus mengkompilasi template begitu melihatnya (ke beberapa jenis parse tree internal), dan menunda kompilasi instantiation sampai nanti.
Pemeriksaan yang dilakukan pada templat itu sendiri, bukan pada instantiations tertentu, mengharuskan kompilator untuk dapat menyelesaikan tata bahasa kode dalam templat.
Di C ++ (dan C), untuk menyelesaikan tata bahasa kode, Anda terkadang perlu tahu apakah sesuatu itu tipe atau bukan. Sebagai contoh:
jika A adalah tipe, yang menyatakan pointer (tanpa efek selain untuk membayangi global
x
). Jika A adalah sebuah objek, itu adalah perkalian (dan mencegah beberapa operator yang membebaniinya ilegal, menugaskan nilai tertentu). Jika salah, kesalahan ini harus didiagnosis pada fase 1 , itu didefinisikan oleh standar untuk menjadi kesalahan dalam template , bukan dalam beberapa contoh tertentu. Bahkan jika templat tidak pernah dipakai, jika A adalahint
maka kode di atas tidak terbentuk dan harus didiagnosis, sama seperti jikafoo
bukan templat sama sekali, tetapi fungsi sederhana.Sekarang, standar mengatakan bahwa nama-nama yang tidak bergantung pada parameter template harus dapat diselesaikan dalam fase 1. di
A
sini bukan nama dependen, itu mengacu pada hal yang sama terlepas dari jenisnyaT
. Jadi itu perlu didefinisikan sebelum template didefinisikan untuk ditemukan dan diperiksa pada fase 1.T::A
akan menjadi nama yang bergantung pada T. Kita tidak mungkin tahu dalam fase 1 apakah itu tipe atau bukan. Tipe yang pada akhirnya akan digunakan sebagaiT
instantiasi sangat mungkin bahkan belum didefinisikan, dan bahkan jika itu kita belum tahu tipe mana yang akan digunakan sebagai parameter template kita. Tetapi kita harus menyelesaikan tata bahasa untuk melakukan pemeriksaan fase 1 berharga kita untuk template yang terbentuk buruk. Jadi standar memiliki aturan untuk nama-nama dependen - kompiler harus berasumsi bahwa mereka bukan tipe, kecuali memenuhi syarattypename
untuk menentukan bahwa mereka adalah tipe, atau digunakan dalam konteks tertentu yang tidak ambigu. Misalnya dalamtemplate <typename T> struct Foo : T::A {};
,T::A
digunakan sebagai kelas dasar dan karenanya merupakan tipe yang jelas. JikaFoo
dipakai dengan beberapa jenis yang memiliki anggota dataA
alih-alih tipe A bersarang, itu adalah kesalahan dalam kode yang melakukan instantiasi (fase 2), bukan kesalahan dalam templat (fase 1).Tapi bagaimana dengan templat kelas dengan kelas dasar dependen?
Apakah nama yang tergantung atau tidak? Dengan kelas dasar, nama apa pun dapat muncul di kelas dasar. Jadi kita dapat mengatakan bahwa A adalah nama dependen, dan memperlakukannya sebagai bukan tipe. Ini akan memiliki efek yang tidak diinginkan bahwa setiap nama di Foo tergantung, dan karenanya setiap jenis yang digunakan di Foo (kecuali tipe bawaan) harus memenuhi syarat. Di dalam Foo, Anda harus menulis:
karena
std::string
akan menjadi nama dependen, dan karenanya dianggap non-tipe kecuali ditentukan lain. Aduh!Masalah kedua dengan mengizinkan kode pilihan Anda (
return x;
) adalah bahwa bahkan jikaBar
didefinisikan sebelumnyaFoo
, danx
bukan anggota dalam definisi itu, seseorang kemudian dapat mendefinisikan spesialisasiBar
untuk beberapa jenisBaz
, sehinggaBar<Baz>
memang memiliki anggota datax
, dan kemudian instantiateFoo<Baz>
. Jadi dalam instantiasi itu, templat Anda akan mengembalikan anggota data alih-alih mengembalikan globalx
. Atau sebaliknya jika definisi template dasarBar
dimilikix
, mereka dapat menentukan spesialisasi tanpa itu, dan template Anda akan mencari globalx
untuk kembaliFoo<Baz>
. Saya pikir ini dinilai sama mengejutkan dan menyedihkannya dengan masalah yang Anda miliki, tetapi ini diam - diam mengejutkan, sebagai lawan untuk melemparkan kesalahan yang mengejutkan.Untuk menghindari masalah ini, standar yang berlaku mengatakan bahwa kelas dasar dari templat kelas tidak dipertimbangkan untuk pencarian kecuali diminta secara eksplisit. Ini menghentikan semuanya dari ketergantungan hanya karena itu dapat ditemukan di basis ketergantungan. Ini juga memiliki efek yang tidak diinginkan yang Anda lihat - Anda harus memenuhi syarat hal-hal dari kelas dasar atau tidak ditemukan. Ada tiga cara umum untuk membuat
A
ketergantungan:using Bar<T>::A;
di kelas -A
sekarang merujuk pada sesuatu diBar<T>
, karenanya tergantung.Bar<T>::A *x = 0;
pada saat digunakan - Sekali lagi,A
sudah pasti diBar<T>
. Ini adalah perkalian karenatypename
tidak digunakan, jadi mungkin contoh yang buruk, tetapi kita harus menunggu sampai instantiasi untuk mengetahui apakahoperator*(Bar<T>::A, x)
mengembalikan nilai. Siapa tahu, mungkin itu ...this->A;
pada titik penggunaan -A
adalah anggota, jadi jika tidak diFoo
, itu harus di kelas dasar, sekali lagi standar mengatakan ini membuatnya tergantung.Kompilasi dua fase sangat rumit dan sulit, dan memperkenalkan beberapa persyaratan mengejutkan untuk verbiage tambahan dalam kode Anda. Tapi agak seperti demokrasi itu mungkin cara terburuk untuk melakukan sesuatu, terlepas dari yang lainnya.
Anda bisa beralasan bahwa dalam contoh Anda,
return x;
tidak masuk akal jikax
merupakan tipe bersarang di kelas dasar, jadi bahasanya harus (a) mengatakan bahwa itu adalah nama dependen dan (2) memperlakukannya sebagai bukan tipe, dan kode Anda akan bekerja tanpathis->
. Sampai batas tertentu Anda adalah korban dari kerusakan jaminan dari solusi untuk masalah yang tidak berlaku dalam kasus Anda, tetapi masih ada masalah kelas dasar Anda berpotensi memperkenalkan nama di bawah Anda yang membayangi global, atau tidak memiliki nama yang Anda pikir mereka punya, dan global yang ditemukan sebagai gantinya.Anda juga bisa berpendapat bahwa default harus kebalikan dari nama-nama dependen (anggap tipe kecuali entah bagaimana ditentukan sebagai objek), atau bahwa default harus lebih peka konteks (dalam
std::string s = "";
,std::string
dapat dibaca sebagai jenis karena tidak ada lagi yang membuat tata bahasa akal, meskipunstd::string *s = 0;
ambigu). Sekali lagi, saya tidak tahu persis bagaimana aturan itu disepakati. Dugaan saya adalah bahwa jumlah halaman teks yang akan diperlukan, dikurangi terhadap pembuatan banyak aturan khusus untuk konteks yang mengambil tipe dan yang non-tipe.sumber
-fpermissive
atau serupa, ya itu mungkin. Saya tidak tahu detail bagaimana itu diterapkan, tetapi kompiler harus menunda penyelesaianx
sampai tahu kelas dasar tempate yang sebenarnyaT
. Jadi, pada prinsipnya dalam mode non-permisif itu bisa merekam fakta bahwa ia telah menunda, menunda, lakukan pencarian sekaliT
, dan ketika pencarian berhasil mengeluarkan teks yang Anda sarankan. Itu akan menjadi saran yang sangat akurat jika itu hanya dibuat dalam kasus-kasus di mana ia bekerja: peluang yang pengguna maksudkanx
dari yang lain dari ruang lingkup lain sangat kecil!(Jawaban asli dari 10 Jan 2011)
Saya pikir saya telah menemukan jawabannya: Masalah GCC: menggunakan anggota kelas dasar yang bergantung pada argumen templat . Jawabannya tidak spesifik untuk gcc.
Pembaruan: Menanggapi komentar mmichael , dari draft N3337 dari Standar C ++ 11:
Apakah "karena standar mengatakan demikian" dianggap sebagai jawaban, saya tidak tahu. Kita sekarang dapat bertanya mengapa standar mengamanatkan itu, tetapi seperti yang ditunjukkan oleh Steve Jessop dan yang lainnya, jawaban untuk pertanyaan terakhir ini agak panjang dan dapat diperdebatkan. Sayangnya, ketika datang ke Standar C ++, seringkali hampir tidak mungkin untuk memberikan penjelasan yang singkat dan lengkap tentang mengapa standar mengamanatkan sesuatu; ini berlaku untuk pertanyaan yang terakhir juga.
sumber
The
x
tersembunyi selama warisan. Anda dapat menyembunyikan melalui:sumber