Pertanyaan Di mana dan mengapa saya harus meletakkan kata kunci "template" dan "typename"?


Dalam template, di mana dan mengapa saya harus meletakkannya typename dan template pada nama yang bergantung? Apa sebenarnya nama yang bergantung 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 ada di typedef Tail::inUnion<U> dummy garis. Saya cukup yakin itu inUnion adalah nama dependen, dan VC ++ cukup tepat untuk mencekiknya. Saya juga tahu bahwa saya harus bisa menambahkan template suatu tempat untuk memberi tahu compiler bahwa inUnion adalah template-id. Tapi di mana tepatnya? Dan haruskah itu kemudian berasumsi bahwa inUnion adalah templat kelas, yaitu inUnion<U> nama jenis dan bukan fungsi?


901
2018-03-04 11:56


asal


Jawaban:


Untuk mem-parse program C ++, compiler perlu mengetahui apakah nama-nama tertentu adalah tipe atau bukan. Contoh berikut menunjukkan bahwa:

t * f;

Bagaimana ini harus diurai? Untuk banyak bahasa, kompiler tidak perlu mengetahui arti nama untuk mengurai dan pada dasarnya mengetahui tindakan apa yang dilakukan oleh baris kode. Namun, dalam C ++, hal di atas dapat menghasilkan interpretasi yang sangat berbeda tergantung pada apa t cara. Jika itu tipe, maka itu akan menjadi deklarasi pointer f. Namun jika itu bukan tipe, itu akan menjadi perkalian. Jadi Standar C ++ mengatakan pada paragraf (3/7):

Beberapa nama menunjukkan jenis atau templat. Secara umum, setiap kali nama ditemui perlu untuk menentukan apakah nama itu menunjukkan salah satu entitas ini sebelum melanjutkan untuk mengurai program yang berisi itu. Proses yang menentukan ini disebut pencarian nama.

Bagaimana kompilator akan mencari tahu nama apa t::x mengacu pada, jika t mengacu pada parameter tipe template? x bisa menjadi anggota data int statis yang dapat dikalikan atau sama-sama bisa menjadi kelas atau typedef bertingkat yang dapat menghasilkan deklarasi. Jika nama memiliki properti ini - bahwa itu tidak dapat dicari sampai argumen template aktual diketahui - maka itu disebut a nama yang tergantung (itu "tergantung" pada parameter template).

Anda mungkin menyarankan untuk menunggu hingga pengguna memberi contoh pada template:

Mari menunggu hingga pengguna memulai instantiate template, dan kemudian menemukan makna sebenarnya t::x * f;. 

Ini akan bekerja dan benar-benar diizinkan oleh Standar sebagai kemungkinan pendekatan implementasi. Kompiler ini pada dasarnya menyalin teks template ke buffer internal, dan hanya ketika diperlukan instantiasi, mereka menguraikan template dan mungkin mendeteksi kesalahan dalam definisi. Namun alih-alih mengganggu pengguna template (rekan miskin!) Dengan kesalahan yang dibuat oleh penulis template, implementasi lain memilih untuk memeriksa template sejak awal dan memberikan kesalahan dalam definisi sesegera mungkin, sebelum instantiation terjadi.

Jadi harus ada cara untuk memberitahu compiler bahwa nama-nama tertentu adalah tipe dan bahwa nama-nama tertentu tidak.

Kata kunci "typename"

Jawabannya adalah: Kita memutuskan bagaimana kompiler harus mengurai ini. Jika t::x adalah nama dependen, maka kita perlu menambahkannya dengan typenameuntuk memberi tahu compiler untuk menguraikannya dengan cara tertentu. The Standard mengatakan pada (14.6 / 2):

Nama yang digunakan dalam deklarasi template atau definisi dan yang bergantung pada parameter-template adalah   diasumsikan tidak menyebutkan jenis kecuali jika nama pencarian yang berlaku menemukan nama jenis atau nama yang memenuhi syarat   dengan nama kunci kata kunci.

