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

135

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'item 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 modèle qui invoque par défaut une fonction libre (que vous devez définir) avec une signature particulière à moins que vous ne définissiez une fonction membre 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. Cela consiste à implémenter une sérialisation à la fois intrusive et non intrusive.

Je n'aime pas cette solution pour deux raisons:

  1. Pour être non intrusif, vous devez remplacer la fonction globale "sérialiser" qui se trouve dans l'espace de noms boost :: serialization, vous avez donc 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 appels 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 à l'intérieur d'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?

ugasoft
la source
1
Question similaire: stackoverflow.com/questions/257288
Johannes Schaub - litb
@ R.MartinhoFernandes Quel genre de réponse recherchez-vous? Cette réponse de Mike Kinghan est assez approfondie et utilise des éléments C ++ 11.
jrok
@ R.MartinhoFernandes C'est peut - être la version moderne que vous recherchez?
Daniel Frey

Réponses:

90

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 une 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>());
}
annee
la source
14
wtf est-ce ??? est-ce du code C ++ légal ?? pouvez-vous écrire "template <typename U, size_t (U :: *) () const>" ?? mais ... c'est une excellente et nouvelle solution! Je vous remercie, je vais mieux analyser demain avec mes collègues ... super!
ugasoft
2
L'exemple n'a pas la définition de «int_to_type». Évidemment, cela n'ajoute pas à la réponse, mais cela signifie que les gens peuvent voir votre code en action après un rapide copier-coller.
Richard Corden
2
Une définition simple de int_to_type pourrait être: 'template <int N> struct int_to_type {};'. De nombreuses implémentations conservent la valeur du paramètre N soit dans une énumération, soit dans une constante entière statique (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas
2
Prenez simplement boost :: integrale_constant au lieu de int_to_type.
Vadim Ferderer
2
@JohanLundberg C'est une fonction membre pointeur vers (non statique). Par exemple size_t(std::vector::*p)() = &std::vector::size;,.
Réintégrer Monica le
133

Voici une implémentation possible reposant sur les fonctionnalités de 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 testée par cet extrait de code s'appelle 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
jrok
la source
Cela fonctionne-t-il si Y n'a pas de méthode appelée «sérialiser»? Je ne vois pas comment il renverrait une valeur fausse si la méthode «sérialiser» n'existait pas.
Collin
1
@Collin dans ce cas, la substitution du paramètre de modèle échoue pour la première surcharge de la vérification et elle est rejetée de l'ensemble de surcharge. Il revient au second qui renvoie false_type. Ce n'est pas une erreur du compilateur car le principe SFINAE.
jrok
1
@ elios264 Il n'y en a pas. Vous pouvez utiliser une macro pour écrire un modèle pour chaque fonction que vous souhaitez vérifier.
jrok
1
Une raison particulière pour laquelle l'argument de vérification est de type T * plutôt que T ou T &?
shibumi
1
Mais que faire si le serializelui - même accepte un modèle. Existe-t-il un moyen de tester l' serializeexistence sans taper le type exact?
Hi-Angel
37

La réponse acceptée à cette question de l'introspection des fonctions membres du temps de compilation, bien qu'elle soit à juste titre populaire, présente 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, les sorties du programme 110- nous informant que T = std::shared_ptr<int>ne fournit pasint & T::operator*() const .

Si vous n'êtes pas déjà avisé de ce piège, alors un coup d'oeil à la définition de std::shared_ptr<T>dans l'en-tête vous <memory>éclairera. Dans cette implémentation, std::shared_ptr<T>est dérivé d'une classe de base dont il hérite operator*() const. Ainsi, l'instanciation de 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 elle std::shared_ptr<T>n'a pas operator*()en soi et l'instanciation de modèle ne "fait pas d'héritage".

Cet hic n'affecte pas l'approche bien connue de SFINAE, utilisant "The sizeof () Trick", pour détecter simplement si Ta une fonction membre mf(voir par exemple cette réponse et commentaires). Mais établir ce qui T::mfexiste n'est souvent (généralement?) Pas assez bon: vous devrez peut-être également établir qu'il a la signature souhaitée. C'est là que la technique illustrée se dé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::mfpour que la sonde SFINAE réussisse. Mais cette technique d'instanciation de modèle donne la mauvaise réponse lorsqu'elle T::mfest héritée.

Une technique SFINAE sûre pour l'introspection du temps de compilation de T::mfdoit éviter d'utiliser &T::mfdans un argument de modèle pour instancier un type dont dépend la résolution de modèle de fonction SFINAE. Au lieu de cela, la résolution de la fonction de modèle SFINAE ne peut dépendre que des déclarations de type exactement pertinentes utilisées comme types d'argument de la fonction de sonde SFINAE surchargée.

En guise de réponse à la question qui respecte cette contrainte, je vais illustrer pour la détection à la compilation de E T::operator*() const, pour arbitraire Tet E. Le même modèle s'appliquera mutatis mutandis à la recherche de 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 de manière récursive". (Bien sûr, il n'est pas du tout invoqué; il a simplement les types de retour des invocations hypothétiques résolues par le compilateur.)

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

  • T::operator*()Existe- t- il du tout? Sinon, nous avons terminé.
  • Étant donné que cela 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 à test(0,0). Cela se fait par:

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

