La accepté de répondre à cette question de la compile-time états-fonction
l'introspection, même si c'est à juste titre populaire, a un hic, ce qui peut être observé
dans le programme suivant:
#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;
}
Construit avec GCC 4.6.3, le programme des sorties 110
- nous informant que
T = std::shared_ptr<int>
ne pas fournir de l' int & T::operator*() const
.
Si vous n'êtes pas sage pour cette chasse aux sorcières, alors un coup d'oeil à la définition de
std::shared_ptr<T>
dans l'en-tête <memory>
jettera de la lumière. Dans ce
la mise en œuvre, std::shared_ptr<T>
est dérivée d'une classe de base
à partir de laquelle il hérite operator*() const
. Donc l'instanciation d'un modèle
SFINAE<U, &U::operator*>
qui constitue une "conclusion" de l'exploitant pour
U = std::shared_ptr<T>
ne se produira pas, car std::shared_ptr<T>
n'a pas
operator*()
dans son propre droit et l'instanciation d'un modèle n'est pas
"faire de l'héritage".
Cet os n'a pas d'incidence sur le bien-connu SFINAE approche, à l'aide de "Le sizeof() Truc",
pour la détection de simplement s' T
a certains membres de la fonction mf
(voir par ex.
cette réponse et les commentaires). Mais
l'établissement qu' T::mf
existe est souvent (?) pas assez bon: vous pouvez
également besoin d'établir qu'il a souhaité la signature. C'est là que le
illustré technique scores. Le pointerized variante de la signature souhaitée
s'inscrit dans un paramètre d'un type de modèle qui doit être satisfaite par
&T::mf
pour les SFINAE sonde pour réussir. Mais ce modèle de l'instanciation
technique donne la mauvaise réponse quand T::mf
est héréditaire.
Un coffre-fort SFINAE technique pour compile-time introspection d' T::mf
doit éviter l'
l'utilisation d' &T::mf
à l'intérieur d'un argument de modèle pour instancier un type qui SFINAE
modèle de fonction de la résolution dépend. Au lieu de cela, SFINAE fonction de modèle
la résolution ne peut dépendre seulement sur exactement pertinentes des déclarations de types utilisés
comme les types d'argument de la surcharge SFINAE de la sonde de fonction.
Une réponse à la question qui respecte cette contrainte, je vais
pour illustrer compile-time de détection d' E T::operator*() const
, pour
arbitraire T
et E
. Le même schéma s'appliqueront , mutatis mutandis,
pour la sonde pour tout autre membre de la signature de la méthode.
#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? */
};
Dans cette solution, la surcharge SFINAE de la sonde de la fonction test()
est "invoquée
de manière récursive". (Bien sûr, il n'est pas appelée en fait à tous; il a simplement
les types de retour hypothétique invocations résolu par le compilateur.)
Nous avons besoin de sonde pour au moins un et au plus deux points d'information:
- N'
T::operator*()
existe? Si non, on en a fait.
- Étant donné qu'
T::operator*()
existe, est sa signature
E T::operator*() const
?
Nous obtenons les réponses en évaluant le type de retour d'un seul appel
d' test(0,0)
. C'est ce que fait par:
typedef decltype(test<T>(0,0)) type;
Cet appel pourrait être résolu à l' /* SFINAE operator-exists :) */
de surcharge
d' test()
, ou il peut se résoudre à l' /* SFINAE game over :( */
de surcharge.
Il ne peut pas résoudre à l' /* SFINAE operator-has-correct-sig :) */
de surcharge,
parce que l'on s'attend à un seul argument et l'on passe de deux.
Pourquoi sommes-nous en passant par deux? Simplement pour forcer la résolution à exclure
/* SFINAE operator-has-correct-sig :) */
. Le deuxième argument n'a pas d'autres signifance.
Cet appel à l' test(0,0)
résoudra /* SFINAE operator-exists :) */
seulement
dans le cas où le premier argument 0 satisfait le premier paramètre de type de surcharge,
qui est - decltype(&A::operator*)
, A = T
. 0 répondre à ce type de
juste au cas T::operator*
existe.
Supposons que le compilateur dire Oui à cela. Puis il va avec
/* SFINAE operator-exists :) */
et il doit déterminer le type de retour de
l'appel de la fonction, qui dans ce cas est - decltype(test(&A::operator*))
-
le type de retour de encore un autre appel à l' test()
.
Cette fois, nous passons à un seul argument, &A::operator*
, que nous avons maintenant
savez qu'il existe, ou nous ne serions pas ici. Un appel à l' test(&A::operator*)
peut
résoudre les problèmes d' /* SFINAE operator-has-correct-sig :) */
ou encore à
pourrait résoudre /* SFINAE game over :( */
. L'appel de match
/* SFINAE operator-has-correct-sig :) */
seulement en cas &A::operator*
satisfait
le seul paramètre de type de surcharge, qui est - E (A::*)() const
,
avec A = T
.
Le compilateur va dire Oui ici si T::operator*
a souhaité que la signature,de la
et puis de nouveau à évaluer le type de retour de la surcharge. Pas plus
"récurrences" maintenant: c'est - std::true_type
.
Si le compilateur ne pas choisir /* SFINAE operator-exists :) */
pour les
appelez test(0,0)
ou de ne pas choisir /* SFINAE operator-has-correct-sig :) */
pour l'appel test(&A::operator*)
, puis dans les deux cas, il va avec
/* SFINAE game over :( */
et le dernier type de retour est - std::false_type
.
Voici un programme de test qui montre que le modèle de production attendus
des réponses dans l'échantillon varié de cas (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;
}
Sont t-il de nouvelles failles dans cette idée? Cela peut-il être plus générique sans, encore une fois
tomber sous le coup de l'accrocher elle évite?