Vérifier si une classe a une fonction membre d’une signature donnée

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 le 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 comstackra 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é avec boost :: serialization mais je n’aime pas la solution qu’ils ont adoptée: une fonction template qui appelle par défaut une fonction libre (que vous devez définir) avec une signature particulière à moins que vous ne définissiez une fonction membre ( dans leur cas, “serialize” qui prend 2 parameters d’un type donné) avec une signature particulière, sinon une erreur de compilation se produira. Cela consiste à mettre en œuvre une sérialisation à la fois intrusive et non intrusive.

Je n’aime pas cette solution pour deux raisons:

  1. Pour ne pas être intrusif, vous devez remplacer la fonction globale “serialize” qui se trouve dans le namespace boost :: serialization, vous avez donc IN YOUR CLIENT CODE pour ouvrir le boost d’espace de nommage et la sérialisation des espaces de noms!
  2. La stack pour résoudre ce problème était de 10 à 12 invocations de fonctions.

Je dois définir un comportement personnalisé pour les classes qui n’ont pas cette fonction membre et mes entités se trouvent dans des espaces de noms différents (et je ne veux pas remplacer une fonction globale définie dans un espace de noms lorsque je suis dans un autre)

Pouvez-vous me donner un indice pour résoudre ce casse-tête?

Je ne sais pas si je vous comprends bien, mais vous pouvez exploiter SFINAE pour détecter la présence de la fonction à la compilation. Exemple de mon code (teste si la classe a la fonction membre size_t used_memory () const).

