Dalam templat, di mana dan mengapa saya harus meletakkan typename
dan template
pada nama-nama dependen?
Apa sebenarnya nama dependen itu?
Saya memiliki kode berikut:
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
Masalah yang saya miliki adalah di typedef Tail::inUnion<U> dummy
telepon. Saya cukup yakin itu inUnion
adalah nama dependen, dan VC ++ cukup tepat dalam mencekiknya.
Saya juga tahu bahwa saya harus dapat menambahkan template
suatu tempat untuk memberitahu kompiler bahwa inUnion adalah template-id. Tapi dimana tepatnya? Dan haruskah kemudian berasumsi bahwa inUnion adalah templat kelas, yaitu inUnion<U>
menamai sebuah tipe dan bukan fungsi?
Jawaban:
(Lihat di sini juga untuk jawaban C ++ 11 saya )
Untuk menguraikan program C ++, kompiler perlu mengetahui apakah nama-nama tertentu adalah tipe atau tidak. Contoh berikut menunjukkan bahwa:
Bagaimana ini harus diurai? Untuk banyak bahasa, kompiler tidak perlu tahu arti nama untuk menguraikan dan pada dasarnya tahu tindakan apa yang dilakukan oleh baris kode. Namun dalam C ++, hal di atas dapat menghasilkan interpretasi yang sangat berbeda tergantung pada apa
t
artinya. Jika itu tipe, maka itu akan menjadi deklarasi pointerf
. Namun jika itu bukan tipe, itu akan menjadi perkalian. Jadi Standar C ++ mengatakan pada ayat (3/7):Bagaimana kompiler mengetahui apa nama yang
t::x
dirujuk, jikat
mengacu pada parameter jenis templat?x
bisa menjadi anggota data int statis yang dapat dikalikan atau bisa juga menjadi kelas bersarang atau typedef yang dapat menghasilkan deklarasi. Jika sebuah nama memiliki properti ini - yang tidak dapat dilihat hingga argumen templat aktual diketahui - maka itu disebut nama dependen ("tergantung" pada parameter templat).Anda mungkin merekomendasikan untuk menunggu sampai pengguna membuat template:
Ini akan bekerja dan sebenarnya diizinkan oleh Standar sebagai pendekatan implementasi yang mungkin. Kompiler ini pada dasarnya menyalin teks templat ke buffer internal, dan hanya ketika diperlukan instantiasi, mereka mem-parsing templat dan mungkin mendeteksi kesalahan dalam definisi. Tetapi alih-alih mengganggu pengguna templat (kolega yang buruk!) Dengan kesalahan yang dibuat oleh pembuat templat, implementasi lainnya memilih untuk memeriksa templat sejak awal dan memberikan kesalahan dalam definisi sesegera mungkin, bahkan sebelum instantiasi terjadi.
Jadi harus ada cara untuk memberitahu kompiler bahwa nama-nama tertentu adalah tipe dan nama-nama tertentu tidak.
Kata kunci "typename"
Jawabannya adalah: Kami memutuskan bagaimana kompiler harus menguraikan ini. Jika
t::x
adalah nama dependen, maka kita perlu awalan dengantypename
memberi tahu kompiler untuk menguraikannya dengan cara tertentu. Standar mengatakan pada (14.6 / 2):Ada banyak nama yang
typename
tidak perlu, karena kompiler dapat, dengan pencarian nama yang berlaku dalam definisi templat, mencari tahu cara mem-parsing sebuah konstruk itu sendiri - misalnya denganT *f;
, kapanT
parameter templat tipe. Tetapi untukt::x * f;
menjadi deklarasi, harus ditulis sebagaitypename t::x *f;
. Jika Anda menghilangkan kata kunci dan nama dianggap non-tipe, tetapi ketika instantiation menemukan itu menunjukkan tipe, pesan kesalahan yang biasa dipancarkan oleh kompiler. Kadang-kadang, kesalahan akibatnya diberikan pada waktu definisi:Sintaks memungkinkan
typename
hanya sebelum nama yang memenuhi syarat - itu diambil sebagai diberikan bahwa nama wajar tanpa pengecualian selalu dikenal untuk merujuk pada jenis jika mereka melakukannya.Gotcha serupa ada untuk nama yang menunjukkan template, seperti yang ditunjukkan oleh teks pengantar.
Kata kunci "templat"
Ingat kutipan awal di atas dan bagaimana Standar memerlukan penanganan khusus untuk templat juga? Mari kita ambil contoh yang terlihat tidak bersalah berikut ini:
Mungkin terlihat jelas bagi pembaca manusia. Tidak demikian halnya dengan kompiler. Bayangkan definisi sewenang-wenang berikut
boost::function
danf
:Itu sebenarnya ungkapan yang valid ! Ia menggunakan operator yang kurang dari untuk membandingkan
boost::function
terhadap nol (int()
), dan kemudian menggunakan operator yang lebih besar dari untuk membandingkan hasilbool
terhadapf
. Namun seperti yang mungkin Anda ketahui,boost::function
dalam kehidupan nyata adalah sebuah template, jadi kompiler tahu (14.2 / 3):Sekarang kita kembali ke masalah yang sama dengan
typename
. Bagaimana jika kita belum tahu apakah nama itu templat ketika menguraikan kode? Kami harus memasukkantemplate
segera sebelum nama templat, seperti yang ditentukan oleh14.2/4
. Ini terlihat seperti:Nama templat tidak hanya dapat terjadi setelah a
::
tetapi juga setelah akses anggota kelas->
atau.
. Anda perlu memasukkan kata kunci di sana juga:Ketergantungan
Untuk orang-orang yang memiliki buku-buku Standardese tebal di rak mereka dan yang ingin tahu apa yang sebenarnya saya bicarakan, saya akan berbicara sedikit tentang bagaimana ini ditentukan dalam Standar.
Dalam deklarasi templat, beberapa konstruk memiliki makna yang berbeda tergantung pada argumen templat apa yang Anda gunakan untuk membuat contoh templat: Ekspresi mungkin memiliki tipe atau nilai yang berbeda, variabel mungkin memiliki tipe atau panggilan fungsi yang berbeda mungkin berakhir dengan memanggil fungsi yang berbeda. Konstruk semacam itu umumnya dikatakan bergantung pada parameter templat.
Standar mendefinisikan dengan tepat aturan dengan apakah konstruk bergantung atau tidak. Ini memisahkan mereka menjadi kelompok yang berbeda secara logis: Satu menangkap jenis, satu lagi menangkap ekspresi. Ekspresi dapat bergantung pada nilainya dan / atau jenisnya. Jadi kita punya, dengan contoh-contoh khas ditambahkan:
T
)N
)(T)0
)Sebagian besar aturan bersifat intuitif dan dibangun secara rekursif: Misalnya, tipe yang dikonstruksikan sebagai
T[N]
tipe dependenN
adalah ekspresi yang bergantung pada nilai atauT
tipe dependen. Rincian ini dapat dibaca di bagian(14.6.2/1
) untuk tipe dependen,(14.6.2.2)
untuk ekspresi bergantung tipe dan(14.6.2.3)
untuk ekspresi tergantung nilai.Nama tergantung
Standar ini agak tidak jelas tentang apa sebenarnya adalah nama tergantung . Pada bacaan sederhana (Anda tahu, prinsip paling tidak mengejutkan), semua itu didefinisikan sebagai nama dependen adalah kasus khusus untuk nama fungsi di bawah ini. Tetapi karena jelas
T::x
juga perlu dilihat dalam konteks instantiasi, itu juga perlu menjadi nama dependen (untungnya, pada pertengahan C ++ 14 komite telah mulai mencari cara untuk memperbaiki definisi yang membingungkan ini).Untuk menghindari masalah ini, saya menggunakan interpretasi sederhana dari teks Standar. Dari semua konstruksi yang menunjukkan tipe atau ekspresi dependen, sebagian dari mereka mewakili nama. Oleh karena itu nama-nama itu "nama tergantung". Sebuah nama dapat mengambil bentuk yang berbeda - Standar mengatakan:
Identifier hanyalah urutan karakter / digit, sedangkan dua berikutnya adalah
operator +
danoperator type
bentuk. Bentuk terakhir adalahtemplate-name <argument list>
. Semua ini adalah nama, dan dengan penggunaan konvensional dalam Standar, sebuah nama juga dapat menyertakan kualifikasi yang menyebutkan namespace atau kelas apa nama harus dicari.Ekspresi ketergantungan nilai
1 + N
bukan nama, tetapiN
. Subset dari semua konstruk dependen yang merupakan nama disebut dependen name . Namun, nama fungsi mungkin memiliki arti berbeda dalam instantiasi template yang berbeda, tetapi sayangnya tidak ditangkap oleh aturan umum ini.Nama fungsi tergantung
Terutama bukan masalah artikel ini, tetapi masih layak disebutkan: Nama fungsi adalah pengecualian yang ditangani secara terpisah. Nama fungsi pengidentifikasi tidak tergantung dengan sendirinya, tetapi oleh tipe ekspresi argumen yang digunakan dalam panggilan. Dalam contoh ini
f((T)0)
,f
adalah nama dependen. Dalam Standar, ini ditentukan pada(14.6.2/1)
.Catatan dan contoh tambahan
Dalam banyak kasus kita membutuhkan keduanya
typename
dantemplate
. Kode Anda akan terlihat seperti berikutKata kunci
template
tidak selalu harus muncul di bagian terakhir nama. Itu bisa muncul di tengah sebelum nama kelas yang digunakan sebagai cakupan, seperti dalam contoh berikutDalam beberapa kasus, kata kunci dilarang, seperti yang dijelaskan di bawah ini
Atas nama kelas dasar tergantung Anda tidak diperbolehkan menulis
typename
. Diasumsikan bahwa nama yang diberikan adalah nama jenis kelas. Ini berlaku untuk kedua nama dalam daftar kelas dasar dan daftar penginisialisasi konstruktor:Dalam menggunakan-deklarasi itu tidak mungkin untuk digunakan
template
setelah yang terakhir::
, dan komite C ++ mengatakan tidak bekerja pada solusi.sumber
typename
ditegakkan bahkan ketika sintaksinya tidak mengizinkan interpretasi alternatif selain tipe-nama pada saat ini?C ++ 11
Masalah
Sementara aturan di C ++ 03 tentang kapan Anda perlu
typename
dantemplate
sebagian besar masuk akal, ada satu kelemahan yang mengganggu dari formulasinyaSeperti dapat dilihat, kita membutuhkan kata kunci disambiguasi bahkan jika kompiler dapat dengan sempurna mengetahuinya
A::result_type
hanya dapatint
(dan karenanya merupakan tipe), danthis->g
hanya dapat menjadi template anggota yangg
dideklarasikan nanti (bahkan jikaA
secara eksplisit dikhususkan di suatu tempat, yang akan tidak memengaruhi kode di dalam templat itu, jadi artinya tidak dapat dipengaruhi oleh spesialisasi nantiA
!).Instansiasi saat ini
Untuk memperbaiki situasi, dalam C ++ 11 bahasa melacak ketika suatu tipe merujuk ke templat yang dilampirkan. Untuk mengetahui itu, tipe harus dibentuk dengan menggunakan bentuk nama tertentu, yang merupakan namanya sendiri (di atas
A
,,A<T>
,::A<T>
). Jenis yang dirujuk oleh nama seperti itu dikenal sebagai instantiasi saat ini . Mungkin ada beberapa tipe yang semuanya instantiasi saat ini jika tipe dari mana nama itu dibentuk adalah anggota / kelas bersarang (kemudian,A::NestedClass
danA
keduanya instantiasi saat ini).Berdasarkan gagasan ini, bahasa mengatakan itu
CurrentInstantiation::Foo
,Foo
danCurrentInstantiationTyped->Foo
(sepertiA *a = this; a->Foo
) adalah semua anggota instantiasi saat ini jika mereka ditemukan sebagai anggota kelas yang merupakan instantiasi saat ini atau salah satu dari kelas dasar yang tidak tergantung (hanya dengan melakukan pencarian nama segera).Kata kunci
typename
dantemplate
sekarang tidak diperlukan lagi jika kualifikasi adalah anggota instantiasi saat ini. Titik kunci di sini untuk diingat adalah bahwaA<T>
itu masih nama yang tergantung tipe (setelah semuaT
juga tipe tergantung). TetapiA<T>::result_type
dikenal sebagai tipe - kompiler akan "ajaib" melihat ke dalam jenis-jenis dependen untuk mencari tahu ini.Itu mengesankan, tetapi bisakah kita berbuat lebih baik? Bahasa ini bahkan melangkah lebih jauh dan mengharuskan implementasi lagi mencari
D::result_type
ketika instantiatingD::f
(bahkan jika sudah menemukan artinya pada waktu definisi). Ketika sekarang hasil pencarian berbeda atau hasil dalam ambiguitas, program ini salah bentuk dan diagnostik harus diberikan. Bayangkan apa yang terjadi jika kita mendefinisikanC
seperti iniDiperlukan kompiler untuk menangkap kesalahan saat membuat instance
D<int>::f
. Jadi Anda mendapatkan yang terbaik dari dua dunia: "Tertunda" pencarian melindungi Anda jika Anda bisa mendapat masalah dengan kelas dasar dependen, dan juga pencarian "Segera" yang membebaskan Anda daritypename
dantemplate
.Spesialisasi tidak dikenal
Dalam kode
D
, namatypename D::questionable_type
bukan anggota instantiasi saat ini. Alih-alih bahasa menandainya sebagai anggota spesialisasi yang tidak dikenal . Secara khusus, ini selalu terjadi ketika Anda melakukanDependentTypeName::Foo
atauDependentTypedName->Foo
dan salah satu jenis dependen bukan instantiasi saat ini (dalam hal ini kompiler dapat menyerah dan berkata "kita akan lihat nanti apaFoo
ada) atau apakah Instansiasi saat ini dan nama tidak ditemukan di dalamnya atau kelas dasar yang tidak tergantung dan ada juga kelas dasar yang tergantung.Bayangkan apa yang terjadi jika kita memiliki fungsi anggota
h
dalamA
templat kelas yang didefinisikan di atasDi C ++ 03, bahasa diizinkan untuk menangkap kesalahan ini karena tidak akan pernah ada cara yang valid untuk instantiate
A<T>::h
(argumen apa pun yang Anda berikanT
). Di C ++ 11, bahasa sekarang memiliki pemeriksaan lebih lanjut untuk memberikan lebih banyak alasan bagi kompiler untuk mengimplementasikan aturan ini. KarenaA
tidak memiliki kelas dasar tergantung, danA
menyatakan tidak ada anggotaquestionable_type
, namaA<T>::questionable_type
adalah anggota dari spesialisasi yang tidak diketahui. Dalam hal itu, seharusnya tidak mungkin kode itu dapat dikompilasi secara valid pada waktu instantiasi, jadi bahasa tersebut melarang nama di mana kualifikasi adalah instantiasi saat ini untuk tidak menjadi anggota dari spesialisasi yang tidak diketahui atau anggota dari Instansiasi saat ini (namun , pelanggaran ini masih belum harus didiagnosis). tidak anggota dari Instansiasi saat ini jugaContoh dan hal sepele
Anda dapat mencoba pengetahuan ini pada jawaban ini dan melihat apakah definisi di atas masuk akal bagi Anda pada contoh dunia nyata (mereka diulang sedikit kurang detail dalam jawaban itu).
Aturan C ++ 11 membuat kode C ++ 03 yang valid berikut ini salah bentuk (yang tidak dimaksudkan oleh komite C ++, tetapi mungkin tidak akan diperbaiki)
C ++ 03 kode yang valid ini akan mengikat
this->f
untukA::f
saat Instansiasi dan semuanya baik-baik saja. Namun C ++ 11 langsung mengikatnyaB::f
dan memerlukan pemeriksaan ulang saat instantiating, memeriksa apakah pencarian masih cocok. Namun ketika instantiatingC<A>::g
, Aturan Dominance berlaku dan pencarian akan menemukanA::f
sebagai gantinya.sumber
Apa tujuan
typename
dantemplate
?typename
dantemplate
dapat digunakan dalam keadaan selain saat mendeklarasikan templat.Ada konteks tertentu dalam C ++ di mana kompiler harus secara eksplisit diberitahu bagaimana memperlakukan nama, dan semua konteks ini memiliki satu kesamaan; mereka bergantung pada setidaknya satu parameter template .
Kami merujuk pada nama-nama tersebut, di mana mungkin ada ambiguitas dalam interpretasi, seperti; " nama tergantung ".
Posting ini akan menawarkan penjelasan tentang hubungan antara nama-tergantung , dan dua kata kunci.
SNIPPET MENGATAKAN LEBIH DARI 1000 KATA
Coba jelaskan apa yang terjadi dalam fungsi-templat berikut , baik untuk diri sendiri, teman, atau mungkin kucing Anda; apa yang terjadi dalam pernyataan bertanda ( A )?
Mungkin tidak semudah yang dipikirkan orang, lebih khusus lagi hasil dari evaluasi ( A ) sangat tergantung pada definisi tipe yang dilewatkan sebagai templat-parameter
T
.Perbedaan
T
dapat secara drastis mengubah semantik yang terlibat.Dua skenario berbeda :
Jika kita instantiate fungsi-template dengan tipe X , seperti dalam ( C ), kita akan memiliki deklarasi pointer-to int bernama x , tetapi;
jika kita instantiate template dengan tipe Y , seperti dalam ( D ), ( A ) akan terdiri dari ekspresi yang menghitung produk dari 123 dikalikan dengan beberapa variabel yang sudah dideklarasikan x .
DASAR
Standar C ++ memperhatikan keselamatan dan kesejahteraan kita, setidaknya dalam kasus ini.
Untuk mencegah implementasi dari kemungkinan mengalami kejutan yang tidak menyenangkan, Standar mengamanatkan bahwa kami memilah ambiguitas nama-tergantung dengan secara eksplisit menyatakan maksud di mana saja kami ingin memperlakukan nama sebagai jenis-nama , atau templat- Indo .
Jika tidak ada yang dinyatakan, nama-tergantung akan dianggap sebagai variabel, atau fungsi.
BAGAIMANA CARA MENANGANI NAMA YANG TERGANTUNG ?
Jika ini adalah film Hollywood, nama-nama tergantung adalah penyakit yang menyebar melalui kontak tubuh, langsung mempengaruhi inangnya untuk membuatnya bingung. Kebingungan yang bisa, mungkin, mengarah pada program perso-, erhm .. yang buruk.
Sebuah bergantung-nama adalah setiap nama yang secara langsung atau tidak langsung, tergantung pada template parameter .
Kami memiliki empat nama dependen dalam cuplikan di atas:
SomeTrait<T>
, yang meliputiT
, dan;SomeTrait<T>
, dan;SomeTrait<T>
, dan;SomeTrait<T>
.Tidak satu pun dari pernyataan ( E ), ( F ) atau ( G ) yang valid jika kompiler akan menginterpretasikan nama-dependen sebagai variabel / fungsi (yang seperti yang dinyatakan sebelumnya adalah apa yang terjadi jika kita tidak secara eksplisit mengatakan sebaliknya).
SOLUSINYA
Untuk membuat
g_tmpl
memiliki definisi yang valid, kita harus secara eksplisit memberi tahu kompiler bahwa kita mengharapkan suatu tipe dalam ( E ), sebuah templat-id dan sebuah tipe dalam ( F ), dan sebuah templat-id dalam ( G ).Setiap kali nama menunjukkan suatu tipe, semua nama yang terlibat harus berupa tipe-nama atau ruang nama , dengan mengingat hal ini, cukup mudah untuk melihat bahwa kita menerapkan
typename
pada awal nama yang memenuhi syarat .template
Namun, berbeda dalam hal ini, karena tidak ada cara untuk sampai pada kesimpulan seperti; "Oh, ini templat, maka yang ini juga harus templat" . Ini berarti kami menerapkantemplate
secara langsung di depan nama apa pun yang ingin kami perlakukan.BISAKAH SAYA HANYA MENCETAK KATA KUNCI DI DEPAN NAMA APA PUN?
Aturan dalam Standar menyatakan bahwa Anda dapat menerapkan kata kunci selama Anda berurusan dengan nama yang memenuhi syarat ( K ), tetapi jika nama tersebut tidak memenuhi syarat , aplikasi tersebut salah bentuk ( L ).
Catatan : Menerapkan
typename
atautemplate
dalam konteks di mana tidak diperlukan tidak dianggap praktik yang baik; hanya karena Anda dapat melakukan sesuatu, tidak berarti Anda harus melakukannya.Selain itu ada konteks di mana
typename
dantemplate
secara eksplisit dilarang:Saat menentukan basis yang mewarisi kelas
Setiap nama yang ditulis dalam basis-specifier-list kelas turunan sudah diperlakukan sebagai nama-tipe , yang secara eksplisit menentukan
typename
keduanya tidak terbentuk dengan baik, dan berlebihan.Ketika templat-id adalah yang dirujuk dalam penggunaan-direktif kelas turunan
sumber
Namun, saya tidak yakin implementasi inUnion Anda benar. Jika saya mengerti dengan benar, kelas ini tidak seharusnya dipakai, karena itu tab "gagal" tidak akan pernah gagal. Mungkin akan lebih baik untuk menunjukkan apakah jenisnya dalam serikat pekerja atau tidak dengan nilai boolean sederhana.
PS: Coba lihat Boost :: Variant
PS2: Lihatlah daftar ketik , terutama dalam buku Andrei Alexandrescu: Modern C ++ Design
sumber
Jawaban ini dimaksudkan untuk menjadi jawaban yang agak pendek dan manis untuk (bagian dari) pertanyaan berjudul. Jika Anda ingin jawaban dengan lebih detail yang menjelaskan mengapa Anda harus meletakkannya di sana, silakan kunjungi di sini .
Aturan umum untuk menempatkan
typename
kata kunci sebagian besar ketika Anda menggunakan parameter templat dan Anda ingin mengaksestypedef
alias bersarang atau menggunakan, misalnya:Perhatikan bahwa ini juga berlaku untuk fungsi meta atau hal-hal yang juga mengambil parameter templat generik. Namun, jika parameter template yang disediakan adalah tipe eksplisit maka Anda tidak perlu menentukan
typename
, misalnya:Aturan umum untuk menambahkan
template
kualifikasi sebagian besar serupa kecuali mereka biasanya melibatkan fungsi anggota templated (statis atau sebaliknya) dari struct / kelas yang itu sendiri templated, misalnya:Dengan struktur dan fungsi ini:
Mencoba mengakses
t.get<int>()
dari dalam fungsi akan menghasilkan kesalahan:Jadi dalam konteks ini Anda perlu
template
kata kunci sebelumnya dan menyebutnya seperti:t.template get<int>()
Dengan begitu kompiler akan menguraikan ini dengan benar daripada
t.get < int
.sumber
Saya menempatkan JLBorges ini sangat baik respon ke verbatim pertanyaan serupa dari cplusplus.com, karena merupakan penjelasan yang paling ringkas saya sudah membaca pada subjek.
Ringkasan
Gunakan nama kata kunci kata kunci hanya dalam deklarasi dan definisi template asalkan Anda memiliki nama yang memenuhi syarat yang merujuk pada jenis dan tergantung pada parameter template.
sumber