Saya menyaksikan pembicaraan Walter Brown di Cppcon14 tentang pemrograman template modern ( Bagian I , Bagian II ) di mana ia mempresentasikan void_t
teknik SFINAE- nya .
Contoh:
Diberi templat variabel sederhana yang mengevaluasi void
jika semua argumen templat terbentuk dengan baik:
template< class ... > using void_t = void;
dan sifat berikut yang memeriksa keberadaan variabel anggota yang disebut anggota :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
Saya mencoba memahami mengapa dan bagaimana ini bekerja. Karena itu contoh kecil:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
adadecltype( A::member )
terbentuk dengan baikvoid_t<>
valid dan dievaluasi untukvoid
has_member< A , void >
dan karena itu memilih templat khusushas_member< T , void >
dan mengevaluasi ketrue_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
tidak adadecltype( B::member )
berbentuk buruk dan gagal diam-diam (sfinae)has_member< B , expression-sfinae >
jadi templat ini dibuang
- kompiler ditemukan
has_member< B , class = void >
dengan void sebagai argumen default has_member< B >
mengevaluasi kefalse_type
Pertanyaan:
1. Apakah pemahaman saya tentang ini benar?
2. Walter Brown menyatakan bahwa argumen default haruslah tipe yang sama persis seperti yang digunakan void_t
agar bisa berfungsi. Mengapa demikian? (Saya tidak mengerti mengapa jenis ini harus cocok, bukankah sembarang jenis standar berfungsi?)
has_member<A,int>::value
. Kemudian, spesialisasi parsial yang mengevaluasihas_member<A,void>
tidak dapat cocok. Oleh karena itu, perluhas_member<A,void>::value
, atau, dengan gula sintaksis, argumen tipe defaultvoid
.has_member< T , class = void >
defaultingvoid
. Dengan asumsi sifat ini hanya akan digunakan dengan 1 argumen templat kapan saja, maka argumen default dapat berupa jenis apa saja?template <class, class = void>
menjaditemplate <class, class = void_t<>>
. Jadi sekarang kita bebas untuk melakukan apa pun yang kita inginkan denganvoid_t
implementasi template alias :)Jawaban:
1. Templat Kelas Utama
Saat Anda menulis
has_member<A>::value
, kompiler mencari namahas_member
dan menemukan templat kelas utama , yaitu deklarasi ini:(Dalam OP, itu ditulis sebagai definisi.)
Daftar argumen templat
<A>
dibandingkan dengan daftar parameter templat templat primer ini. Sejak template primer memiliki dua parameter, tetapi Anda hanya diberikan satu, parameter yang tersisa gagal untuk template argumen default:void
. Seolah-olah Anda telah menulishas_member<A, void>::value
.2. Template Kelas Khusus
Sekarang , daftar parameter template dibandingkan dengan spesialisasi template
has_member
. Hanya jika tidak ada spesialisasi yang cocok, definisi templat utama digunakan sebagai mundur. Jadi spesialisasi parsial diperhitungkan:Kompiler mencoba untuk mencocokkan argumen templat
A, void
dengan pola yang ditentukan dalam spesialisasi parsial:T
danvoid_t<..>
satu per satu. Pertama , pengurangan argumen templat dilakukan. Spesialisasi parsial di atas masih berupa templat dengan parameter templat yang perlu "diisi" oleh argumen.Pola pertama
T
, memungkinkan kompiler untuk menyimpulkan parameter templateT
. Ini adalah pengurangan sepele, tetapi pertimbangkan pola sepertiT const&
, di mana kita masih dapat menyimpulkanT
. UntukT
argumen pola dan templatA
, kami menyimpulkanT
demikianA
.Dalam pola kedua
void_t< decltype( T::member ) >
, parameter templateT
muncul dalam konteks di mana parameter tidak dapat dideduksi dari argumen template apa pun.Argumen template deduksi selesai (*) , sekarang yang dideduksi argumen template yang diganti. Ini menciptakan spesialisasi yang terlihat seperti ini:
Jenis
void_t< decltype( A::member ) >
sekarang dapat dievaluasi. Ini terbentuk dengan baik setelah substitusi, karenanya, tidak ada Kegagalan Substitusi terjadi. Kita mendapatkan:3. Pilihan
Sekarang , kita dapat membandingkan daftar parameter templat dari spesialisasi ini dengan argumen templat yang disediakan ke aslinya
has_member<A>::value
. Kedua jenis sama persis, sehingga spesialisasi parsial ini dipilih.Di sisi lain, ketika kita mendefinisikan templat sebagai:
Kami berakhir dengan spesialisasi yang sama:
tetapi daftar argumen templat kami untuk
has_member<A>::value
saat ini adalah<A, int>
. Argumen tidak cocok dengan parameter spesialisasi, dan templat utama dipilih sebagai mundur.(*) Standar, IMHO membingungkan, termasuk proses substitusi dan pencocokan argumen template yang ditentukan secara eksplisit dalam proses pengurangan argumen template . Misalnya (post-N4296) [temp.class.spec.match] / 2:
Tetapi ini tidak hanya berarti bahwa semua parameter templat dari spesialisasi parsial harus disimpulkan; itu juga berarti bahwa substitusi harus berhasil dan (sepertinya?) argumen templat harus cocok dengan parameter templat (diganti) dari spesialisasi parsial. Perhatikan bahwa saya tidak sepenuhnya mengetahui di mana Standar menentukan perbandingan antara daftar argumen yang diganti dan daftar argumen yang disediakan.
sumber
Spesialisasi di atas hanya ada ketika itu terbentuk dengan baik, jadi ketika
decltype( T::member )
itu valid dan tidak ambigu. spesialisasi itu seperti untukhas_member<T , void>
negara dalam komentar.Ketika Anda menulis
has_member<A>
, ituhas_member<A, void>
karena argumen templat default.Dan kami memiliki spesialisasi untuk
has_member<A, void>
(jadi mewarisi daritrue_type
) tetapi kami tidak memiliki spesialisasi untukhas_member<B, void>
(jadi kami menggunakan definisi default: mewarisi darifalse_type
)sumber