Je demande une astuce de modèle pour détecter si une classe a une fonction membre spécifique d'une signature donnée.
Le problème est similaire à celui cité ici http://www.gotw.ca/gotw/071.htm mais pas le même: dans l'article du livre de Sutter, il a répondu à la question qu'une classe C DOIT FOURNIR une fonction membre avec une signature particulière, sinon le programme ne compilera pas. Dans mon problème, je dois faire quelque chose si une classe a cette fonction, sinon faire "autre chose".
Un problème similaire a été rencontré par boost :: serialization mais je n'aime pas la solution qu'ils ont adoptée: une fonction de modèle qui appelle par défaut une fonction libre (que vous devez définir) avec une signature particulière, sauf si vous définissez une fonction membre particulière ( dans leur cas "sérialiser" qui prend 2 paramètres d'un type donné) avec une signature particulière, sinon une erreur de compilation se produira. Il s'agit d'implémenter une sérialisation intrusive et non intrusive.
Je n'aime pas cette solution pour deux raisons:
J'ai besoin de définir un comportement personnalisé pour les classes qui n'ont pas cette fonction membre, et mes entités sont dans des espaces de noms différents (et je ne veux pas remplacer une fonction globale définie dans un espace de noms pendant que je suis dans un autre)
Pouvez-vous me donner un indice pour résoudre ce puzzle?
Je ne sais pas si je vous comprends bien, mais vous pouvez exploiter SFINAE pour détecter la présence de fonction au moment de la compilation. Exemple de mon code (teste si la classe a la fonction membre size_t used_memory () const).
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
Voici une implémentation possible basée sur les fonctionnalités C++ 11. Il détecte correctement la fonction même si elle est héritée (contrairement à la solution dans la réponse acceptée, comme l'observe Mike Kinghan dans sa réponse ).
La fonction pour laquelle cet extrait de test est appelée serialize
:
#include <type_traits>
// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.
template<typename, typename T>
struct has_serialize {
static_assert(
std::integral_constant<T, false>::value,
"Second template parameter needs to be of function type.");
};
// specialization that does the checking
template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
template<typename T>
static constexpr auto check(T*)
-> typename
std::is_same<
decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>::type; // attempt to call it and see if the return type is correct
template<typename>
static constexpr std::false_type check(...);
typedef decltype(check<C>(0)) type;
public:
static constexpr bool value = type::value;
};
Usage:
struct X {
int serialize(const std::string&) { return 42; }
};
struct Y : X {};
std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
La réponse acceptée à cette question de l'introspection des fonctions membres de la compilation, bien qu'elle soit à juste titre populaire, comporte un hic 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 génère 110
- nous informant que T = std::shared_ptr<int>
Ne pas fournit la fonction int & T::operator*() const
.
Si vous n'êtes pas déjà sage pour ce piège, un coup d'œil à la définition de std::shared_ptr<T>
Dans l'en-tête <memory>
Fera la lumière. Dans cette implémentation, std::shared_ptr<T>
Est dérivé d'une classe de base dont il hérite operator*() const
. Donc l'instanciation du modèle SFINAE<U, &U::operator*>
Qui constitue la "recherche" de l'opérateur pour U = std::shared_ptr<T>
Ne se produira pas, car std::shared_ptr<T>
N'a pas de operator*()
à part entière et de modèle l'instanciation ne "fait pas d'héritage".
Cet accroc n'affecte pas l'approche bien connue de SFINAE, utilisant "The sizeof () Trick", pour détecter simplement si T
a une fonction membre mf
(voir par exemple cette réponse et commentaires). Mais établir que T::mf
Existe est souvent (généralement?) Insuffisant: vous devrez peut-être également établir qu'il a la signature souhaitée. C'est là que la technique illustrée marque. La variante pointée de la signature souhaitée est inscrite dans un paramètre d'un type de modèle qui doit être satisfait par &T::mf
Pour que la sonde SFINAE réussisse. Mais cette technique d'instanciation de modèle donne la mauvaise réponse lorsque T::mf
Est hérité.
Une technique SFINAE sûre pour l'introspection en temps de compilation de T::mf
Doit éviter l'utilisation de &T::mf
Dans un argument de modèle pour instancier un type dont dépend la résolution du modèle de fonction SFINAE. Au lieu de cela, la résolution de la fonction de modèle SFINAE ne peut dépendre que de déclarations de type exactement pertinentes utilisées comme types d'argument de la fonction de sonde SFINAE surchargée.
En guise de réponse à la question qui respecte cette contrainte, je vais l'illustrer pour la détection de temps de compilation de E T::operator*() const
, pour arbitraire T
et E
. Le même modèle s'appliquera mutatis mutandis pour rechercher toute autre signature de méthode membre.
#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 fonction de sonde SFINAE surchargée test()
est "invoquée récursivement". (Bien sûr, il n'est en fait pas du tout invoqué; il a simplement les types de retour des invocations hypothétiques résolues par le compilateur.)
Nous devons rechercher au moins un et au plus deux points d'information:
T::operator*()
existe-t-il? Sinon, nous avons terminé.T::operator*()
existe, sa signature est-elle E T::operator*() const
?Nous obtenons les réponses en évaluant le type de retour d'un seul appel à test(0,0)
. Cela se fait par:
typedef decltype(test<T>(0,0)) type;
Cet appel peut être résolu en surcharge /* SFINAE operator-exists :) */
De test()
, ou il peut se résoudre en surcharge /* SFINAE game over :( */
. Il ne peut pas résoudre la surcharge /* SFINAE operator-has-correct-sig :) */
, Car celui-ci attend un seul argument et nous en passons deux.
Pourquoi passons-nous deux? Simplement pour forcer la résolution à exclure /* SFINAE operator-has-correct-sig :) */
. Le deuxième argument n'a pas d'autre signification.
Cet appel à test(0,0)
se résoudra en /* SFINAE operator-exists :) */
Juste au cas où le premier argument 0 satifierait le premier type de paramètre de cette surcharge, qui est decltype(&A::operator*)
, avec A = T
. 0 satisfera ce type au cas où T::operator*
Existe.
Supposons que le compilateur dise Oui à cela. Ensuite, cela va avec /* SFINAE operator-exists :) */
Et il doit déterminer le type de retour de l'appel de fonction, qui dans ce cas est decltype(test(&A::operator*))
- le type de retour d'un autre appel à test()
.
Cette fois, nous passons un seul argument, &A::operator*
, Dont nous savons maintenant qu'il existe, sinon nous ne serions pas là. Un appel à test(&A::operator*)
peut être résolu soit en /* SFINAE operator-has-correct-sig :) */
, Soit de nouveau en peut se résoudre en /* SFINAE game over :( */
. L'appel correspondra à /* SFINAE operator-has-correct-sig :) */
Juste au cas où &A::operator*
Satisfait le type de paramètre unique de cette surcharge, qui est E (A::*)() const
, avec A = T
.
Le compilateur dira Oui ici si T::operator*
A la signature souhaitée, puis doit à nouveau évaluer le type de retour de la surcharge. Plus de "récursions" maintenant: c'est std::true_type
.
Si le compilateur ne choisit pas /* SFINAE operator-exists :) */
Pour l'appel test(0,0)
ou ne choisit pas /* SFINAE operator-has-correct-sig :) */
Pour l'appel test(&A::operator*)
, alors dans les deux cas, il va de pair avec /* SFINAE game over :( */
Et le type de retour final est std::false_type
.
Voici un programme de test qui montre le modèle produisant les réponses attendues dans un échantillon varié de cas (GCC 4.6.3 à nouveau).
// 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;
}
Y a-t-il de nouveaux défauts dans cette idée? Peut-il être rendu plus générique sans tomber à nouveau dans le piège qu'il évite?
Voici quelques extraits d'utilisation: * Les tripes pour tout cela sont plus bas
Vérifier le membre x
dans une classe donnée. Peut être var, func, class, union ou enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Vérifier la fonction membre void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
Vérifier la variable membre x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Vérifiez la classe membre x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Vérifiez le syndicat membre x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Vérifier l'énumération des membres x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Recherchez toute fonction membre x
quelle que soit la signature:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
OR
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Détails et noyau:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
Cela devrait être suffisant si vous connaissez le nom de la fonction membre que vous attendez. (Dans ce cas, la fonction bla ne parvient pas à instancier s'il n'y a pas de fonction membre (en écrire une qui fonctionne de toute façon est difficile car il y a un manque de spécialisation partielle de la fonction. Vous devrez peut-être utiliser des modèles de classe). De plus, la structure enable (qui est similaire à enable_if) pourrait également être basé sur le type de fonction que vous souhaitez qu'il ait en tant que membre.
template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}
Pour ce faire, nous devons utiliser:
type_traits
, nous souhaitons renvoyer un true_type
Ou false_type
= de nos surchargestrue_type
En attendant une surcharge int
et false_type
En attendant que les paramètres Variadic exploitent: "La priorité la plus basse de la conversion Ellipsis dans la résolution de surcharge" =true_type
, Nous utiliserons declval
et decltype
nous permettant de détecter la fonction indépendamment des différences de type de retour ou des surcharges entre les méthodesVous pouvez voir un exemple en direct de cela ici . Mais je l'expliquerai également ci-dessous:
Je veux vérifier l'existence d'une fonction nommée test
qui prend un type convertible de int
, alors je devrais déclarer ces deux fonctions:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
decltype(hasTest<a>(0))::value
est true
(Notez qu'il n'est pas nécessaire de créer une fonctionnalité spéciale pour gérer la surcharge de void a::test()
, la void a::test(int)
est acceptée)decltype(hasTest<b>(0))::value
est true
(parce que int
est convertible en double
int b::test(double)
est accepté, indépendamment du type de retour)decltype(hasTest<c>(0))::value
est false
(c
n'a pas de méthode nommée test
qui accepte un type convertible de int
donc cela n'est pas accepté )Cette solution présente 2 inconvénients:
test()
?Il est donc important que ces fonctions soient déclarées dans un espace de noms de détails, ou idéalement si elles ne doivent être utilisées qu'avec une classe, elles devraient être déclarées en privé par cette classe. À cette fin, j'ai écrit une macro pour vous aider à résumer ces informations:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
Vous pouvez utiliser ceci comme:
namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
Par la suite, appeler details::test_int<a>::value
Ou details::test_void<a>::value
Donnerait true
ou false
à des fins de code en ligne ou de méta-programmation.
Voici une version plus simple de la réponse de Mike Kinghan. Cela détectera les méthodes héritées. Il vérifiera également la signature exacte (contrairement à l'approche de jrok qui permet les conversions d'arguments).
template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);
template <class T>
static decltype(testSignature(&T::greet)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");
Exécutable exemple
Vous pouvez utiliser std :: is_member_function_pointer
class A {
public:
void foo() {};
}
bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Je suis moi-même venu avec le même type de problème et j'ai trouvé les solutions proposées ici très intéressantes ... mais j'avais besoin d'une solution qui:
Trouvé un autre thread proposant quelque chose comme ça, basé sur un discussion BOOST . Voici la généralisation de la solution proposée sous forme de deux déclarations de macros pour la classe de traits, suivant le modèle des classes boost :: has _ * .
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>
/// Has constant function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)
/// Has non-const function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)
// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \
template \
< typename Type, \
bool is_class = boost::is_class<Type>::value \
> \
class has_func_ ## func_name; \
template<typename Type> \
class has_func_ ## func_name<Type,false> \
{public: \
BOOST_STATIC_CONSTANT( bool, value = false ); \
typedef boost::false_type type; \
}; \
template<typename Type> \
class has_func_ ## func_name<Type,true> \
{ struct yes { char _foo; }; \
struct no { yes _foo[2]; }; \
struct Fallback \
{ func_ret_type func_name( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const) {} \
}; \
struct Derived : public Type, public Fallback {}; \
template <typename T, T t> class Helper{}; \
template <typename U> \
static no deduce(U*, Helper \
< func_ret_type (Fallback::*)( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const), \
&U::func_name \
>* = 0 \
); \
static yes deduce(...); \
public: \
BOOST_STATIC_CONSTANT( \
bool, \
value = sizeof(yes) \
== sizeof( deduce( static_cast<Derived*>(0) ) ) \
); \
typedef ::boost::integral_constant<bool,value> type; \
BOOST_STATIC_CONSTANT(bool, is_const = func_const); \
typedef func_ret_type return_type; \
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \
}
// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__
Ces macros s'étendent à une classe de traits avec le prototype suivant:
template<class T>
class has_func_[func_name]
{
public:
/// Function definition result value
/** Tells if the tested function is defined for type T or not.
*/
static const bool value = true | false;
/// Function definition result type
/** Type representing the value attribute usable in
http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
*/
typedef boost::integral_constant<bool,value> type;
/// Tested function constness indicator
/** Indicates if the tested function is const or not.
This value is not deduced, it is forced depending
on the user call to one of the traits generators.
*/
static const bool is_const = true | false;
/// Tested function return type
/** Indicates the return type of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef func_ret_type return_type;
/// Tested function arguments types
/** Indicates the arguments types of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};
Alors, quelle est l'utilisation typique que l'on peut en faire?
// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
// Next line will declare the traits class
// to detect the member function void foo(int,int) const
DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}
// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>
// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ _this_.foo(a,b);
}
// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ default_foo(_this_,a,b);
}
// Let us declare test types
struct empty
{
};
struct direct_foo
{
void foo(int,int);
};
struct direct_const_foo
{
void foo(int,int) const;
};
struct inherited_const_foo :
public direct_const_foo
{
};
// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
int a;
foo_bar(a); // calls default_foo
empty b;
foo_bar(b); // calls default_foo
direct_foo c;
foo_bar(c); // calls default_foo (member function is not const)
direct_const_foo d;
foo_bar(d); // calls d.foo (member function is const)
inherited_const_foo e;
foo_bar(e); // calls e.foo (inherited member function)
}
Pour être non intrusif, vous pouvez également mettre serialize
dans l'espace de noms de la classe en cours de sérialisation, ou de la classe archive, grâce à recherche Koenig . Voir Namespaces for Free Function Overrides pour plus de détails. :-)
Ouvrir un espace de noms donné pour implémenter une fonction gratuite est tout simplement faux. (Par exemple, vous n'êtes pas censé ouvrir l'espace de noms std
pour implémenter swap
pour vos propres types, mais vous devriez utiliser la recherche Koenig à la place.)
D'accord. Deuxième essai. Ce n'est pas grave si vous ne l'aimez pas non plus, je cherche plus d'idées.
L'article de Herb Sutter parle des traits. Vous pouvez donc avoir une classe de traits dont l'instanciation par défaut a le comportement de secours, et pour chaque classe où existe votre fonction membre, la classe de traits est spécialisée pour appeler la fonction membre. Je crois que l'article de Herb mentionne une technique pour ce faire afin qu'elle n'implique pas beaucoup de copier-coller.
Comme je l'ai dit, cependant, vous ne voulez peut-être pas le travail supplémentaire impliqué avec les classes de "marquage" qui implémentent ce membre. Dans ce cas, j'envisage une troisième solution ...
Sans le support de C++ 11 (decltype
), cela pourrait fonctionner:
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
A
, Aa
et B
sont les classes en question, Aa
étant la classe spéciale qui hérite du membre que nous recherchons.
Dans le FooFinder
le true_type
et false_type
sont les remplacements des classes C++ 11 correspondantes. Également pour la compréhension de la méta-programmation de modèles, ils révèlent la base même de l'astuce SFINAE-sizeof.
TypeSink
est une structure de modèle qui sera utilisée plus tard pour plonger le résultat intégral de l'opérateur sizeof
dans une instanciation de modèle pour former un type.
La fonction match
est un autre type de modèle SFINAE qui est laissé sans équivalent générique. Il ne peut donc être instancié que si le type de son argument correspond au type pour lequel il était spécialisé.
Les fonctions test
et la déclaration enum forment finalement le modèle central SFINAE. Il y en a un générique utilisant un Ellipsis qui retourne le false_type
et une contrepartie avec des arguments plus spécifiques pour avoir la priorité.
Pour pouvoir instancier la fonction test
avec un argument de modèle de T
, la fonction match
doit être instanciée, car son type de retour est requis pour instancier la TypeSink
argument. La mise en garde est que &U::foo
, étant enveloppé dans un argument de fonction, est pas référencé à partir d'une spécialisation d'argument de modèle, donc la recherche de membre hérité a toujours lieu.
Je crois que la réponse que vous cherchez est ici.
http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time
et un exemple un peu plus complet ici
J'utilise la technique pour détecter la présence d'un opérateur ostream de support << sur la classe en question, puis générer un bit de code différent en fonction.
Je ne pensais pas que c'était possible avant de trouver la solution liée, mais c'est une astuce très intéressante. Passez du temps à comprendre le code et cela en vaut la peine.
Brad
Si vous utilisez la folie Facebook, leur macro est prête à l'emploi pour vous aider:
#include <folly/Traits.h>
namespace {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace
void some_func() {
cout << "Does class Foo have a member int test() const? "
<< boolalpha << has_test_traits<Foo, int() const>::value;
}
Bien que les détails d'implémentation soient les mêmes qu'avec la réponse précédente, l'utilisation d'une bibliothèque est plus simple.