web-dev-qa-db-fra.com

Comment détecter s'il existe une variable membre spécifique dans la classe?

Pour créer une fonction de modèle d'algorithme, j'ai besoin de savoir si x ou X (et y ou Y) dans la classe qui est un argument de modèle. Cela peut être utile lors de l’utilisation de ma fonction pour la classe CPoint MFC ou la classe GDI + PointF ou quelques autres. Tous utilisent différents x en eux. Ma solution pourrait être réduite au code suivant:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::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 compile pas dans Visual Studio, lors de la compilation dans le GNU C++. Avec Visual Studio, je pourrais utiliser le modèle suivant:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }

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

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

P.S. Veuillez ne pas publier les solutions C++ 11 ici car elles sont évidentes et ne sont pas pertinentes pour la question.

54

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

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

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

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

Il est basé sur une brillante idée de quelqu'un sur Usenet.

Remarque: HasX recherche toutes les données ou fonctions appelées x, de type arbitraire. L'introduction du nom de membre a pour seul objectif de créer une ambiguïté lors de la recherche nom-membre - le type de membre n'a pas d'importance. 

46

Voici une solution plus simple que Johannes Schaub - litb 's one . Il nécessite C++ 11.

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Update : un exemple rapide et une explication sur la façon dont cela fonctionne.

Pour ces types:

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

nous avons HasX<A>::value == true et HasX<B>::value == false. Voyons pourquoi.

Rappelons d’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 simplement, puis procédons étape par étape jusqu’au code ci-dessus.

1) Point de départ:

template <typename T, typename U>
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<bool, double>::value == false et HasX<bool, int>::value == false.

2) Valeur par défaut U:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

Étant donné que U par défaut est int, Has<bool> signifie en réalité HasX<bool, int> et donc HasX<bool>::value == HasX<bool, int>::value == false.

3) Ajout d'une spécialisation:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };

En général, grâce au modèle principal, HasX<T, U> 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<bool, double>::value == false mais HasX<bool, int>::value == true.

Merci à la valeur par défaut pour U, HasX<bool>::value == HasX<bool, int>::value == true.

4) decltype et une manière élégante 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 ainsi, 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 objet 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 et ensuite 0), ignore la première valeur. et résultats dans le second. Par conséquent,

int x = (1.2, 0);

est équivalent à

int x = 0;

La combinaison de ceci avec decltype donne que decltype(1.2, 0) signifie int. Il n'y a rien de vraiment spécial à propos de 1.2 ou 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? Par exemple, que signifie decltype(f, 0)? Il est naturel de s’attendre à ce que cela signifie toujours int mais ce n’est 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 retourne un char. Dans ce cas, decltype(foo, 0) est char.

Comment pouvons-nous éviter l'utilisation d'une surcharge pour l'opérateur de virgule? Eh bien, il n'y a aucun moyen de surcharger l'opérateur de virgule pour un opérande void et nous pouvons transtyper n'importe quoi en void. Par conséquent, decltype((void) f, 0) signifie int. En effet, (void) f convertit f de foo à void qui ne fait en gros que dire que l'expression doit être considérée comme ayant le type void. Ensuite, la virgule de l'opérateur intégré est utilisée et ((void) f, 0) résulte en 0 de 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 de virgule prenant foo et int, alors ce n'est pas nécessaire. Nous pouvons toujours inspecter le code source pour voir s'il existe ou non un tel opérateur. Cependant, si cela apparaît dans un modèle et que f a le type V qui est un paramètre de modèle, il n’est plus clair (ni même impossible de savoir) si une telle surcharge existe ou non pour l’opérateur de virgule. Pour être générique, nous lançons quand même.

En bout de ligne: decltype((void) f, 0) est une manière élégante 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 limiter l'explication au strict minimum.

SFINAE signifie Substitution Failure 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 si ce n'était pas là. Voyons comment cela s'applique à notre cas:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Ici encore, decltype((void) T::x, 0) est une manière é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 non valide. Selon le principe SFINAE, le compilateur ne rejette pas le code, il l'ignore (en partie). Plus précisément, comme nous l'avons vuHasX<bool> signifie en réalité HasX<bool, int>. La spécialisation pour U = int doit être sélectionnée mais, tout en l'instanciant, le compilateur trouve bool::x et ignore la spécialisation de modèle 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<bool, int>::value == false.

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

6) Libellé U:

En regardant à nouveau le code dans (5), nous voyons que le nom U n'est utilisé nulle part sauf dans sa déclaration (typename U). Nous pouvons alors annuler le nom du deuxième argument de modèle et obtenir le code présenté en haut de cet article.

80
Cassio Neri

J'ai été redirigé ici à partir d'une question qui a été fermée en tant que duplicata de celle-ci. Je sais que c'est un vieux fil, mais je voulais simplement suggérer une implémentation alternative (plus simple?) Qui fonctionne avec C++ 11. Supposons que nous voulions vérifier si une classe donnée a une variable membre appelée id:

#include <type_traits>

template<typename T, typename = void>
struct has_id : std::false_type { };

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };

C'est tout. Et voici comment il serait utilisé ( exemple live ):

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}

Les choses peuvent être encore plus simples avec quelques macros:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value

Ce 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;
}
28
Andy Prowl

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

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

Recherchez le membre x dans une classe donnée. Pourrait être var, func, classe, 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érifier la classe de membres x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Chèque du syndicat membre x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Vérifier le nom du membre x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Recherche d'une 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<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.
*/

template <typename... Args> struct ambiguate : public Args... {};

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)
7
Brett Rossier

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

has_member_##name<T>

Cela donne true 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 vous suffira d'ajouter un fichier d'en-tête.

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

et vérifier comme suit

BOOST_STATIC_ASSERT(has_member_x<P1>::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, ainsi que SFINAE, constitue un remplacement parfait pour travailler avec la plupart des concepts.

4

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?

2
James Hopkin

Pourquoi n'utilisez-vous pas la spécialisation comme ceci:

struct P1 {int x; };
struct P2 {int X; };

template<class P> 
bool Check_x(P p) { return true; }

template<> 
bool Check_x<P2>(P2 p) { return false; }
2
Naveen

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

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

Zut, quand j'y pense. Si vous avez seulement deux types, pourquoi avez-vous même besoin de modèles pour cela?

1
ralphtheninja

Les fonctions (x, X, y, Y) proviennent-elles d'une classe de base abstraite ou pourraient-elles être refactorisées pour l'être? Si c'est le cas, vous pouvez utiliser la macro SUPERSUBCLASS () de Modern C++ Design, ainsi que les idées de la réponse à cette question:

Répartition basée sur le type à la compilation

1
user23167

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

#include <iostream>
#include <type_traits>

#define IS_MEMBER(T1, M)    \
struct {        \
    struct verystrangename1 { bool M; };    \
    template<typename T> struct verystrangename2 : verystrangename1, public T { }; \
    \
    enum return_t { not_member, is_object, is_function }; \
    template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() { return not_member;  }  \
    template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_object; }   \
    template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_function; }   \
    constexpr operator return_t() { return what_member<T1>(); } \
}

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 compile 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 classe/struct:

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