Bagaimana cara kerja `is_base_of`?

118

Bagaimana cara kerja kode berikut?

typedef char (&yes)[1];
typedef char (&no)[2];

template <typename B, typename D>
struct Host
{
  operator B*() const;
  operator D*();
};

template <typename B, typename D>
struct is_base_of
{
  template <typename T> 
  static yes check(D*, T);
  static no check(B*, int);

  static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};

//Test sample
class Base {};
class Derived : private Base {};

//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
  1. Perhatikan bahwa itu Badalah pangkalan pribadi. Bagaimana cara kerjanya?

  2. Perhatikan bahwa operator B*()adalah const. Mengapa ini penting?

  3. Mengapa template<typename T> static yes check(D*, T);lebih baik dari static yes check(B*, int);?

Catatan : Ini adalah versi yang diperkecil (makro dihapus) dari boost::is_base_of. Dan ini bekerja pada berbagai macam kompiler.

Alexey Malistov
sumber
4
Sangat membingungkan bagi Anda untuk menggunakan pengenal yang sama untuk parameter template dan nama kelas yang sebenarnya ...
Matthieu M.
1
@Matthieu M., saya telah mengambil sendiri untuk mengoreksi :)
Kirill V. Lyadvinsky
2
Beberapa waktu lalu saya menulis implementasi alternatif dari is_base_of: ideone.com/T0C1V Ini tidak bekerja dengan versi GCC yang lebih lama (GCC4.3 berfungsi dengan baik).
Johannes Schaub - litb
3
Oke, saya akan jalan-jalan.
jokoon
2
Penerapan ini tidak benar. is_base_of<Base,Base>::valueseharusnya true; ini kembali false.
chengiz

Jawaban:

109

Jika mereka terkait

Mari sejenak kita asumsikan bahwa Bitu sebenarnya adalah basis D. Kemudian untuk panggilan ke check, kedua versi tersebut layak karena Hostdapat diubah menjadi D* dan B* . Ini adalah urutan konversi yang ditentukan pengguna seperti yang dijelaskan oleh 13.3.3.1.2dari Host<B, D>ke D*dan B*masing - masing. Untuk menemukan fungsi konversi yang dapat mengonversi kelas, fungsi kandidat berikut disintesis untuk checkfungsi pertama menurut13.3.1.5/1

D* (Host<B, D>&)

Fungsi konversi pertama bukanlah kandidat, karena B*tidak dapat dikonversi menjadi D*.

Untuk fungsi kedua, ada kandidat berikut:

B* (Host<B, D> const&)
D* (Host<B, D>&)

Itu adalah dua kandidat fungsi konversi yang mengambil objek host. Yang pertama mengambilnya dengan referensi const, dan yang kedua tidak. Jadi yang kedua lebih cocok untuk objek non-const *this( argumen objek tersirat ) oleh 13.3.3.2/3b1sb4dan digunakan untuk mengonversi ke B*untuk checkfungsi kedua .

Jika Anda ingin menghapus const, kami akan memiliki kandidat berikut

B* (Host<B, D>&)
D* (Host<B, D>&)

Ini berarti bahwa kita tidak dapat lagi memilih dengan konstan. Dalam skenario resolusi kelebihan beban biasa, panggilan tersebut sekarang menjadi ambigu karena biasanya jenis kembalian tidak akan berpartisipasi dalam resolusi kelebihan beban. Untuk fungsi konversi, bagaimanapun, ada pintu belakang. Jika dua fungsi konversi sama baiknya, maka jenis kembaliannya memutuskan siapa yang terbaik menurut 13.3.3/1. Jadi, jika Anda akan menghapus const, maka yang pertama akan diambil, karena B*bertobat lebih baik untuk B*daripada D*untuk B*.