Cet appel peut être résolu en /* SFINAE operator-exists :) */surcharge de test(), ou il peut résoudre en /* SFINAE game over :( */surcharge. Il ne peut pas résoudre la /* SFINAE operator-has-correct-sig :) */surcharge, car celui-ci n'attend qu'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 au /* SFINAE operator-exists :) */cas où le premier argument 0 satisferait le premier type de paramètre de cette surcharge, qui est decltype(&A::operator*), avec A = T. 0 satisfera ce type juste au cas où il T::operator*existe.

Supposons que le compilateur dise Oui à cela. Ensuite, il 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 ne passons qu'un seul argument, &A::operator*dont nous savons maintenant qu'il existe, sinon nous ne serions pas là. Un appel à test(&A::operator*)peut résoudre soit à, soit à /* SFINAE operator-has-correct-sig :) */nouveau à pourrait résoudre à /* 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 s'il T::operator*a la signature souhaitée, puis à nouveau doit évaluer le type de retour de la surcharge. Plus de "récursions" maintenant: ça l'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*), dans les deux cas, il va 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 sous le coup de l'accident qu'il évite?

Mike Kinghan
la source
16

Voici quelques extraits d'utilisation: * Les tripes pour tout cela sont plus bas

Recherchez un membre xdans 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érifiez la fonction du 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érifiez 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 de 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érifiez 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;

Vérifiez toute fonction membre xindé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;

OU

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)
Brett Rossier
la source
1
C'est bien; ce serait bien de le mettre dans une seule bibliothèque de fichiers d'en-tête.
Allan
12

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 fonction. Vous devrez peut-être utiliser des modèles de classe) En outre, la structure enable (qui est similaire à enable_if) pourrait également être basé sur le type de fonction que vous voulez qu'elle 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);
}
coppro
la source
4
thaks! c'est similaire à la solution proposée par yrp. Je ne savais pas que le modèle pouvait être modelé sur les fonctions membres. C'est une nouvelle fonctionnalité que j'ai apprise aujourd'hui! ... et une nouvelle leçon: "ne dites jamais que vous êtes expert en C ++" :)
ugasoft
7

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, "");

Exemple exécutable

Valentin Milea
la source
C'est bien, mais cela ne fonctionnera pas si la fonction ne prend aucun argument
Triskeldeian
Cela fonctionne très bien. Je n'ai eu aucun problème à appliquer cette astuce aux fonctions membres ne prenant aucun argument.
JohnB du
Cela fonctionne bien pour moi avec plusieurs arguments de méthode et sans arguments, y compris avec les surcharges, et y compris avec l'héritage, et avec l'utilisation de usingpour apporter des surcharges à partir de la classe de base. Cela fonctionne pour moi sur MSVC 2015 et avec Clang-CL. Cela ne fonctionne pas avec MSVC 2012 cependant.
steveire
5

Vous pouvez utiliser std :: is_member_function_pointer

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

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Yochai Timmer
la source
16
Ne &A::foosera- t-il pas une erreur de compilation s'il n'y a pas foodu tout A? J'ai lu la question d'origine comme étant censée fonctionner avec n'importe quelle classe d'entrée, pas seulement celles qui ont une sorte de membre nommé foo.
Jeff Walden
5

Je suis venu avec le même genre de problème moi-même, et j'ai trouvé les solutions proposées ici très intéressantes ... mais avait l'exigence 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)

J'ai trouvé un autre fil proposant quelque chose comme ça, basé sur une discussion BOOST . Voici la généralisation de la solution proposée sous forme de déclaration de deux 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 se développent en 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 faire de cela?

// 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)
}
S. Paris
la source
5

Pour ce faire, nous devrons utiliser:

  1. Surcharge du modèle de fonction avec des types de retour différents selon que la méthode est disponible ou non
  2. Conformément aux méta-conditions de l'en- type_traitstête, nous voudrons retourner un true_typeoufalse_type de nos surcharges
  3. Déclarez la true_typesurcharge attendue intet la false_typesurcharge attendue des paramètres Variadic à exploiter: «La priorité la plus basse de la conversion des points de suspension dans la résolution de surcharge»
  4. En définissant la spécification du modèle pour la true_typefonction, nous utiliserons declvalet decltypenous permettant de détecter la fonction indépendamment des différences de type de retour ou des surcharges entre les méthodes

Vous pouvez en voir un exemple en direct ici .Mais je vais également l'expliquer ci-dessous:

Je veux vérifier l'existence d'une fonction nommée testqui prend un type convertible int, alors j'aurais besoin de 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))::valueis true(Notez qu'il n'est pas nécessaire de créer des fonctionnalités spéciales pour gérer la void a::test()surcharge, le void a::test(int)est accepté)
  • decltype(hasTest<b>(0))::valueis true(Car intest convertible en double int b::test(double)est accepté, indépendamment du type de retour)
  • decltype(hasTest<c>(0))::valueest false( cn'a pas de méthode nommée testqui accepte un type convertible à partir de là, intce 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 de l'espace de noms en particulier si nous voulons tester des noms similaires, par exemple comment nommerions-nous une fonction qui voulait tester une test()méthode?

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 doivent ê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)
}

Appelant par la suite details::test_int<a>::valueou details::test_void<a>::valueproduirait trueou falseaux fins de code en ligne ou de méta-programmation.

Jonathan Mee
la source
3

Pour ne pas être intrusif, vous pouvez également mettre serializedans l'espace de noms de la classe en cours de sérialisation, ou de la classe archive, grâce à la recherche Koenig . Voir Espaces de noms pour les remplacements de fonctions gratuits 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 un espace stdde noms pour implémenterswap pour vos propres types, mais devriez plutôt utiliser la recherche Koenig.)

Chris Jester-Young
la source
3

Vous semblez vouloir l'idiome du détecteur. Les réponses ci-dessus sont des variantes qui fonctionnent avec C ++ 11 ou C ++ 14.

La std::experimentalbibliothèque a des fonctionnalités qui font essentiellement cela. Retravailler un exemple d'en haut, cela pourrait être:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Si vous ne pouvez pas utiliser std :: experimental, une version rudimentaire peut être créée comme ceci:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Puisque has_serialize_t est en réalité soit std :: true_type soit std :: false_type, il peut être utilisé via n'importe lequel des idiomes SFINAE courants:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Ou en utilisant la répartition avec résolution de surcharge:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}
lar
la source
2

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

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

Comme je l'ai dit, cependant, peut-être que vous ne voulez pas du travail supplémentaire impliqué avec les classes de "marquage" qui implémentent ce membre. Dans ce cas, je cherche une troisième solution ...

Chris Jester-Young
la source
hein ... J'ai analysé cette solution ... Je pense que c'est un peu trop cher pour les utilisateurs de mon framework. (ok, j'avoue, je développe un framework de streaming et je
choisis
Ma troisième solution serait d'utiliser SFINAE. Puisque la réponse d'yrp le mentionne déjà, je ne vais pas y entrer (car je suis toujours en train de faire des recherches là-dessus: je connais l'idée, mais le diable est dans les détails), à moins que sa solution ne fonctionne pas pour vous à la fin . :-)
Chris Jester-Young
1

Sans le support 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 ça marche j'espère

A, Aaet Bsont les classes en question,Aa étant la classe spéciale qui hérite du membre que nous recherchons.

Dans le FooFinderla true_typeet false_typesont les remplacements pour le C ++ correspondant 11 classes. Aussi pour la compréhension de la métaprogrammation modèle, ils révèlent la base même de la SFINAE-sizeof-trick.

Le TypeSinkest une structure de modèle qui est utilisée ultérieurement pour intégrer le résultat intégral de l' sizeofopérateur dans une instanciation de modèle pour former un type.

La matchfonction est un autre type de modèle SFINAE qui n'a pas d'é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 deux testfonctions ainsi que la déclaration enum forment finalement le modèle SFINAE central. Il y en a un générique utilisant une ellipse qui renvoie lefalse_type et un homologue avec des arguments plus spécifiques pour avoir la priorité.

Pour pouvoir instancier la testfonction avec un argument de modèle de T, la matchfonction doit être instanciée, car son type de retour est requis pour instancier l' TypeSinkargument. La mise en garde est que &U::foo, étant enveloppé dans un argument de fonction, n'est pas référencé à partir d'une spécialisation d'argument de modèle, donc la recherche de membre héritée a toujours lieu.

Kamajii
la source
1

Si vous utilisez la folie de 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 que ceux de la réponse précédente, utiliser une bibliothèque est plus simple.

pingouin préhistorique
la source
0

J'avais un besoin similaire et suis tombé sur ce SO. Il existe de nombreuses solutions intéressantes / puissantes proposées ici, même si elles sont un peu longues pour juste un besoin spécifique: détecter si une classe a une fonction membre avec une signature précise. J'ai donc fait quelques lectures / tests et suis venu avec ma version qui pourrait être intéressante. Il détecte:

  • fonction membre statique
  • fonction membre non statique
  • fonction membre non statique const

avec une signature précise. Comme je n'ai pas besoin de capturer de signature (qui nécessiterait une solution plus compliquée), celle-ci me convient . Il a essentiellement utilisé enable_if_t .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Production :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1
ctNGUYEN
la source
0

En me basant sur la réponse de jrok , j'ai évité d'utiliser des classes et / ou des fonctions de modèle imbriquées.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Nous pouvons utiliser les macros ci-dessus comme ci-dessous:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Les suggestions sont les bienvenues.

debashish.ghosh
la source