Secara resmi, untuk apa nama hurufnya?

131

Kadang-kadang saya telah melihat beberapa pesan kesalahan yang benar-benar tidak dapat dipahami meludah gccketika menggunakan template ... Secara khusus, saya punya masalah di mana deklarasi yang tampaknya benar menyebabkan kesalahan kompilasi yang sangat aneh yang secara ajaib pergi dengan memprefixing typenamekata kunci ke awal kata kunci. deklarasi ... (Misalnya, minggu lalu, saya mendeklarasikan dua iterator sebagai anggota kelas templated lain dan saya harus melakukan ini) ...

Apa ceritanya typename?

dicroce
sumber

Jawaban:

207

Berikut ini adalah kutipan dari buku Josuttis:

Kata kunci typenamediperkenalkan untuk menentukan bahwa pengidentifikasi yang mengikuti adalah tipe. Perhatikan contoh berikut:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Di sini, typenamedigunakan untuk menjelaskan bahwa itu SubTypeadalah tipe class T. Jadi, ptradalah pointer ke tipe T::SubType. Tanpa typename, SubType akan dianggap sebagai anggota statis. Jadi

T::SubType * ptr

akan menjadi perkalian nilai dengan SubTypetipe .Tptr

Naveen
sumber
2
Buku yang bagus. Baca sekali lalu simpan sebagai referensi jika Anda suka.
deft_code
1
Pembaca yang cerdik akan menyadari bahwa ekspresi multiplikasi tidak diizinkan oleh tata bahasa untuk deklarasi anggota. Dengan demikian, C ++ 20 membuang kebutuhan untuk ini typename(meskipun tidak semuanya!).
Davis Herring
Tidak meyakinkan saya. Setelah template sedang dipakai, itu didefinisikan dengan sangat baik apa T :: Subtipe
kovarex
36

Pos BLog milik Stan Lippman menunjukkan: -

Stroustrup menggunakan kembali kata kunci kelas yang ada untuk menentukan parameter tipe daripada memperkenalkan kata kunci baru yang tentu saja dapat merusak program yang ada. Bukan berarti kata kunci baru tidak dipertimbangkan - hanya saja itu tidak dianggap perlu mengingat potensi gangguannya. Dan sampai standar ISO-C ++, ini adalah satu-satunya cara untuk mendeklarasikan parameter type.

Jadi pada dasarnya Stroustrup menggunakan kembali kata kunci kelas tanpa memperkenalkan kata kunci baru yang diubah setelahnya dalam standar karena alasan berikut

Seperti contoh yang diberikan

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

tata bahasa salah tafsir T::A *aObj;sebagai ekspresi aritmatika sehingga kata kunci baru diperkenalkan disebuttypename

typename T::A* a6;

itu memerintahkan kompiler untuk memperlakukan pernyataan selanjutnya sebagai deklarasi.

Karena kata kunci ada di daftar gaji, heck, mengapa tidak memperbaiki kebingungan yang disebabkan oleh keputusan awal untuk menggunakan kembali kata kunci kelas.

Itulah mengapa kami memiliki keduanya

Anda dapat melihat posting ini , itu pasti akan membantu Anda, saya hanya mengekstrak sebanyak yang saya bisa

Xinus
sumber
Ya, tetapi mengapa typenameperlu kata kunci baru , jika Anda dapat menggunakan kata kunci yang ada classuntuk tujuan yang sama?
Jesper
5
@Jesper: Saya pikir jawaban Xenus membingungkan di sini. typenamemenjadi perlu untuk memperbaiki masalah parsing seperti yang dijelaskan dalam jawaban Naveen dengan mengutip Josuttis. (Saya tidak berpikir memasukkan classdi tempat ini akan berhasil.) Hanya setelah kata kunci baru diterima untuk kasus ini, itu juga diperbolehkan dalam deklarasi argumen templat ( atau apakah definisi itu? ), Karena classselalu ada sedikit menyesatkan.
sbi
13

Pertimbangkan kodenya

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

Sayangnya, kompiler tidak diharuskan untuk menjadi peramal, dan tidak tahu apakah T :: sometype akan berakhir dengan merujuk pada nama tipe atau anggota statis dari T. Jadi, orang typenamedapat mengatakannya:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
bayangan bulan
sumber
6

