167 votes

Vérifier si une classe a une fonction membre d'une signature

Je pose la question pour un modèle astuce pour détecter si une classe a un membre spécifique de la fonction de signature.

Le problème est similaire à celui qui a été cité ici http://www.gotw.ca/gotw/071.htm mais pas le même: dans l'élément de Sutter du livre, il répondit à la question qu'une classe C DOIT FOURNIR une fonction de membre avec une signature, sinon le programme ne compile pas. Dans mon problème j'ai besoin de faire quelque chose si une classe a qui fonctionnent, d'autre le faire "autre chose".

Un problème similaire a été confronté par boost::serialization mais je n'aime pas la solution, ils ont adopté: un modèle de fonction qui appelle par défaut une fonction libre (que vous avez à définir) avec une signature, sauf si vous définissez un membre particulier de la fonction (dans ce cas "sérialiser" qui prend 2 paramètres d'un type donné) avec une signature, sinon une erreur de compilation se produit. C'est-à mettre en œuvre à la fois intrusive et non-intrusive de la sérialisation.

Je n'aime pas cette solution pour deux raisons: 1) pour être non intrusif, vous devez remplacer le "sérialiser" fonction qui est dans boost::serialization espace de noms, de sorte que vous avez DANS VOTRE CODE CLIENT à ouvrir l'espace de noms de pouce et de l'espace de noms de sérialisation!! Et une deuxième, de la raison pratique, c'est parce que la pile pour résoudre ce désordre a été de 10 à 12 invocation de la fonction... et je suis un développeur de jeu.

J'ai besoin de définir un comportement personnalisé pour les classes qui n'a pas que de la fonction membre, et mes entités sont à l'intérieur des espaces de noms différents (et je ne veux pas remplacer une fonction globale définie dans un espace de noms alors que je suis dans un autre)

Pouvez-vous me donner une astuce pour résoudre ce casse-tête?

EDIT: @Chris Bouffon-Jeune Je sais bien Koenig de recherche. En fait, j'ai été surpris de ce qu'ils ont fait dans la documentation (http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html)

pourquoi ouvrir le boost::serialization espace de noms?

mais vous ne répondez pas à ma question: je ne veux pas faire la même chose de boost::serialization. Peut-être que je décide de ne "rien" si la classe n'a pas cette fonction avec celle de la signature. dans boost::serialization si vous n'avez pas que la fonction de membre OU si vous n'avez pas remplacer mondial "sérialiser" fonction... erreur de compilation! Je ne veux pas de cela.

EDIT: @Tom Leys Je suis désolé, ce n'est pas ce que j'attendais comme réponse. Ce que vous me suggérer n'est pas ce que je veux. Si vous lisez le lien de le gotw site (ancien site de Herb Sutter), vous découvrirez que votre solution est à la fois envahissante (et je ne veux pas) et ça ne résout pas le problème avec la signature, et ça ne résout pas le fait que je ne peux accepter les classes qui n'ont pas cette fonction membre... C'est un peu plus compliqué.

160voto

jrok Points 30472

Voici une mise en œuvre possible en s'appuyant sur le C++11 caractéristiques. Il détecte correctement la fonction, même si c'est héréditaire (à la différence de la solution dans la accepté de répondre, comme Mike Kinghan observe dans sa réponse).

La fonction de cet extrait tests for, 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;
};

Utilisation:

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

106voto

yrp Points 2931

Je ne suis pas sûr si je vous comprends bien, mais vous pouvez exploiter SFINAE à la fonction de détection de présence au moment de la compilation. Exemple de mon code (tests si de catégorie a de 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>());
}

41voto

Mike Kinghan Points 4567

La accepté de répondre à cette question de la compile-time états-fonction l'introspection, même si c'est à juste titre populaire, a un hic, ce qui peut être observé dans le programme suivant:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Construit avec GCC 4.6.3, le programme des sorties 110 - nous informant que T = std::shared_ptr<int> ne pas fournir de l' int & T::operator*() const.

Si vous n'êtes pas sage pour cette chasse aux sorcières, alors un coup d'oeil à la définition de std::shared_ptr<T> dans l'en-tête <memory> jettera de la lumière. Dans ce la mise en œuvre, std::shared_ptr<T> est dérivée d'une classe de base à partir de laquelle il hérite operator*() const. Donc l'instanciation d'un modèle SFINAE<U, &U::operator*> qui constitue une "conclusion" de l'exploitant pour U = std::shared_ptr<T> ne se produira pas, car std::shared_ptr<T> n'a pas operator*() dans son propre droit et l'instanciation d'un modèle n'est pas "faire de l'héritage".

