Comment détecter s’il existe une variable membre spécifique en classe?

Pour créer une fonction de modèle d’algorithme, je dois savoir si x ou X (et y ou Y) dans la classe est un argument de modèle. Cela peut être utile lorsque j’utilise ma fonction pour la classe MFC CPoint ou la classe GDI + PointF ou d’autres. Ils utilisent tous des x différents. Ma solution pourrait être réduite au code suivant:

template struct TT {typedef int type;}; template bool Check_x(P p, typename TT::type b = 0) { return true; } template bool Check_x(P p, typename TT::type b = 0) { return false; } struct P1 {int x; }; struct P2 {float X; }; // it also could be struct P3 {unknown_type X; }; int main() { P1 p1 = {1}; P2 p2 = {1}; Check_x(p1); // must return true Check_x(p2); // must return false return 0; } 

Mais il ne comstack pas dans Visual Studio, lors de la compilation dans GNU C ++. Avec Visual Studio, je pourrais utiliser le modèle suivant:

 template bool Check_x(P p, typename TT::type b = 0) { return true; } template bool Check_x(P p, typename TT::type b = 0) { return false; } 

Mais il ne comstack pas en GNU C ++. Y a-t-il une solution universelle?

UPD: Les structures P1 et P2 ne sont ici que par exemple. Il pourrait y avoir des classes avec des membres inconnus.

PS S’il vous plaît, ne publiez pas de solutions C ++ 11 ici car elles sont évidentes et ne sont pas pertinentes pour la question.

Une autre façon est celle-ci, qui repose également sur SFINAE pour les expressions . Si la recherche de nom entraîne une ambiguïté, le compilateur rejettera le modèle

 template struct HasX { struct Fallback { int x; }; // introduce member name "x" struct Derived : T, Fallback { }; template struct ChT; template static char (&f(ChT*))[1]; template static char (&f(...))[2]; static bool const value = sizeof(f(0)) == 2; }; struct A { int x; }; struct B { int X; }; int main() { std::cout < < HasX::value < < std::endl; // 1 std::cout << HasX::value < < std::endl; // 0 } 

C'est basé sur une idée géniale de quelqu'un sur Usenet.

Remarque: HasX recherche les données ou les membres de fonction appelés x, avec un type arbitraire. Le seul but du nom de membre est d’avoir une ambiguïté possible pour la recherche de nom de membre - le type du membre n’est pas important.

Voici une solution plus simple que celle de Johannes Schaub – litb . Il nécessite C ++ 11.

 #include  template  struct HasX : std::false_type { }; template  struct HasX  : std::true_type { }; 

Mise à jour : Un exemple rapide et l’explication de la façon dont cela fonctionne.

Pour ces types:

 struct A { int x; }; struct B { int y; }; 

nous avons HasX::value == true et HasX::value == false . Voyons pourquoi.

std::false_type abord que std::false_type et std::true_type ont un membre static constexpr bool nommé value qui est défini sur false et true , respectivement. Par conséquent, les deux modèles HasX ci-dessus héritent de ce membre. (Le premier modèle de std::false_type et le second de std::true_type .)

Commençons simple et procédons pas à pas jusqu’à ce que nous arrivions au code ci-dessus.

1) Point de départ:

 template  struct HasX : std::false_type { }; 

Dans ce cas, il n’y a pas de surprise: HasX dérive de std::false_type et donc HasX::value == false et HasX::value == false .

2) U défaut:

 // Primary template template  struct HasX : std::false_type { }; 

Etant donné que U est HasX défaut sur int , Has signifie-t-il HasX et donc, HasX::value == HasX::value == false .

3) Ajouter une spécialisation:

 // Primary template template  struct HasX : std::false_type { }; // Specialization for U = int template  struct HasX : std::true_type { }; 

En général, grâce au template primaire, HasX dérive de std::false_type . Cependant, il existe une spécialisation pour U = int qui dérive de std::true_type . Par conséquent, HasX::value == false mais HasX::value == true .

Grâce à la valeur par défaut pour U , HasX::value == HasX::value == true .

4) decltype et une façon decltype de dire int :

Une petite digression ici, mais, s’il vous plaît, supportez-moi.

Fondamentalement (ce n’est pas tout à fait correct), decltype(expression) donne le type d’ expression . Par exemple, 0 a le type int donc, decltype(0) signifie int . De manière analogue, 1.2 a le type double et ainsi, decltype(1.2) signifie double .

Considérons une fonction avec cette déclaration:

 char func(foo, int); 

foo est un type de classe. Si f est un object de type foo , alors decltype(func(f, 0)) signifie char (le type renvoyé par func(f, 0) ).