Sekarang, urutan konversi yang ditentukan pengguna mana yang lebih baik? Yang untuk fungsi pemeriksaan kedua atau pertama? Aturannya adalah bahwa urutan konversi yang ditentukan pengguna hanya dapat dibandingkan jika mereka menggunakan fungsi atau konstruktor konversi yang sama sesuai dengan 13.3.3.2/3b2. Inilah yang terjadi di sini: Keduanya menggunakan fungsi konversi kedua. Perhatikan bahwa dengan demikian konstanta penting karena memaksa kompiler untuk mengambil fungsi konversi kedua.

Karena kita bisa membandingkannya - mana yang lebih baik? Aturannya adalah bahwa konversi yang lebih baik dari jenis pengembalian fungsi konversi ke jenis tujuan menang (lagi dengan 13.3.3.2/3b2). Dalam hal ini, D*mengonversi lebih baik ke D*daripada B*. Jadi fungsi pertama dipilih dan kami mengenali warisan!

Perhatikan bahwa karena kita tidak pernah benar - benar perlu mengubah ke kelas dasar, dengan demikian kita dapat mengenali pewarisan pribadi karena apakah kita dapat mengonversi dari a D*ke a B*tidak bergantung pada bentuk pewarisan menurut4.10/3

Jika mereka tidak berhubungan

Sekarang anggap saja mereka tidak terkait dengan warisan. Jadi untuk fungsi pertama kami memiliki kandidat berikut

D* (Host<B, D>&) 

Dan untuk yang kedua sekarang kita memiliki satu set lagi

B* (Host<B, D> const&)

Karena kami tidak dapat mengonversi D*menjadi B*jika kami tidak memiliki hubungan warisan, kami sekarang tidak memiliki fungsi konversi yang sama di antara dua urutan konversi yang ditentukan pengguna! Jadi, kita akan menjadi ambigu jika bukan karena fakta bahwa fungsi pertama adalah template. Template adalah pilihan kedua jika ada fungsi non-template yang juga sama baiknya 13.3.3/1. Jadi, kami memilih fungsi non-templat (yang kedua) dan kami menyadari bahwa tidak ada warisan antara Bdan D!

Johannes Schaub - litb
sumber
2
Ah! Andreas benar paragrafnya, sayang sekali dia tidak memberikan jawaban seperti itu :) Terima kasih atas waktu Anda, saya berharap saya dapat menempatkan favorit itu.
Matthieu M.
2
Ini akan menjadi jawaban favorit saya ... pertanyaan: apakah Anda sudah membaca seluruh standar C ++ atau apakah Anda baru saja bekerja di komite C ++ ?? Selamat!
Marco A.
4
@DavidKernin yang bekerja di komite C ++ tidak secara otomatis membuat Anda tahu cara kerja C ++ :) Jadi Anda pasti harus membaca bagian dari Standar yang diperlukan untuk mengetahui detailnya, yang telah saya lakukan. Belum membaca semuanya, jadi saya pasti tidak dapat membantu dengan sebagian besar perpustakaan Standar atau pertanyaan terkait threading :)
Johannes Schaub - litb
1
@underscore_d Agar adil, spesifikasi tidak melarang std :: traits untuk menggunakan beberapa sihir kompilator sehingga implemer pustaka standar dapat menggunakannya sesuka mereka . Mereka akan menghindari akrobat template yang juga membantu mempercepat waktu kompilasi dan penggunaan memori. Ini benar bahkan jika antarmuka terlihat seperti itu std::is_base_of<...>. Semuanya ada di bawah tenda.
Johannes Schaub - litb
2
Tentu saja, perpustakaan umum seperti boost::perlu memastikan bahwa mereka memiliki hakiki ini tersedia sebelum menggunakannya. Dan saya merasa ada semacam mentalitas "tantangan diterima" di antara mereka untuk mengimplementasikan sesuatu tanpa bantuan kompiler :)
Johannes Schaub - litb
24

Mari kita cari tahu cara kerjanya dengan melihat langkah-langkahnya.