Cet os n'a pas d'incidence sur le bien-connu SFINAE approche, à l'aide de "Le sizeof() Truc", pour la détection de simplement s' T a certains membres de la fonction mf (voir par ex. cette réponse et les commentaires). Mais l'établissement qu' T::mf existe est souvent (?) pas assez bon: vous pouvez également besoin d'établir qu'il a souhaité la signature. C'est là que le illustré technique scores. Le pointerized variante de la signature souhaitée s'inscrit dans un paramètre d'un type de modèle qui doit être satisfaite par &T::mf pour les SFINAE sonde pour réussir. Mais ce modèle de l'instanciation technique donne la mauvaise réponse quand T::mf est héréditaire.

Un coffre-fort SFINAE technique pour compile-time introspection d' T::mf doit éviter l' l'utilisation d' &T::mf à l'intérieur d'un argument de modèle pour instancier un type qui SFINAE modèle de fonction de la résolution dépend. Au lieu de cela, SFINAE fonction de modèle la résolution ne peut dépendre seulement sur exactement pertinentes des déclarations de types utilisés comme les types d'argument de la surcharge SFINAE de la sonde de fonction.

Une réponse à la question qui respecte cette contrainte, je vais pour illustrer compile-time de détection d' E T::operator*() const, pour arbitraire T et E. Le même schéma s'appliqueront , mutatis mutandis, pour la sonde pour tout autre membre de la signature de la méthode.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

Dans cette solution, la surcharge SFINAE de la sonde de la fonction test() est "invoquée de manière récursive". (Bien sûr, il n'est pas appelée en fait à tous; il a simplement les types de retour hypothétique invocations résolu par le compilateur.)

Nous avons besoin de sonde pour au moins un et au plus deux points d'information:

  • N' T::operator*() existe? Si non, on en a fait.
  • Étant donné qu' T::operator*() existe, est sa signature E T::operator*() const?

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

    typedef decltype(test<T>(0,0)) type;

Cet appel pourrait être résolu à l' /* SFINAE operator-exists :) */de surcharge d' test(), ou il peut se résoudre à l' /* SFINAE game over :( */de surcharge. Il ne peut pas résoudre à l' /* SFINAE operator-has-correct-sig :) */de surcharge, parce que l'on s'attend à un seul argument et l'on passe de deux.

Pourquoi sommes-nous en passant par deux? Simplement pour forcer la résolution à exclure /* SFINAE operator-has-correct-sig :) */. Le deuxième argument n'a pas d'autres signifance.

Cet appel à l' test(0,0) résoudra /* SFINAE operator-exists :) */seulement dans le cas où le premier argument 0 satisfait le premier paramètre de type de surcharge, qui est - decltype(&A::operator*), A = T. 0 répondre à ce type de juste au cas T::operator* existe.

Supposons que le compilateur dire Oui à cela. Puis il va avec /* SFINAE operator-exists :) */ et il doit déterminer le type de retour de l'appel de la fonction, qui dans ce cas est - decltype(test(&A::operator*))- le type de retour de encore un autre appel à l' test().

Cette fois, nous passons à un seul argument, &A::operator*, que nous avons maintenant savez qu'il existe, ou nous ne serions pas ici. Un appel à l' test(&A::operator*)peut résoudre les problèmes d' /* SFINAE operator-has-correct-sig :) */ ou encore à pourrait résoudre /* SFINAE game over :( */. L'appel de match /* SFINAE operator-has-correct-sig :) */ seulement en cas &A::operator*satisfait le seul paramètre de type de surcharge, qui est - E (A::*)() const, avec A = T.

Le compilateur va dire Oui ici si T::operator* a souhaité que la signature,de la et puis de nouveau à évaluer le type de retour de la surcharge. Pas plus "récurrences" maintenant: c'est - std::true_type.

Si le compilateur ne pas choisir /* SFINAE operator-exists :) */ pour les appelez test(0,0) ou de ne pas choisir /* SFINAE operator-has-correct-sig :) */ pour l'appel test(&A::operator*), puis dans les deux cas, il va avec /* SFINAE game over :( */ et le dernier type de retour est - std::false_type.

Voici un programme de test qui montre que le modèle de production attendus des réponses dans l'échantillon varié de cas (GCC 4.6.3).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Sont t-il de nouvelles failles dans cette idée? Cela peut-il être plus générique sans, encore une fois tomber sous le coup de l'accrocher elle évite?

13voto

coppro Points 10692

Cela devrait être suffisant, si vous connaissez le nom de la fonction de membre vous attendent. (Dans ce cas, la fonction bla ne parvient pas à instancier si il n'y a pas de fonction de membre (de l'écriture de celui qui fonctionne est de toute façon difficile parce qu'il y a un manque de fonction partielle de la spécialisation. Vous devrez peut-être utiliser des modèles de classe), De l'activer struct (qui est similaire à enable_if) pourrait également être basé sur un modèle du type de fonction que vous souhaitez avoir comme 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);
}

5voto

Yochai Timmer Points 19802

Vous pouvez utiliser std::is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X