Ada banyak nama untuk itu typename tidak diperlukan, karena kompilator dapat, dengan pencarian nama yang berlaku dalam definisi template, mencari tahu cara mengurai konstruk itu sendiri - misalnya dengan T *f;, kapan T adalah parameter template jenis. Tapi untuk t::x * f; untuk menjadi deklarasi, harus ditulis sebagai typename t::x *f;. Jika Anda menghilangkan kata kunci dan nama yang diambil menjadi non-tipe, tetapi ketika Instansiasi menemukan itu menandakan jenis, pesan kesalahan yang biasa dipancarkan oleh kompiler. Terkadang, kesalahan secara konsekuen 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;

Syntax memungkinkan typename hanya sebelum nama yang memenuhi syarat - Oleh karena itu dianggap sepantasnya bahwa nama-nama yang tidak memenuhi syarat selalu dikenal untuk merujuk pada tipe jika mereka melakukannya.

Sebuah gotcha serupa ada untuk nama-nama yang menunjukkan templat, seperti yang diisyaratkan oleh teks pengantar.

Kata kunci "template"

Ingat kutipan awal di atas dan bagaimana Standar memerlukan penanganan khusus untuk templat juga? Mari kita ambil contoh yang tampak tidak berbahaya berikut ini:

boost::function< int() > f;

Mungkin terlihat jelas bagi pembaca manusia. Tidak demikian halnya untuk compiler. Bayangkan definisi sewenang-wenang berikut ini boost::function dan f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Itu sebenarnya valid ekspresi! Ini menggunakan operator yang kurang dari untuk membandingkan boost::function melawan nol (int()), dan kemudian menggunakan operator yang lebih besar daripada membandingkan hasilnya bool melawan f. Namun seperti yang Anda ketahui, boost::function  dalam kehidupan nyata adalah template, sehingga compiler mengetahui (14.2 / 3):

Setelah pencarian nama (3.4) menemukan bahwa nama adalah nama-templat, jika nama ini diikuti oleh <, maka <adalah   selalu dianggap sebagai awal dari daftar argumen-argumen dan tidak pernah sebagai nama yang diikuti oleh yang kurang   operator.

Sekarang kita kembali ke masalah yang sama dengan typename. Bagaimana jika kita belum tahu apakah nama adalah template saat menguraikan kode? Kita perlu memasukkan template tepat sebelum nama template, seperti yang ditentukan oleh 14.2/4. Ini terlihat seperti:

t::template f<int>(); // call a function template

Nama template tidak hanya dapat terjadi setelah a :: tetapi juga setelah -> atau . dalam akses anggota kelas. Anda perlu memasukkan kata kunci di sana juga:

this->template f<int>(); // call a function template

Dependensi

Untuk orang-orang yang memiliki buku-buku Standardese tebal di rak mereka dan yang ingin tahu apa sebenarnya yang saya bicarakan, saya akan berbicara sedikit tentang bagaimana ini ditentukan dalam Standar.

Dalam deklarasi template, beberapa konstruk memiliki arti yang berbeda tergantung pada argumen template apa yang Anda gunakan untuk menginstansi template: Ekspresi mungkin memiliki jenis atau nilai yang berbeda, variabel mungkin memiliki tipe atau panggilan fungsi yang berbeda yang mungkin berakhir dengan memanggil fungsi yang berbeda. Konstruksi seperti itu umumnya dikatakan tergantung pada parameter template.

Standar mendefinisikan secara tepat aturan dengan apakah konstruk itu tergantung atau tidak. Ini memisahkan mereka ke dalam kelompok yang berbeda secara logis: Satu menangkap jenis, yang lain menangkap ekspresi. Ekspresi dapat bergantung pada nilainya dan / atau tipenya. Jadi kita punya, dengan contoh-contoh khas yang ditambahkan:

  • Jenis dependen (misalnya: parameter template jenis T)
  • Ekspresi bergantung-nilai (misalnya: parameter template non-tipe N)
  • Ekspresi bergantung-tipe (misalnya: cast ke parameter template tipe (T)0)