Mulailah dengan sizeof(check(Host<B,D>(), int()))bagiannya. Kompilator dapat dengan cepat melihat bahwa ini check(...)adalah ekspresi panggilan fungsi, jadi ia perlu melakukan resolusi berlebih pada check. Ada dua kelebihan kandidat yang tersedia, template <typename T> yes check(D*, T);dan no check(B*, int);. Jika yang pertama dipilih, Anda mendapatkan sizeof(yes), yang lainsizeof(no)

Selanjutnya, mari kita lihat resolusi kelebihan beban. Kelebihan pertama adalah instansiasi template check<int> (D*, T=int)dan kandidat kedua adalah check(B*, int). Argumen sebenarnya yang diberikan adalah Host<B,D>dan int(). Parameter kedua jelas tidak membedakannya; itu hanya berfungsi untuk membuat kelebihan pertama menjadi template. Kita akan melihat nanti mengapa bagian templat itu relevan.

Sekarang lihat urutan konversi yang dibutuhkan. Untuk kelebihan beban pertama, kami memiliki Host<B,D>::operator D*- satu konversi yang ditentukan pengguna. Untuk yang kedua, kelebihan beban lebih rumit. Kami membutuhkan B *, tetapi mungkin ada dua urutan konversi. Salah satunya adalah melalui Host<B,D>::operator B*() const. Jika (dan hanya jika) B dan D terkait dengan warisan akan urutan konversiHost<B,D>::operator D*() + akan D*->B*ada. Sekarang asumsikan D memang mewarisi dari B. Kedua urutan konversi adalah Host<B,D> -> Host<B,D> const -> operator B* const -> B*dan Host<B,D> -> operator D* -> D* -> B*.

Jadi, untuk terkait B dan D, no check(<Host<B,D>(), int())pasti ambigu. Hasilnya, template yes check<int>(D*, int)dipilih. Namun, jika D tidak mewarisi dari B, maka no check(<Host<B,D>(), int())tidak ambigu. Pada titik ini, resolusi kelebihan beban tidak dapat terjadi berdasarkan urutan konversi terpendek. Namun, dengan urutan konversi yang sama, resolusi overload lebih memilih fungsi non-template, yaitu no check(B*, int).

Anda sekarang melihat mengapa tidak masalah bahwa warisan bersifat pribadi: relasi itu hanya berfungsi untuk menghilangkan no check(Host<B,D>(), int())resolusi berlebih sebelum pemeriksaan akses terjadi. Dan Anda juga melihat mengapa operator B* constmust be const: jika tidak, tidak perlu Host<B,D> -> Host<B,D> constlangkah, tidak ada ambiguitas, dan no check(B*, int)akan selalu dipilih.

MSalters
sumber
Penjelasan Anda tidak memperhitungkan keberadaan const. Jika jawaban Anda benar maka tidak constdiperlukan. Tapi itu tidak benar. Hapus constdan trik tidak akan berhasil.
Alexey Malistov
Tanpa konstanta, kedua urutan konversi no check(B*, int)tidak lagi ambigu.
MSalters
Jika Anda pergi saja no check(B*, int), maka untuk terkait Bdan D, itu tidak akan ambigu. Kompilator pasti akan memilih operator D*()untuk melakukan konversi karena tidak memiliki konstanta. Ini agak berlawanan arah: Jika Anda menghapus const, Anda memperkenalkan beberapa rasa ambiguitas, tetapi yang diselesaikan oleh fakta yang operator B*()menyediakan tipe pengembalian superior yang tidak memerlukan konversi pointer B*seperti D*halnya.
Johannes Schaub - litb
Memang itulah intinya: ambiguitas adalah antara dua urutan konversi yang berbeda untuk mendapatkan a B*dari yang <Host<B,D>()sementara.
MSalters
Ini jawaban yang lebih baik. Terima kasih! Jadi, seperti yang saya pahami, jika satu fungsi lebih baik, tetapi ambigu, maka fungsi lain dipilih?
pengguna1289
4

The privatebit benar-benar diabaikan olehis_base_of karena resolusi yang berlebihan terjadi sebelum pemeriksaan aksesibilitas.

Anda dapat memverifikasi ini dengan mudah:

class Foo
{
public:
  void bar(int);
private:
  void bar(double);
};

int main(int argc, char* argv[])
{
  Foo foo;
  double d = 0.3;
  foo.bar(d);       // Compiler error, cannot access private member function
}

Hal yang sama berlaku di sini, fakta bahwa Bbasis pribadi tidak mencegah pemeriksaan berlangsung, itu hanya akan mencegah konversi, tetapi kami tidak pernah meminta konversi yang sebenarnya;)

Matthieu M.
sumber
Semacam. Tidak ada konversi dasar yang dilakukan sama sekali. hostdiubah secara sewenang-wenang menjadi D*atau B*dalam ekspresi yang tidak dievaluasi. Untuk beberapa alasan, D*lebih disukai B*dalam kondisi tertentu.
Potatoswatter
Saya pikir jawabannya ada di 13.3.1.1.2 tetapi saya belum memilah detailnya :)
Andreas Brinck
Jawaban saya hanya menjelaskan bagian "why even private works" saja, jawaban sellibitze tentu lebih lengkap meski saya sangat menantikan penjelasan yang jelas tentang proses penyelesaian penuh tergantung kasusnya.
Matthieu M.
2

Ini mungkin ada hubungannya dengan pemesanan parsial resolusi overload wrt. D * lebih terspesialisasi daripada B * jika D berasal dari B.

Detail pastinya agak rumit. Anda harus mencari tahu preseden dari berbagai aturan resolusi kelebihan beban. Pemesanan parsial adalah satu. Panjang / macam urutan konversi adalah satu lagi. Akhirnya, jika dua fungsi yang layak dianggap sama baiknya, non-templat dipilih daripada templat fungsi.

Saya tidak pernah perlu mencari tahu bagaimana aturan ini berinteraksi. Namun tampaknya pengurutan parsial mendominasi aturan resolusi berlebih lainnya. Ketika D tidak berasal dari B aturan pemesanan parsial tidak berlaku dan non-template lebih menarik. Ketika D berasal dari B, pengurutan parsial dimulai dan membuat template fungsi lebih menarik - seperti yang terlihat.

Adapun pewarisan menjadi privete: kode tidak pernah meminta konversi dari D * ke B * yang akan membutuhkan pewarisan publik.

menjual
sumber
Saya pikir itu seperti itu, saya ingat telah melihat diskusi ekstensif tentang arsip peningkatan tentang penerapan is_base_ofdan loop yang dilalui kontributor untuk memastikan hal ini.
Matthieu M.
The exact details are rather complicated- itulah intinya. Tolong jelaskan. Saya ingin tahu.
Alexey Malistov
@Alexey: Ya, saya pikir saya mengarahkan Anda ke arah yang benar. Lihat bagaimana berbagai aturan resolusi kelebihan beban berinteraksi dalam kasus ini. Satu-satunya perbedaan antara D yang berasal dari B dan D yang tidak berasal dari B sehubungan dengan penyelesaian kasus kelebihan beban ini adalah aturan urutan parsial. Resolusi kelebihan beban dijelaskan dalam §13 pada standar C ++. Anda bisa mendapatkan draf secara gratis: open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
sellibitze
Resolusi beban berlebih mencakup 16 halaman dalam draf itu. Saya kira, jika Anda benar-benar perlu memahami aturan dan interaksi di antara mereka untuk kasus ini, Anda harus membaca bagian lengkap §13.3. Saya tidak akan berharap mendapatkan jawaban di sini yang 100% benar dan sesuai dengan standar Anda.
sellibitze
silahkan lihat jawaban saya untuk penjelasannya jika anda tertarik.
Johannes Schaub - litb
0

Mengikuti pertanyaan kedua Anda, perhatikan bahwa jika bukan karena const, Host akan rusak jika dibuat dengan B == D. Tetapi is_base_of dirancang sedemikian rupa sehingga setiap kelas adalah basisnya sendiri, oleh karena itu salah satu operator konversi harus jadilah const.

Hertz
sumber