template struct HasUsedMemoryMethod { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool Has = sizeof(Test(0)) == sizeof(char); }; template void ReportMemUsage(const TMap& m, std::true_type) { // We may call used_memory() on m here. } template void ReportMemUsage(const TMap&, std::false_type) { } template void ReportMemUsage(const TMap& m) { ReportMemUsage(m, std::integral_constant::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 Mike Kinghan l’observe dans sa réponse ).

La fonction testée par cet extrait est appelée serialize :

 #include  // 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 struct has_serialize { static_assert( std::integral_constant::value, "Second template parameter needs to be of function type."); }; // specialization that does the checking template struct has_serialize { private: template static constexpr auto check(T*) -> typename std::is_same< decltype( std::declval().serialize( std::declval()... ) ), Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >::type; // attempt to call it and see if the return type is correct template static constexpr std::false_type check(...); typedef decltype(check(0)) type; public: static constexpr bool value = type::value; }; 

Usage:

 struct X { int serialize(const std::ssortingng&) { return 42; } }; struct Y : X {}; std::cout << has_serialize::value; // will print 1 

La réponse acceptée à cette question de l’introspection des fonctions membres, même si elle est justement populaire, a un problème qui peut être observé dans le programme suivant:

 #include  #include  #include  /* Here we apply the accepted answer's technique to probe for the the existence of `ET::operator*() const` */ template struct has_const_reference_op { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool value = sizeof(Test(0)) == sizeof(char); }; using namespace std; /* Here we test the `std::` smart pointer templates, including the deprecated `auto_ptr`, 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,int &>::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op,int &>::value << endl; return 0; } 

Construit avec GCC 4.6.3, le programme affiche 110 - nous informant que T = std::shared_ptr ne fournit pas int & T::operator*() const .

Si vous n'êtes pas déjà au courant de ce piège, alors la définition de std::shared_ptr dans l'en-tête fera la lumière. Dans cette implémentation, std::shared_ptr est dérivé d'une classe de base à partir de laquelle il hérite de l' operator*() const . L'instanciation de template SFINAE qui constitue "la recherche" de l'opérateur pour U = std::shared_ptr ne se produira donc pas, car std::shared_ptr n'a pas d' operator*() dans son propre droit et l'instanciation du modèle ne "font pas l'inheritance".

Ce snag n'affecte pas l'approche SFINAE bien connue, en utilisant "The sizeof () Trick", pour détecter simplement si T a une fonction membre mf (voir par exemple cette réponse et des 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 de comstacktime 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 des 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 illustrer la détection de compiléime de ET::operator*() const , pour T et E arbitraires. Le même schéma s’appliquera mutatis mutandis à la recherche de toute autre signature de méthode de membre.

 #include  /*! The template `has_const_reference_op` exports a boolean constant `value that is true iff `T` provides `ET::operator*() const` */ template< typename T, typename E> struct has_const_reference_op { /* SFINAE operator-has-correct-sig :) */ template static std::true_type test(E (A::*)() const) { return std::true_type(); } /* SFINAE operator-exists :) */ template  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 static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test(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 pas du tout invoqué; il a simplement les types d’invocations hypothétiques renvoyés par le compilateur).

Nous devons rechercher au moins un et au plus deux points d'information:

  • Est-ce que T::operator*() existe du tout? Sinon, nous avons terminé.
  • Étant donné que T::operator*() existe, sa signature est-elle ET::operator*() const ?

Nous obtenons les réponses en évaluant le type de retour d'un seul appel à test(0,0) . C'est fait par:

  typedef decltype(test(0,0)) type; 

Cet appel peut être résolu par l' /* SFINAE operator-exists :) */ surcharge de test() , ou peut être résolu par le jeu /* SFINAE game over :( */ overload. Il ne peut pas être résolu avec /* SFINAE operator-has-correct-sig :) */ overload, car on n'attend qu'un seul argument et on en passe deux.

Pourquoi passons-nous deux? Simplement forcer la résolution à exclure /* SFINAE operator-has-correct-sig :) */ . Le second argument n'a aucune autre signification.

Cet appel à test(0,0) se résoudra à /* SFINAE operator-exists :) */ juste au cas où le premier argument 0 saturerait 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 dit oui à cela. Ensuite, il y a /* 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 ne transmettons qu'un seul argument, &A::operator* , dont nous soaps maintenant qu'il existe, ou nous ne serions pas là. Un appel à test(&A::operator*) peut se résoudre à /* SFINAE operator-has-correct-sig :) */ ou encore à résoudre /* SFINAE game over :( */ . L'appel correspondra /* SFINAE operator-has-correct-sig :) */ juste en cas &A::operator* vérifie le type de paramètre unique de cette surcharge, qui est E (A::*)() const , avec A = T

Le compilateur dira Oui si T::operator* a cette signature désirée, puis à nouveau pour évaluer le type de retour de la surcharge. Plus de "récurrences" maintenant: c'est std::true_type .

Si le compilateur ne choisit pas l' /* SFINAE operator-exists :) */ pour le test(0,0) appel test(0,0) ou ne choisit pas /* SFINAE operator-has-correct-sig :) */ pour le test(&A::operator*) appel test(&A::operator*) , alors dans un cas comme dans l'autre, le /* 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 différents échantillons 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 struct ee_ref { E & operator*() { return *_pe; } E & foo() const { return *_pe; } E * _pe; }; // To test struct sub_ee_ref : ee_ref{}; using namespace std; #include  #include  #include  int main(void) { cout << "Expect Yes" << endl; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op::iterator,int &>::value; cout << has_const_reference_op::const_iterator, int const &>::value; cout << has_const_reference_op::value; cout << has_const_reference_op::value << endl; cout << "Expect No" << endl; cout << has_const_reference_op::value; cout << has_const_reference_op,char &>::value; cout << has_const_reference_op,int const &>::value; cout << has_const_reference_op,int>::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op,int &>::value; cout << has_const_reference_op::value; cout << has_const_reference_op::value << endl; return 0; } 

Y a-t-il de nouveaux défauts dans cette idée? Peut-on le rendre plus générique sans tomber une fois de plus dans le piège qu'il évite?

Cela devrait suffire si vous connaissez le nom de la fonction membre que vous attendez. (Dans ce cas, la fonction bla n’instancie pas s’il n’ya pas de fonction membre (écrire une fonction qui fonctionne de toute façon est difficile car il ya un manque de spécialisation partielle de fonction. Vous devrez peut-être utiliser des modèles de classe). est similaire à enable_if) pourrait également être basé sur le type de fonction que vous souhaitez avoir en tant que membre.

 template  struct enable { typedef T type; }; template  typename enable::type bla (T&); struct A { void i(); }; struct B { int i(); }; int main() { A a; B b; bla(b); bla(a); } 

Voici quelques extraits d’utilisation: * Les entrailles pour tout cela sont plus loin

Vérifiez 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::value; 

Vérifiez 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::value; 

Recherchez la variable membre x :

 CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x::value; 

Recherchez la classe de membre x :

 CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x::value; 

Vérifier l’union membre x :

 CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x::value; 

Vérifier le membre enum x :

 CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x::value; 

Vérifiez toute fonction membre x indépendamment de 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::value; 

OU

 CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x::value; 

Détails et kernel:

 /* - 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  struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template  struct ambiguate : public A, public B {}; template struct got_type : std::false_type {}; template struct got_type : std::true_type { typedef A type; }; template struct sig_check : std::true_type {}; template struct has_member { template static char ((&f(decltype(&C::value))))[1]; template static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f(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(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 \ struct Alias_##member; \ \ template \ struct Alias_##member < \ T, std::integral_constant::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member> \ , Alias_##member \ >::value \ ; \ } 

CREATE_MEMBER_VAR_CHECK:

 //Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template \ struct has_member_var_##var_name : std::false_type {}; \ \ template \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer::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 \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check::value \ > \ > : std::true_type {} 

CREATE_MEMBER_CLASS_CHECK:

 //Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template \ struct has_member_class_##class_name : std::false_type {}; \ \ template \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_UNION_CHECK:

 //Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template \ struct has_member_union_##union_name : std::false_type {}; \ \ template \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_ENUM_CHECK:

 //Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_CHECK:

 //Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template \ struct has_member_func_##func { \ static const bool value \ = has_member_##func::value \ && !has_member_var_##func::value \ && !has_member_class_##func::value \ && !has_member_union_##func::value \ && !has_member_enum_##func::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) 

Vous pouvez utiliser std :: is_member_function_pointer

 class A { public: void foo() {}; } bool test = std::is_member_function_pointer::value; 

Je suis venu avec le même genre de problème et j’ai trouvé les solutions proposées ici très intéressantes… mais nécessitaient une solution qui:

  1. Détecte également les fonctions héritées
  2. Est compatible avec les compilateurs non compatibles C ++ 11 (donc pas de decltype)

Nous avons trouvé un autre fil de discussion proposant quelque chose comme ça, basé sur une discussion BOOST . Voici la généralisation de la solution proposée en deux déclarations de macros pour la classe traits, suivant le modèle des classes boost :: has_ ​​* .

 #include  #include  /// 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::value \ > \ class has_func_ ## func_name; \ template \ class has_func_ ## func_name \ {public: \ BOOST_STATIC_CONSTANT( bool, value = false ); \ typedef boost::false_type type; \ }; \ template \ class has_func_ ## func_name \ { 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  class Helper{}; \ template  \ 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(0) ) ) \ ); \ typedef ::boost::integral_constant 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 se développent en une classe de traits avec le prototype suivant:

 template 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 atsortingbute usable in http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html */ typedef boost::integral_constant 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 tirer?

 // 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  // Here is a function that is active for types // declaring the good member function template inline typename boost::enable_if< ns_0::has_func_foo >::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 inline typename boost::disable_if< ns_0::has_func_foo >::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) } 

Voici une version plus simple de la réponse de Mike Kinghan. Cela détectera les méthodes héritées. Il vérifie également la signature exacte (contrairement à l’approche de jrok qui permet les conversions d’arguments).

 template  class HasGreetMethod { template  static std::true_type testSignature(void (T::*)(const char*) const); template  static decltype(testSignature(&T::greet)) test(std::nullptr_t); template  static std::false_type test(...); public: using type = decltype(test(nullptr)); static const bool value = type::value; }; struct A { void greet(const char* name) const; }; struct Derived : A { }; static_assert(HasGreetMethod::value, ""); 

Exemple runnable

Pour ce faire, nous devons utiliser:

  1. Surcharge du modèle de fonction avec différents types de retour selon que la méthode est disponible
  2. En accord avec les méta-conditions de l’en-tête type_traits , nous voudrons renvoyer un true_type ou un false_type de nos surcharges
  3. Déclarez la surcharge true_type attendant une surcharge int et la surcharge false_type attendant que les parameters Variadic soient exploités: “La priorité la plus basse de la conversion des points de suspension en résolution de surcharge”
  4. En définissant la spécification du modèle pour la fonction true_type , nous utiliserons declval et decltype pour détecter la fonction indépendamment des différences de type de retour ou des surcharges entre les méthodes.

Vous pouvez voir un exemple vivant de ceci ici , mais je l’expliquerai ci-dessous:

Je veux vérifier l’existence d’une fonction nommée test qui prend un type convertible de int , alors il faudrait que je déclare ces deux fonctions:

 template ().test(declval))> static true_type hasTest(int); template  static false_type hasTest(...); 
  • decltype(hasTest(0))::value est true (Notez qu’il n’est pas nécessaire de créer des fonctionnalités spéciales pour traiter les surcharges a void a::test() , le void a::test(int) est accepté)
  • decltype(hasTest(0))::value est true (car int est convertible en double int b::test(double) est accepté, indépendamment du type de retour)
  • decltype(hasTest(0))::value est false ( c n’a pas de méthode nommée test qui accepte un type convertible depuis int car cela n’est pas accepté)

Cette solution présente 2 inconvénients:

  1. Nécessite une déclaration par méthode d’une paire de fonctions
  2. Crée une pollution d’espace de noms, en particulier si l’on veut tester des noms similaires, par exemple, comment nommer une fonction qui veut tester une méthode 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 doivent uniquement être utilisées avec une classe, elles doivent être déclarées en privé par cette classe. À cette fin, j’ai écrit une macro pour vous aider à résumer cette information:

 #define FOO(FUNCTION, DEFINE) template ().FUNCTION)> static true_type __ ## DEFINE(int); \ template  static false_type __ ## DEFINE(...); \ template  using DEFINE = decltype(__ ## DEFINE(0)); 

Vous pourriez l’utiliser comme:

 namespace details { FOO(test(declval()), test_int) FOO(test(), test_void) } 

Par la suite, appeler details::test_int::value ou details::test_void::value donnerait true ou false pour les besoins du code en ligne ou de la méta-programmation.

Pour ne pas être intrusif, vous pouvez également mettre en serialize dans l’espace de noms de la classe en cours de sérialisation ou de la classe d’archivage, grâce à la recherche Koenig . Voir Espaces de noms pour les remplacements de fonctions libres pour plus de détails. 🙂

Ouvrir un espace de nom donné pour implémenter une fonction gratuite est tout simplement faux. (Par exemple, vous n’êtes pas censé ouvrir un espace de noms std pour implémenter le swap pour vos propres types, mais vous devriez plutôt utiliser la recherche de Koenig.)

D’accord. Deuxième essai. Ce n’est pas grave si tu n’aimes pas celui-ci non plus, je cherche plus d’idées.

L’article de Herb Sutter parle de traits. Ainsi, vous pouvez avoir une classe de traits dont l’instanciation par défaut a le comportement de secours, et pour chaque classe où votre fonction membre existe, alors la classe traits est spécialisée pour appeler la fonction membre. Je crois que l’article de Herb mentionne une technique pour faire cela afin qu’il ne nécessite pas beaucoup de copie et de collage.

Comme je l’ai dit, vous ne voulez peut-être pas que le travail supplémentaire soit associé aux classes de “tagging” qui implémentent ce membre. Dans ce cas, je cherche une troisième solution ….

Je crois que la réponse que vous recherchez est ici.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Comstack-Time

et un exemple un peu plus rempli ici

http://pastie.org/298994

J’utilise la technique pour détecter la présence d’un opérateur ostream pris en charge << 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 soignée. Passez le temps à comprendre le code et cela en vaut vraiment la peine.

Brad

Sans support C ++ 11 ( decltype ), cela pourrait fonctionner:

SSCCE

 #include  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 struct FooFinder { typedef char true_type[1]; typedef char false_type[2]; template struct TypeSink; template static true_type &match(U); template static true_type &test(TypeSink( &U::foo ) )> *); template static false_type &test(...); enum { value = (sizeof(test(0, 0)) == sizeof(true_type)) }; }; int main() { cout << FooFinder::value << endl; cout << FooFinder::value << endl; cout << FooFinder::value << endl; cout << FooFinder::value << endl; cout << FooFinder::value << endl; cout << FooFinder::value << endl; cout << FooFinder::value << endl; } 

Comment ça marche?

A , Aa et B sont les classes en question, Aa étant le spécial qui hérite du membre que nous recherchons.

Dans le FooFinder les true_type et false_type sont les remplacements des classes C ++ 11 correspondantes. Aussi, pour la compréhension de la méta-programmation des modèles, ils révèlent la base même de la méthode SFINAE.

TypeSink est une structure de modèle utilisée ultérieurement pour intégrer le résultat intégral de l'opérateur sizeof dans une instanciation de modèle pour former un type.

La fonction de match est un autre type de modèle SFINAE qui est laissé sans contrepartie 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 deux fonctions de test ainsi que la déclaration enum forment finalement le motif central SFINAE. Il y en a un générique utilisant des points de suspension qui renvoie le false_type et un homologue avec des arguments plus spécifiques qui ont priorité.

Pour pouvoir instancier la fonction de test avec un argument de modèle de T , la fonction de match doit être instanciée, car son type de retour est requirejs pour instancier l'argument TypeSink . The caveat is that &U::foo , being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.