Maintenant, l’expression (1.2, 0) utilise l’opérateur de virgule (intégré) qui évalue les deux sous-expressions dans l’ordre (c’est-à-dire d’abord 1.2 puis 0 ), supprime la première valeur et en génère la seconde. Par conséquent,

 int x = (1.2, 0); 

est équivalent à

 int x = 0; 

Le mettre avec decltype que decltype(1.2, 0) signifie int . Il n’y a rien de vraiment spécial à propos de 1.2 ou de double ici. Par exemple, true a le type bool et decltype(true, 0) signifie également int .

Qu’en est-il d’un type de classe? Pour instace, que signifie decltype(f, 0) ? Il est naturel de s’attendre à ce que cela signifie toujours int mais ce ne sera peut-être pas le cas. En effet, il pourrait y avoir une surcharge pour l’opérateur de virgule similaire à la fonction func ci-dessus qui prend un foo et un int et renvoie un caractère. Dans ce cas, decltype(foo, 0) est char .

Comment éviter l’utilisation d’une surcharge pour l’opérateur virgule? Eh bien, il n’y a aucun moyen de surcharger l’opérateur de virgule pour un opérande void et nous pouvons tout jeter à rien. Par conséquent, decltype((void) f, 0) signifie int . En effet, (void) f f de foo to void ce qui ne fait rien d’autre que de dire que l’expression doit être considérée comme ayant un type void . Ensuite, la virgule de l’opérateur intégré est utilisée et ((void) f, 0) donne comme résultat 0 qui a le type int . Par conséquent, decltype((void) f, 0) signifie int .

Ce casting est-il vraiment nécessaire? Eh bien, s’il n’y a pas de surcharge pour l’opérateur virgule prenant foo et int ce n’est pas nécessaire. Nous pouvons toujours inspecter le code source pour voir si un tel opérateur existe ou non. Cependant, si cela apparaît dans un modèle et que f a le type V qui est un paramètre de modèle, alors il n’est plus clair (ou même impossible de savoir) si cette surcharge existe pour l’opérateur de virgule. Pour être générique, on lance quand même.

Bottom line: decltype((void) f, 0) est une façon decltype((void) f, 0) de dire int .

5) SFINAE:

C’est une science entière 😉 OK, j’exagère mais ce n’est pas très simple non plus. Je vais donc garder l’explication au ssortingct minimum.

SFINAE signifie Échec de la substitution n’est pas une erreur. Cela signifie que lorsqu’un paramètre de modèle est remplacé par un type, un code C ++ illégal peut apparaître mais, dans certaines circonstances , au lieu d’abandonner la compilation, le compilateur ignore simplement le code incriminé comme s’il n’y était pas. Voyons comment cela s’applique à notre cas:

 // Primary template template  struct HasX : std::false_type { }; // Specialization for U = int template  struct HasX  : std::true_type { }; 

Ici encore, decltype((void) T::x, 0) est une façon élégante de dire int mais avec l’avantage de SFINAE.

Lorsque T est remplacé par un type, une construction non valide peut apparaître. Par exemple, bool::x n’est pas valide en C ++, par conséquent, remplacer T par bool dans T::x donne une construction invalide. Sous le principe SFINAE, le compilateur ne rejette pas le code, il l’ignore simplement (en partie). Plus précisément, comme nous l’avons vu, HasX signifie en fait HasX . La spécialisation pour U = int doit être sélectionnée mais, lors de l’instanciation, le compilateur trouve bool::x et ignore la spécialisation du template comme si elle n’existait pas.

À ce stade, le code est essentiellement le même que dans le cas (2) ci-dessus, où seul le modèle principal existe. Par conséquent, HasX::value == false .

Le même argument utilisé pour bool est valable pour B puisque B::x est une construction invalide ( B n’a pas de membre x ). Cependant, A::x est OK et le compilateur ne voit aucun problème à instancier la spécialisation pour U = int (ou, plus précisément, pour U = decltype((void) A::x, 0) ). Par conséquent, HasX::value == true .

6) Déblocage U :

Eh bien, en regardant à nouveau le code dans (5), nous voyons que le nom U n’est utilisé nulle part ailleurs que dans sa déclaration ( typename U ). Nous pouvons alors nommer le second argument du modèle et nous obtenons le code indiqué en haut de ce post.

J’ai été redirigé ici à partir d’une question qui a été fermée en deux. Je sais que c’est un ancien thread, mais je voulais juste suggérer une implémentation alternative (plus simple?) Qui fonctionne avec C ++ 11. En supposant que l’on veuille vérifier si une certaine classe a une variable membre appelée id :

 #include  template struct has_id : std::false_type { }; template struct has_id().id, void())> : std::true_type { }; 