Dalam beberapa situasi di mana Anda merujuk ke anggota yang disebut tipe dependen (artinya "bergantung pada parameter templat"), kompiler tidak selalu dapat secara jelas mendeduksi makna semantik dari konstruk yang dihasilkan, karena ia tidak tahu nama jenis apa itu. (yaitu apakah itu nama tipe, nama anggota data atau nama yang lain). Dalam kasus seperti itu, Anda harus menyamarkan situasi dengan secara eksplisit memberi tahu kompiler bahwa nama itu milik nama ketik yang didefinisikan sebagai anggota dari tipe dependen itu.

Sebagai contoh

template <class T> struct S {
  typename T::type i;
};

Dalam contoh ini kata kunci yang typenamediperlukan untuk mengkompilasi kode.

Hal yang sama terjadi ketika Anda ingin merujuk ke anggota templat dari tipe dependen, yaitu ke nama yang menunjuk templat. Anda juga harus membantu kompiler dengan menggunakan kata kunci template, meskipun ditempatkan berbeda

template <class T> struct S {
  T::template ptr<int> p;
};

Dalam beberapa kasus mungkin perlu menggunakan keduanya

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(jika saya mendapatkan sintaks dengan benar).

Tentu saja, peran lain dari kata kunci typenameadalah untuk digunakan dalam deklarasi parameter template.

Semut
sumber
Lihat juga Deskripsi kata kunci jenis kata C ++ untuk informasi lebih lanjut (latar belakang).
Atafar
5

Rahasianya terletak pada kenyataan bahwa templat dapat dikhususkan untuk beberapa jenis. Ini berarti juga dapat mendefinisikan antarmuka yang sama sekali berbeda untuk beberapa jenis. Misalnya, Anda dapat menulis:

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

Orang mungkin bertanya mengapa ini berguna dan memang: Itu benar-benar terlihat tidak berguna. Tetapi mengambil diingat bahwa misalnya std::vector<bool>dengan referencejenis terlihat sama sekali berbeda dibandingkan lainnya Ts. Memang itu tidak mengubah jenis dari referencejenis ke sesuatu yang berbeda tetapi tetap saja itu bisa terjadi.

Sekarang apa yang terjadi jika Anda menulis templat Anda sendiri menggunakan testtemplat ini . Sesuatu seperti ini

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

tampaknya tidak apa-apa untuk Anda karena Anda mengharapkan itu test<T>::ptradalah tipe. Tetapi kompiler tidak tahu dan dalam akta dia bahkan disarankan oleh standar untuk mengharapkan yang sebaliknya, test<T>::ptrbukan tipe. Untuk memberi tahu kompiler apa yang Anda harapkan, Anda harus menambahkan typenamesebelumnya. Template yang benar terlihat seperti ini

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Intinya: Anda harus menambahkan typenamesebelum setiap kali Anda menggunakan tipe templat bersarang di templat Anda. (Tentu saja hanya jika parameter templat templat Anda digunakan untuk templat dalam itu.)

phlipsy
sumber
5

Dua kegunaan:

  1. Sebagai templatekata kunci argumen (bukan class)
  2. Kata typenamekunci memberi tahu kompiler bahwa pengenal adalah tipe (bukan variabel anggota statis)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
sumber
4

Saya pikir semua jawaban telah menyebutkan typenamekata kunci, digunakan dalam dua kasus berbeda:

a) Saat mendeklarasikan parameter tipe template. misalnya

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Yang tidak ada perbedaan di antara mereka dan mereka PERSIS sama.

b) Sebelum menggunakan nama tipe dependen bersarang untuk templat.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

Yang tidak menggunakan typenamemengarah ke kesalahan parsing / kompilasi.

Apa yang ingin saya tambahkan ke kasus kedua, seperti yang disebutkan dalam buku Scot Meyers Effective C ++ , adalah bahwa ada pengecualian menggunakan typenamesebelum nama tipe bersarang tergantung . Pengecualian adalah bahwa jika Anda menggunakan nama tipe bersarang tergantung baik sebagai kelas dasar atau dalam daftar inisialisasi anggota , Anda tidak boleh menggunakannya di typenamesana:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Catatan: Menggunakan typenameuntuk kasus kedua (yaitu sebelum nama tipe dependen bersarang) tidak diperlukan sejak C ++ 20.

Gupta
sumber
2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
Jobin
sumber