Sebagian besar aturan bersifat intuitif dan dibangun secara rekursif: Misalnya, jenis yang dikonstruksi sebagai T[N] adalah tipe dependen jika N adalah ekspresi yang bergantung pada nilai atau T adalah tipe dependen. Detail ini dapat dibaca di bagian (14.6.2/1) untuk jenis yang bergantung, (14.6.2.2) untuk ekspresi yang bergantung pada jenis dan (14.6.2.3) untuk ekspresi yang bergantung pada nilai.

Nama yang tergantung

Standar agak tidak jelas tentang apa persis adalah nama yang tergantung. Pada pembacaan sederhana (Anda tahu, prinsip paling tidak mengejutkan), semua itu mendefinisikan sebagai nama yang tergantung adalah kasus khusus untuk nama fungsi di bawah ini. Tapi karena sudah jelas T::x juga perlu dilihat dalam konteks instantiasi, itu juga perlu menjadi nama yang bergantung (untungnya, pada pertengahan C ++ 14 komite telah mulai mencari cara untuk memperbaiki definisi yang membingungkan ini).

Untuk menghindari masalah ini, saya telah menggunakan interpretasi sederhana dari teks Standar. Dari semua konstruk yang menunjukkan jenis atau ekspresi bergantung, sebagian dari mereka mewakili nama. Nama-nama itu karena itu "nama yang bergantung". Nama dapat mengambil bentuk berbeda - Standar mengatakan:

Nama adalah penggunaan pengidentifikasi (2.11), operator-fungsi-id (13.5), konversi-fungsi-id (12.3.2), atau template-id (14.2) yang menunjukkan entitas atau label (6.6.4, 6.1)

Suatu identifier hanyalah suatu urutan karakter / digit sederhana, sementara dua yang berikutnya adalah operator + dan operator type bentuk. Bentuk terakhir adalah template-name <argument list>. Semua ini adalah nama, dan dengan penggunaan konvensional dalam Standar, nama juga dapat menyertakan kualifikasi yang mengatakan apa nama atau kelas nama harus dicari.

Ekspresi tergantung nilai 1 + N bukan nama, tapi N aku s. Subset dari semua konstruk dependen adalah nama yang dipanggil nama yang tergantung. Nama fungsi, bagaimanapun, mungkin memiliki arti yang berbeda dalam berbagai instantiations dari template, tetapi sayangnya tidak tertangkap oleh aturan umum ini.

Nama fungsi dependen

Bukan yang utama dari artikel ini, tetapi masih perlu disebutkan: Nama fungsi adalah pengecualian yang ditangani secara terpisah. Nama fungsi identifier tidak bergantung pada dirinya sendiri, tetapi oleh ekspresi argumen bergantung jenis yang digunakan dalam panggilan. Dalam contoh ini f((T)0), f adalah nama yang bergantung. Dalam Standar, ini ditentukan pada (14.6.2/1).

Catatan dan contoh tambahan

Dalam banyak kasus, kami membutuhkan keduanya typename dan template. Kode Anda harus seperti berikut ini

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 dari sebuah nama. Ini dapat muncul di tengah sebelum nama kelas yang digunakan sebagai ruang lingkup, seperti pada 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 yang tergantung Anda tidak diizinkan untuk menulis typename. Diasumsikan bahwa nama yang diberikan adalah nama tipe 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 digunakan template setelah yang terakhir ::, dan komite C ++ kata tidak mengerjakan 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
     };
    

931
2018-03-05 00:27



C ++ 11

Masalah

Sedangkan aturan dalam C ++ 03 tentang kapan Anda butuhkan typename dan template sebagian besar masuk akal, ada satu kelemahan yang mengganggu dari formulasinya

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Seperti yang bisa dilihat, kita perlu kata kunci disambiguasi bahkan jika compiler bisa dengan sempurna mengetahui itu sendiri A::result_type hanya bisa int (dan karenanya merupakan tipe), dan this->g hanya bisa menjadi templat anggota g dideklarasikan nanti (bahkan jika A secara eksplisit khusus di suatu tempat, yang tidak akan mempengaruhi kode di dalam template itu, jadi artinya tidak dapat dipengaruhi oleh spesialisasi selanjutnya A!).

Instansiasi saat ini