C’est tout. Et voici comment il serait utilisé ( exemple en direct ):

 #include  using namespace std; struct X { int id; }; struct Y { int foo; }; int main() { cout < < boolalpha; cout << has_id::value < < endl; cout << has_id::value < < endl; } 

Les choses peuvent être simplifiées avec quelques macros:

 #define DEFINE_MEMBER_CHECKER(member) \ template \ struct has_ ## member : false_type { }; \ template \ struct has_ ## member().member), void>::value, \ bool \ >::type \ > : true_type { }; #define HAS_MEMBER(C, member) \ has_ ## member::value 

Qui pourrait être utilisé de cette façon:

 using namespace std; struct X { int id; }; struct Y { int foo; }; DEFINE_MEMBER_CHECKER(foo) int main() { cout < < boolalpha; cout << HAS_MEMBER(X, foo) << endl; cout << HAS_MEMBER(Y, foo) << endl; } 

MISE À JOUR: J’ai récemment fait un peu plus avec le code que j’ai posté dans ma réponse originale, alors je le mets à jour pour tenir compte des modifications / ajouts.

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. */ template  struct ambiguate : public Args... {}; 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) 

Boost.ConceptTraits fournit entre autres des macros pour définir des traits de caractères, comme par exemple BOOST_TT_EXT_DEFINE_HAS_MEMBER(name) , qui définit un trait de type de la forme:

 has_member_##name 

Cela donne vrai si T a un type de membre nommé. Notez cependant que cela ne détectera pas les membres de type référence.

Dans votre cas, il suffira d’append un fichier d’en-tête

 BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x) 

et vérifiez comme suit

 BOOST_STATIC_ASSERT(has_member_x::value); 

La technique utilisée est la même que celle expliquée dans certaines des réponses précédentes.

Malheureusement, cette bibliothèque n’est plus maintenue. Maintenant que C ++ 0x n’inclut pas le concept, cette bibliothèque, associée à SFINAE, est un remplacement parfait pour travailler avec la plupart des concepts.

Pourquoi n’utilisez-vous pas une spécialisation comme celle-ci:

 struct P1 {int x; }; struct P2 {int X; }; template bool Check_x(P p) { return true; } template<> bool Check_x(P2 p) { return false; } 

La deuxième réponse (litb) à ceci montre comment détecter un membre:

Est-il possible d’écrire un modèle pour vérifier l’existence d’une fonction?

Pourquoi ne créez-vous pas simplement des spécialisations de modèle de Check_x?

 template<> bool Check_x(P1 p) { return true; } template<> bool Check_x(P2 p) { return false; } 

Heck, quand j’y pense. Si vous ne disposez que de deux types, pourquoi avez-vous même besoin de modèles pour cela?

Les fonctions (x, X, y, Y) sont-elles issues d’une classe de base abstraite ou peuvent-elles être refactorisées pour l’être? Si oui, vous pouvez utiliser la macro SUPERSUBCLASS () de Modern C ++ Design, ainsi que des idées provenant de la réponse à cette question:

Expédition basée sur le type de compilation

Nous pouvons obtenir au moment de la compilation: 0 - not_member, 1 - is_object, 2 - is_function pour chaque classe et membre requirejs – object ou fonction: http://ideone.com/Fjm9u5

 #include  #include  #define IS_MEMBER(T1, M) \ struct { \ struct verystrangename1 { bool M; }; \ template struct verystrangename2 : verystrangename1, public T { }; \ \ enum return_t { not_member, is_object, is_function }; \ template::M)> constexpr return_t what_member() { return not_member; } \ template typename std::enable_if::value, return_t>::type constexpr what_member() { return is_object; } \ template typename std::enable_if::value, return_t>::type constexpr what_member() { return is_function; } \ constexpr operator return_t() { return what_member(); } \ } struct t { int aaa; float bbb; void func() {} }; // Can't be in function IS_MEMBER(t, aaa) is_aaa_member_of_t; IS_MEMBER(t, ccc) is_ccc_member_of_t; IS_MEMBER(t, func) is_func_member_of_t; // known at comstack time enum { const_is_aaa_member_of_t = (int)is_aaa_member_of_t }; static constexpr int const_is_func_member_of_t = is_func_member_of_t; int main() { std::cout < < std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" << "is aaa member of t = " << is_aaa_member_of_t << std::endl << "is ccc member of t = " << is_ccc_member_of_t << std::endl << "is func member of t = " << is_func_member_of_t << std::endl << std::endl; return 0; } 

Résultat:

 0 - not_member, 1 - is_object, 2 - is_function is aaa member of t = 1 is ccc member of t = 0 is func member of t = 2 

Pour class / struct:

 struct t { int aaa; float bbb; void func() {} };