Bu derleme zamanlı üye işlevi iç gözlem sorusuna verilen kabul edilen yanıt, haklı olarak popüler olmasına rağmen, aşağıdaki programda gözlemlenebilecek bir engele sahiptir:
#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;
}
GCC 4.6.3 ile oluşturulmuş program çıktıları 110
- bizi bilgilendirdiğiniz olduğunu
T = std::shared_ptr<int>
gelmez değil sağlamak int & T::operator*() const
.
Zaten bu konuya akıllı değilseniz std::shared_ptr<T>
, başlıktaki tanımına bir göz atmak
<memory>
ışık tutacaktır. Bu uygulamada, std::shared_ptr<T>
miras aldığı bir temel sınıftan türetilir operator*() const
. Dolayısıyla, SFINAE<U, &U::operator*>
işleci "bulmayı" oluşturan
şablon somutlaştırma
U = std::shared_ptr<T>
gerçekleşmeyecektir, çünkü kendi başına std::shared_ptr<T>
hiçbir
operator*()
hakkı yoktur ve şablon somutlaştırması "kalıtım yapmaz".
Bu engel, sadece T
üye işlevine sahip olup olmadığını tespit etmek için "sizeof () Trick" i kullanan iyi bilinen SFINAE yaklaşımını etkilemez mf
(örneğin,
bu cevaba ve yorumlara bakın). Ancak T::mf
var olduğunu tespit etmek çoğu zaman (genellikle?) Yeterince iyi değildir: İstenilen bir imzaya sahip olduğunu da belirlemeniz gerekebilir. Resimli tekniğin puan aldığı yer burasıdır. İstenen imzanın işaretli varyantı &T::mf
, SFINAE sondasının başarılı olması için yerine getirilmesi gereken bir şablon türünün bir parametresine yazılmıştır
. Ancak bu şablon örnekleme tekniği T::mf
, miras alındığında yanlış yanıt verir .
Derleme zamanı iç gözlemi için güvenli bir SFINAE tekniği, SFINAE işlev şablonu çözümlemesinin bağlı olduğu bir türü somutlaştırmak için bir şablon bağımsız değişkeninin T::mf
kullanımından kaçınmalıdır &T::mf
. Bunun yerine, SFINAE şablon işlev çözümlemesi, yalnızca aşırı yüklenmiş SFINAE araştırma işlevinin bağımsız değişken türleri olarak kullanılan tam olarak uygun tür bildirimlerine bağlı olabilir.
Bu kısıtlamaya uyan soruya bir cevap olarak, E T::operator*() const
rasgele T
ve için derleme zamanı tespiti için örnekleyeceğim E
. Aynı model,
herhangi bir başka üye yöntemi imzasını araştırmak için gerekli değişiklikler yapılarak uygulanacaktır .
#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? */
};
Bu çözümde, aşırı yüklenmiş SFINAE araştırma işlevi test()
"özyinelemeli olarak çağrılır". (Elbette aslında çağrılmamıştır; yalnızca derleyici tarafından çözülen varsayımsal çağrıların dönüş türlerine sahiptir.)
En az bir ve en fazla iki bilgi noktasını araştırmamız gerekiyor:
- Hiç
T::operator*()
var mı ? Değilse, işimiz bitti.
- Var olduğu göz önüne alındığında
T::operator*()
, imzası
E T::operator*() const
mı?
Cevapları tek bir aramanın dönüş türünü değerlendirerek alıyoruz test(0,0)
. Bunu yapan:
typedef decltype(test<T>(0,0)) type;
Bu çağrı, /* SFINAE operator-exists :) */
aşırı yüklemeye çözümlenebilir test()
veya aşırı yüklemeye çözümlenebilir /* SFINAE game over :( */
. Bu /* SFINAE operator-has-correct-sig :) */
aşırı yüklemeyi çözemez , çünkü bu sadece bir argüman bekliyor ve biz ikisini geçiyoruz.
Neden ikiyi geçiyoruz? Çözünürlüğü hariç tutmaya zorlamak için
/* SFINAE operator-has-correct-sig :) */
. İkinci argümanın başka bir önemi yoktur.
Bu çağrı test(0,0)
için irade kararlılığı /* SFINAE operator-exists :) */
her ihtimale karşı ilk argüman 0 satifies olduğu aşırı yük, ilk parametresi tür decltype(&A::operator*)
ile, A = T
. 0 olması durumunda bu türü tatmin edecektir T::operator*
.
Derleyicinin buna Evet dediğini varsayalım. Daha sonra devam
/* SFINAE operator-exists :) */
eder ve işlev çağrısının dönüş türünü belirlemesi gerekir, bu durumda decltype(test(&A::operator*))
- başka bir çağrının dönüş türü test()
.
Bu sefer, var olduğunu bildiğimiz tek bir argüman geçiriyoruz &A::operator*
, yoksa burada olmazdık. Bir çağrı test(&A::operator*)
ya kudreti kararlılığının /* SFINAE operator-has-correct-sig :) */
veya tekrar etmek kudreti Çözmek /* SFINAE game over :( */
. Çağrı maç olacak
/* SFINAE operator-has-correct-sig :) */
durumda sadece &A::operator*
tatmin olduğu aşırı yük, tek parametre türü E (A::*)() const
ile A = T
.
Derleyici, T::operator*
istenen imzaya sahipse burada Evet diyecek ve sonra tekrar aşırı yüklenmenin dönüş türünü değerlendirmesi gerekecektir. Artık "özyineleme" yok: öyle std::true_type
.
Derleyici /* SFINAE operator-exists :) */
çağrı için seçim yapmazsa test(0,0)
veya çağrı /* SFINAE operator-has-correct-sig :) */
için seçim yapmazsa test(&A::operator*)
, her iki durumda da birlikte gider
/* SFINAE game over :( */
ve son dönüş türü olur std::false_type
.
Burada, çeşitli vaka örneklerinde beklenen yanıtları üreten şablonu gösteren bir test programı bulunmaktadır (yine GCC 4.6.3).
// 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;
}
Bu fikirde yeni kusurlar var mı? Bir kez daha kaçındığı engele takılmadan daha genel hale getirilebilir mi?