Untuk memperbaiki situasi, di C ++ 11 trek bahasa ketika suatu tipe mengacu pada templat lampiran. Untuk mengetahui itu, jenisnya pasti sudah terbentuk dengan menggunakan bentuk nama tertentu, yang namanya sendiri (di atas, A, A<T>, ::A<T>). Suatu tipe yang direferensikan oleh nama tersebut dikenal sebagai instantiasi saat ini. Mungkin ada beberapa jenis yang semua instantiasi saat ini jika jenis dari mana nama tersebut terbentuk adalah anggota / kelas bersarang (kemudian, A::NestedClass dan A keduanya instantiations saat ini).

Berdasarkan gagasan ini, bahasa mengatakan itu CurrentInstantiation::Foo, Foo dan CurrentInstantiationTyped->Foo (seperti A *a = this; a->Foo) adalah semua anggota Instansiasi saat ini  jika mereka ditemukan menjadi anggota kelas yang merupakan instantiasi saat ini atau salah satu dari kelas dasar non-dependen (dengan hanya melakukan pencarian nama segera).

Kata kunci typename dan template sekarang tidak diperlukan lagi jika kualifikasi adalah anggota instantiasi saat ini. Sebuah keypoint di sini untuk diingat adalah itu A<T> aku s masih nama yang bergantung pada jenis (setelah semua T juga tipe tergantung). Tapi A<T>::result_type dikenal sebagai tipe - kompilator akan "secara ajaib" melihat jenis dependen semacam ini untuk mengetahui hal ini.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Itu mengesankan, tetapi bisakah kita melakukan lebih baik? Bahkan bahasanya semakin jauh dan membutuhkan bahwa implementasi kembali terlihat D::result_type ketika instantiate D::f (bahkan jika sudah diketahui artinya pada waktu definisi). Ketika sekarang hasil pencarian berbeda atau menghasilkan ambiguitas, program ini tidak terbentuk dan diagnosis harus diberikan. Bayangkan apa yang terjadi jika kita mendefinisikannya C seperti ini

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Diperlukan compiler untuk menangkap kesalahan saat melakukan instantiasi D<int>::f. Jadi Anda mendapatkan yang terbaik dari dua dunia: "Tertunda" lookup melindungi Anda jika Anda bisa mendapat masalah dengan kelas dasar tergantung, dan juga "Segera" lookup yang membebaskan Anda dari typename dan template.

Spesialisasi tidak dikenal

Dalam kode D, nama typename D::questionable_type bukan anggota instantiasi saat ini. Sebaliknya bahasa menandainya sebagai anggota dari spesialisasi yang tidak diketahui. Secara khusus, ini selalu terjadi ketika Anda melakukan DependentTypeName::Foo atau DependentTypedName->Foo dan tipe dependennya tidak Instansiasi saat ini (dalam hal ini compiler dapat menyerah dan berkata "kita akan melihat nanti apa Foo adalah) atau itu aku s Instansiasi saat ini dan nama tidak ditemukan di dalamnya atau kelas dasar non-dependen dan ada juga kelas dasar yang bergantung.

Bayangkan apa yang terjadi jika kita memiliki fungsi anggota h dalam yang didefinisikan di atas A template kelas

void h() {
  typename A<T>::questionable_type x;
}

Di 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 berikan T). Di C ++ 11, bahasa sekarang memiliki pemeriksaan lebih lanjut untuk memberikan alasan lebih banyak bagi para kompiler untuk menerapkan aturan ini. Sejak A tidak memiliki kelas dasar yang bergantung, dan A menyatakan tidak ada anggota questionable_type, nama A<T>::questionable_type aku s tidak juga anggota Instansiasi saat ini maupun anggota dari spesialisasi yang tidak diketahui. Dalam hal ini, seharusnya tidak ada cara bahwa kode itu dapat secara sah mengkompilasi pada waktu instantiasi, sehingga bahasa melarang nama di mana kualifikasi adalah instantiasi saat ini untuk menjadi anggota dari spesialisasi yang tidak diketahui atau anggota dari instantiasi saat ini (namun , pelanggaran ini masih belum perlu didiagnosis).

Contoh dan hal-hal sepele

