(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:
t * f;
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 pointer f
. Namun jika itu bukan tipe, itu akan menjadi perkalian. Jadi Standar C ++ mengatakan pada ayat (3/7):
Beberapa nama menunjukkan tipe atau templat. Secara umum, setiap kali nama ditemui, perlu untuk menentukan apakah nama itu menunjukkan salah satu dari entitas ini sebelum melanjutkan untuk menguraikan program yang berisi itu. Proses yang menentukan ini disebut pencarian nama.
Bagaimana kompiler mengetahui apa nama yang t::x
dirujuk, jika t
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:
Mari kita tunggu sampai pengguna membuat template, dan kemudian mencari tahu arti sebenarnya dari t::x * f;
.
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 dengan typename
memberi tahu kompiler untuk menguraikannya dengan cara tertentu. Standar mengatakan pada (14.6 / 2):
Nama yang digunakan dalam pernyataan atau definisi templat dan yang bergantung pada parameter templat diasumsikan tidak menyebutkan nama jenis kecuali pencarian nama yang berlaku menemukan nama jenis atau nama tersebut dikualifikasikan oleh nama kunci kata kunci.
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 dengan T *f;
, kapan T
parameter templat tipe. Tetapi untuk t::x * f;
menjadi deklarasi, harus ditulis sebagai typename 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:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
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:
boost::function< int() > f;
Mungkin terlihat jelas bagi pembaca manusia. Tidak demikian halnya dengan kompiler. Bayangkan definisi sewenang-wenang berikut boost::function
dan f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
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 hasil bool
terhadap f
. Namun seperti yang mungkin Anda ketahui, boost::function
dalam kehidupan nyata adalah sebuah template, jadi kompiler tahu (14.2 / 3):
Setelah pencarian nama (3.4) menemukan bahwa nama adalah nama templat, jika nama ini diikuti oleh <, maka <selalu diambil sebagai awal dari daftar templat-argumen dan tidak pernah sebagai nama diikuti oleh less- dari operator.
Sekarang kita kembali ke masalah yang sama dengan typename
. Bagaimana jika kita belum tahu apakah nama itu templat ketika menguraikan kode? Kami harus memasukkan template
segera sebelum nama templat, seperti yang ditentukan oleh 14.2/4
. Ini terlihat seperti:
t::template f<int>(); // call a function template
Nama templat tidak hanya dapat terjadi setelah a ::
tetapi juga setelah akses anggota kelas ->
atau .
. Anda perlu memasukkan kata kunci di sana juga:
this->template f<int>(); // call a function template
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:
- Tipe dependen (mis: parameter tipe template
T
)
- Ekspresi yang bergantung pada nilai (misalnya: parameter templat non-tipe
N
)
- Ekspresi yang bergantung pada tipe (mis: dilemparkan ke parameter templat tipe
(T)0
)
Sebagian besar aturan bersifat intuitif dan dibangun secara rekursif: Misalnya, tipe yang dikonstruksikan sebagai T[N]
tipe dependen N
adalah ekspresi yang bergantung pada nilai atau T
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:
Nama adalah penggunaan pengidentifikasi (2.11), id fungsi operator (13.5), id fungsi konversi (12.3.2), atau id template (14.2) yang menunjukkan entitas atau label (6.6.4, 6.1)
Identifier hanyalah urutan karakter / digit, sedangkan dua berikutnya adalah operator +
dan operator type
bentuk. Bentuk terakhir adalah template-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, tetapi N
. 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
dan template
. Kode Anda akan terlihat seperti berikut
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
Kata 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 berikut
typename t::template iterator<int>::value_type v;
Dalam 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:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
Dalam menggunakan-deklarasi itu tidak mungkin untuk digunakan template
setelah yang terakhir ::
, dan komite C ++ mengatakan tidak bekerja pada solusi.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};