Jawaban yang diterima untuk pertanyaan introspeksi fungsi-anggota ini, meskipun populer, memiliki hambatan yang dapat diamati dalam program berikut:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
Dibangun dengan GCC 4.6.3, program keluaran 110
- memberitahu kita bahwa
T = std::shared_ptr<int>
tidak tidak memberikan int & T::operator*() const
.
Jika Anda belum bijaksana dengan gotcha ini, maka melihat definisi
std::shared_ptr<T>
di header <memory>
akan menjelaskan. Dalam implementasi itu, std::shared_ptr<T>
diturunkan dari kelas dasar dari mana ia mewarisi operator*() const
. Jadi contoh template
SFINAE<U, &U::operator*>
yang merupakan "menemukan" operator untuk
U = std::shared_ptr<T>
tidak akan terjadi, karena std::shared_ptr<T>
tidak ada
operator*()
memiliki haknya sendiri dan instantiasi template tidak "melakukan pewarisan".
Halangan ini tidak mempengaruhi pendekatan SFINAE yang terkenal, menggunakan "Trik sizeof ()", untuk mendeteksi hanya apakah T
memiliki beberapa fungsi anggota mf
(lihat misalnya
jawaban dan komentar ini). Tetapi membangun yang T::mf
ada sering (biasanya?) Tidak cukup baik: Anda mungkin juga perlu memastikan bahwa itu memiliki tanda tangan yang diinginkan. Di situlah skor teknik digambarkan. Varian terarah dari tanda tangan yang diinginkan tertulis dalam parameter tipe templat yang harus dipenuhi
&T::mf
agar probe SFINAE berhasil. Namun teknik cetakan template ini memberikan jawaban yang salah ketika T::mf
diwariskan.
Teknik SFINAE yang aman untuk introspeksi kompilasi dari T::mf
harus menghindari penggunaan di &T::mf
dalam argumen templat untuk membuat instantiate jenis di mana resolusi templat fungsi SFINAE bergantung. Sebagai gantinya, resolusi fungsi templat SFINAE hanya dapat bergantung pada deklarasi tipe yang tepat terkait yang digunakan sebagai tipe argumen dari fungsi probe SFINAE yang kelebihan beban.
Sebagai jawaban atas pertanyaan yang mematuhi batasan ini, saya akan mengilustrasikan untuk pendeteksian waktu E T::operator*() const
, untuk arbitrer T
dan E
. Pola yang sama akan berlaku mutatis mutandis
untuk menyelidiki tanda tangan metode anggota lainnya.
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
Dalam solusi ini, fungsi probe SFINAE yang kelebihan beban test()
"dipanggil secara rekursif". (Tentu saja itu tidak benar-benar dipanggil sama sekali; itu hanya memiliki jenis pengembalian pemanggilan hipotetis diselesaikan oleh kompiler.)
Kami perlu menyelidiki setidaknya satu dan paling banyak dua poin informasi:
- Apakah
T::operator*()
ada sama sekali? Jika tidak, kita sudah selesai.
- Mengingat
T::operator*()
ada, apakah tanda tangannya
E T::operator*() const
?
Kami mendapatkan jawaban dengan mengevaluasi tipe kembali dari satu panggilan ke test(0,0)
. Itu dilakukan oleh:
typedef decltype(test<T>(0,0)) type;
Panggilan ini mungkin diselesaikan dengan /* SFINAE operator-exists :) */
kelebihan test()
, atau mungkin memutuskan untuk /* SFINAE game over :( */
kelebihan. Itu tidak dapat mengatasi /* SFINAE operator-has-correct-sig :) */
kelebihan, karena yang satu hanya mengharapkan satu argumen dan kami melewati dua.
Mengapa kita melewati dua? Cukup dengan memaksa resolusi untuk dikecualikan
/* SFINAE operator-has-correct-sig :) */
. Argumen kedua tidak memiliki arti lain.
Panggilan ke ini test(0,0)
akan diselesaikan untuk /* SFINAE operator-exists :) */
berjaga-jaga jika argumen pertama 0 mengesahkan jenis parameter pertama dari kelebihan itu, yaitu decltype(&A::operator*)
, dengan A = T
. 0 akan memenuhi jenis itu untuk berjaga-jaga jika T::operator*
ada.
Anggap saja kompiler mengatakan Ya untuk itu. Maka itu akan dengan
/* SFINAE operator-exists :) */
dan perlu menentukan jenis kembali dari panggilan fungsi, yang dalam hal ini adalah decltype(test(&A::operator*))
- jenis kembali dari panggilan lain untuk test()
.
Kali ini, kami hanya menyampaikan satu argumen &A::operator*
, yang sekarang kami tahu ada, atau kami tidak akan berada di sini. Panggilan ke test(&A::operator*)
mungkin diselesaikan baik ke /* SFINAE operator-has-correct-sig :) */
atau lagi ke mungkin diselesaikan /* SFINAE game over :( */
. Panggilan akan cocok untuk
/* SFINAE operator-has-correct-sig :) */
berjaga-jaga jika &A::operator*
memenuhi jenis parameter tunggal dari kelebihan itu, yaitu E (A::*)() const
, dengan A = T
.
Kompiler akan mengatakan Ya di sini jika T::operator*
memiliki tanda tangan yang diinginkan, dan sekali lagi harus mengevaluasi jenis pengembalian kelebihan. Tidak ada lagi "rekursi" sekarang: itu std::true_type
.
Jika kompiler tidak memilih /* SFINAE operator-exists :) */
untuk panggilan test(0,0)
atau tidak memilih /* SFINAE operator-has-correct-sig :) */
untuk panggilan test(&A::operator*)
, maka dalam kedua kasus itu berjalan dengan
/* SFINAE game over :( */
dan jenis pengembalian akhir adalah std::false_type
.
Berikut adalah program pengujian yang menunjukkan templat yang menghasilkan jawaban yang diharapkan dalam beragam sampel kasus (GCC 4.6.3 lagi).
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
Apakah ada kekurangan baru dalam ide ini? Bisakah itu dibuat lebih umum tanpa sekali lagi jatuh dari halangan yang dihindarinya?