Anda dapat mencoba pengetahuan ini jawaban ini dan lihat apakah definisi di atas masuk akal bagi Anda pada contoh dunia nyata (mereka diulang sedikit kurang rinci dalam jawaban itu).

Aturan C ++ 11 membuat kode C ++ 03 valid yang tidak benar (yang tidak dimaksudkan oleh komite C ++, tetapi mungkin tidak akan diperbaiki)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Kode C ++ 03 yang valid ini akan mengikat this->f untuk A::f pada waktu Instansiasi dan semuanya baik-baik saja. Namun C ++ 11 segera mengikatnya B::f dan membutuhkan pemeriksaan ganda saat instantiasi, memeriksa apakah pencarian masih cocok. Namun ketika instantiate C<A>::g, yang Aturan Dominasi berlaku dan pencarian akan ditemukan A::f sebagai gantinya.


118
2017-07-10 20:02



KATA PENGANTAR

Posting ini dimaksudkan untuk menjadi mudah dibaca alternatif untuk posting litb.

Tujuan dasarnya adalah sama; penjelasan untuk "Kapan?" dan mengapa?" typename dan template harus diterapkan.


Apa tujuan dari typename dan template?

typename dan template dapat digunakan dalam situasi lain selain saat mendeklarasikan template.

Ada konteks tertentu dalam C ++ dimana kompilator harus secara eksplisit diberitahu bagaimana memperlakukan nama, dan semua konteks ini memiliki satu kesamaan; mereka bergantung pada setidaknya satu template-parameter.

Kami mengacu pada nama-nama tersebut, di mana bisa 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 di berikut ini fungsi-template, baik untuk diri sendiri, teman, atau mungkin kucing Anda; apa yang terjadi dalam pernyataan yang ditandai (SEBUAH)?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Ini mungkin tidak semudah yang dipikirkan, lebih khusus lagi hasil evaluasi (SEBUAH) berat tergantung pada definisi jenis dilewatkan sebagai template-parameter T.

Berbeda TSecara drastis dapat mengubah semantik yang terlibat.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


Dua skenario berbeda:

  • Jika kita instantiate fungsi-template dengan tipe X, seperti dalam (C), kami akan memiliki deklarasi a penunjuk ke int bernama x, tapi;

  • jika kita instantiate template dengan tipe Y, seperti dalam (D), (SEBUAH) Sebaliknya akan terdiri dari ekspresi yang menghitung produk 123 dikalikan dengan beberapa variabel yang sudah dideklarasikan x.



THE RASIONAL

Standar C ++ peduli dengan keselamatan dan kesejahteraan kita, setidaknya dalam kasus ini.

Untuk mencegah implementasi dari berpotensi menderita kejutan buruk, Standar mengamanatkan bahwa kita memilah ambiguitas a nama-dependen oleh secara eksplisit menyatakan maksud di mana saja kami ingin memperlakukan nama sebagai salah satu ketik nama, atau a template-id.

Jika tidak ada yang disebutkan, maka nama-dependen akan dianggap sebagai variabel, atau fungsi.



BAGAIMANA MENANGANI NAMA-NAMA TERGANTUNG?

Jika ini adalah film Hollywood, nama tergantung akan menjadi penyakit yang menyebar melalui kontak tubuh, langsung mempengaruhi inangnya untuk membuatnya bingung. Kebingungan yang bisa, mungkin, mengarah pada program perso-, erhm .. yang buruk.

SEBUAH nama-dependen aku s apa saja nama yang secara langsung, atau tidak langsung, tergantung pada template-parameter.

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Kami punya empat tergantung nama dalam cuplikan di atas:

  • E)
    • "mengetik" tergantung pada instantiasi SomeTrait<T>, yang termasuk T, dan;
  • F)
    • "NestedTrait", yang mana template-id, tergantung pada SomeTrait<T>, dan;
    • "mengetik" pada akhir (F) tergantung pada NestedTrait, yang tergantung pada SomeTrait<T>, dan;
  • G)
    • "data", yang terlihat seperti template anggota-fungsi, secara tidak langsung a nama-dependen sejak jenis foo tergantung pada instantiasi SomeTrait<T>.

