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];
Perhatikan bahwa itu
B
adalah pangkalan pribadi. Bagaimana cara kerjanya?Perhatikan bahwa
operator B*()
adalah const. Mengapa ini penting?Mengapa
template<typename T> static yes check(D*, T);
lebih baik daristatic yes check(B*, int);
?
Catatan : Ini adalah versi yang diperkecil (makro dihapus) dari boost::is_base_of
. Dan ini bekerja pada berbagai macam kompiler.
c++
templates
overloading
implicit-conversion
typetraits
Alexey Malistov
sumber
sumber
is_base_of
: ideone.com/T0C1V Ini tidak bekerja dengan versi GCC yang lebih lama (GCC4.3 berfungsi dengan baik).is_base_of<Base,Base>::value
seharusnyatrue
; ini kembalifalse
.Jawaban:
Jika mereka terkait
Mari sejenak kita asumsikan bahwa
B
itu sebenarnya adalah basisD
. Kemudian untuk panggilan kecheck
, kedua versi tersebut layak karenaHost
dapat diubah menjadiD*
danB*
. Ini adalah urutan konversi yang ditentukan pengguna seperti yang dijelaskan oleh13.3.3.1.2
dariHost<B, D>
keD*
danB*
masing - masing. Untuk menemukan fungsi konversi yang dapat mengonversi kelas, fungsi kandidat berikut disintesis untukcheck
fungsi pertama menurut13.3.1.5/1
Fungsi konversi pertama bukanlah kandidat, karena
B*
tidak dapat dikonversi menjadiD*
.Untuk fungsi kedua, ada kandidat berikut:
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 ) oleh13.3.3.2/3b1sb4
dan digunakan untuk mengonversi keB*
untukcheck
fungsi kedua .Jika Anda ingin menghapus const, kami akan memiliki kandidat berikut
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, karenaB*
bertobat lebih baik untukB*
daripadaD*
untukB*
.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 keD*
daripadaB*
. 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 aB*
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
Dan untuk yang kedua sekarang kita memiliki satu set lagi
Karena kami tidak dapat mengonversi
D*
menjadiB*
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 baiknya13.3.3/1
. Jadi, kami memilih fungsi non-templat (yang kedua) dan kami menyadari bahwa tidak ada warisan antaraB
danD
!sumber
std::is_base_of<...>
. Semuanya ada di bawah tenda.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 :)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 inicheck(...)
adalah ekspresi panggilan fungsi, jadi ia perlu melakukan resolusi berlebih padacheck
. Ada dua kelebihan kandidat yang tersedia,template <typename T> yes check(D*, T);
danno check(B*, int);
. Jika yang pertama dipilih, Anda mendapatkansizeof(yes)
, yang lainsizeof(no)
Selanjutnya, mari kita lihat resolusi kelebihan beban. Kelebihan pertama adalah instansiasi template
check<int> (D*, T=int)
dan kandidat kedua adalahcheck(B*, int)
. Argumen sebenarnya yang diberikan adalahHost<B,D>
danint()
. 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 melaluiHost<B,D>::operator B*() const
. Jika (dan hanya jika) B dan D terkait dengan warisan akan urutan konversiHost<B,D>::operator D*()
+ akanD*->B*
ada. Sekarang asumsikan D memang mewarisi dari B. Kedua urutan konversi adalahHost<B,D> -> Host<B,D> const -> operator B* const -> B*
danHost<B,D> -> operator D* -> D* -> B*
.Jadi, untuk terkait B dan D,
no check(<Host<B,D>(), int())
pasti ambigu. Hasilnya, templateyes check<int>(D*, int)
dipilih. Namun, jika D tidak mewarisi dari B, makano 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, yaituno 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 mengapaoperator B* const
must be const: jika tidak, tidak perluHost<B,D> -> Host<B,D> const
langkah, tidak ada ambiguitas, danno check(B*, int)
akan selalu dipilih.sumber
const
. Jika jawaban Anda benar maka tidakconst
diperlukan. Tapi itu tidak benar. Hapusconst
dan trik tidak akan berhasil.no check(B*, int)
tidak lagi ambigu.no check(B*, int)
, maka untuk terkaitB
danD
, itu tidak akan ambigu. Kompilator pasti akan memilihoperator 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 yangoperator B*()
menyediakan tipe pengembalian superior yang tidak memerlukan konversi pointerB*
sepertiD*
halnya.B*
dari yang<Host<B,D>()
sementara.The
private
bit benar-benar diabaikan olehis_base_of
karena resolusi yang berlebihan terjadi sebelum pemeriksaan aksesibilitas.Anda dapat memverifikasi ini dengan mudah:
Hal yang sama berlaku di sini, fakta bahwa
B
basis pribadi tidak mencegah pemeriksaan berlangsung, itu hanya akan mencegah konversi, tetapi kami tidak pernah meminta konversi yang sebenarnya;)sumber
host
diubah secara sewenang-wenang menjadiD*
atauB*
dalam ekspresi yang tidak dievaluasi. Untuk beberapa alasan,D*
lebih disukaiB*
dalam kondisi tertentu.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.
sumber
is_base_of
dan loop yang dilalui kontributor untuk memastikan hal ini.The exact details are rather complicated
- itulah intinya. Tolong jelaskan. Saya ingin tahu.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.
sumber