web-dev-qa-db-fra.com

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 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:

  1. Pour ne pas être intrusif, vous devez remplacer la fonction globale "sérialiser" qui se trouve dans l'espace de noms boost :: serialization, donc vous avez DANS VOTRE CODE CLIENT pour ouvrir le boost d'espace de noms et la sérialisation de l'espace de noms!
  2. La pile pour résoudre ce désordre était de 10 à 12 invocations de fonctions.

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?

121
ugasoft

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>());
}
87
yrp

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
119
jrok

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é.
  • Étant donné que 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?

35
Mike Kinghan

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

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);
}
11
coppro

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. Conformément aux méta-conditions de l'en-tête type_traits , nous souhaitons renvoyer un true_type Ou false_type = de nos surcharges
  3. Déclarez la surcharge true_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" =
  4. Pour définir la spécification du modèle pour la fonction 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éthodes

Vous 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 doubleint 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:

  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 nous voulons tester des noms similaires, par exemple, comment nommer une fonction qui voulait 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 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.

5
Jonathan Mee

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

5
Valentin Milea

Vous pouvez utiliser std :: is_member_function_pointer

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

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

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:

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

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)
}
4
S. Paris

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.)

3
Chris Jester-Young

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 ...

2
Chris Jester-Young

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

SSCCE

#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;
}

Comment cela fonctionne, espérons-le

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.

1
Kamajii

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

http://pastie.org/298994

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

0
Brad Phelan

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.

0
prehistoricpenguin