Tidak ada pernyataan (E), (F) atau (G) berlaku jika compiler akan menafsirkan nama tergantung sebagai variabel / fungsi (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 compiler yang kita harapkan tipenya (E), Sebuah template-id dan a mengetik di (F), dan a template-id di (G).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Setiap kali a nama menunjukkan suatu tipe, semua  nama-nama Terlibat harus baik nama-nama jenis atau ruang nama, dengan pemikiran ini cukup mudah untuk melihat apa yang kami terapkan typename di awal kami sepenuhnya nama yang memenuhi syarat.

template namun, berbeda dalam hal ini, karena tidak ada cara untuk mencapai kesimpulan seperti; "oh, ini adalah template, daripada benda lain ini juga harus template". Ini berarti kami berlaku template langsung di depan apapun nama yang ingin kami perlakukan seperti itu.



BISA SAYA HANYA STICK THE KATA KUNCI DI DEPAN NAMA APAPUN?

"Bisakah saya tetap typename dan template di depan nama apa pun? Saya tidak ingin khawatir tentang konteks di mana mereka muncul ..."- Some C++ Developer

Aturan dalam Standar menyatakan bahwa Anda dapat menerapkan kata kunci selama Anda berurusan dengan nama berkualitas (K), tetapi jika namanya tidak berkualitas aplikasi tidak terbentuk (L).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Catatan: Menerapkan typename atau template dalam konteks di mana tidak diperlukan tidak dianggap praktik yang baik; hanya karena Anda dapat melakukan sesuatu, bukan berarti Anda harus melakukannya.


Selain itu ada konteks di mana typename dan template adalah secara eksplisit tidak diizinkan:

  • Ketika menentukan basis yang kelas mewarisi

    Setiap nama ditulis dalam kelas turunan daftar penentu-dasar sudah diperlakukan sebagai ketik nama, secara eksplisit menentukan typename keduanya buruk terbentuk, dan berlebihan.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • Ketika template-id adalah yang disebut di kelas turunan menggunakan-direktif

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    

73
2018-06-07 20:28



typedef typename Tail::inUnion<U> dummy;

Namun, saya tidak yakin Anda menerapkan inUnion benar. Jika saya mengerti dengan benar, kelas ini tidak seharusnya dipakai, oleh karena itu "gagal" tab tidak akan pernah gagal. Mungkin akan lebih baik untuk menunjukkan apakah jenisnya dalam serikat atau tidak dengan nilai boolean sederhana.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Coba lihat Boost :: Varian

PS2: Coba lihat typelists, terutama dalam buku Andrei Alexandrescu: Modern C ++ Design


17
2018-03-04 13:37



Jawaban ini dimaksudkan untuk menjadi jawaban yang agak pendek dan manis untuk menjawab (bagian dari) pertanyaan yang berjudul. Jika Anda menginginkan jawaban dengan lebih detail yang menjelaskan mengapa Anda harus meletakkannya di sana, silakan pergi sini.


Aturan umum untuk menempatkan typename kata kunci sebagian besar ketika Anda menggunakan parameter template dan Anda ingin mengakses bersarang typedef atau menggunakan-alias, misalnya:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Perhatikan bahwa ini juga berlaku untuk fungsi meta atau hal-hal yang mengambil parameter template umum juga. Namun, jika parameter template yang disediakan adalah tipe eksplisit maka Anda tidak harus menentukan typename, sebagai contoh:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Aturan umum untuk menambahkan template kualifikasi sebagian besar serupa kecuali mereka biasanya melibatkan fungsi anggota templated (statis atau lainnya) dari struct / kelas itu sendiri templated, misalnya:

Dengan struktur dan fungsi ini:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Mencoba mengakses t.get<int>() dari dalam fungsi akan menghasilkan kesalahan:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Jadi dalam konteks ini Anda akan membutuhkan template kata kunci sebelumnya dan menyebutnya seperti ini:

t.template get<int>()

Dengan cara itu kompilator akan mengurai ini dengan benar daripada t.get < int.


14
2018-06-06 22:23