Vérification modèle de l'existence d'une fonction membre de classe?


499

Est-il possible d'écrire un modèle qui change de comportement selon qu'une certaine fonction membre est définie sur une classe?

Voici un exemple simple de ce que je voudrais écrire:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Donc, si class Ta toString()défini, alors il l'utilise; sinon, ce n'est pas le cas. La partie magique que je ne sais pas faire est la partie "FUNCTION_EXISTS".


6
Bien sûr, il va sans dire que la ou les réponses de modèle ci-dessous ne fonctionnent qu'avec des informations au moment de la compilation, c'est-à-dire que T doit avoir toString. Si vous passez dans une sous - classe de T qui ne définit toString, mais T ne pas , on vous dira toString n'est pas défini.
Alice Purcell

Réponses:


319

Oui, avec SFINAE, vous pouvez vérifier si une classe donnée fournit une certaine méthode. Voici le code de travail:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Je viens de le tester avec Linux et gcc 4.1 / 4.3. Je ne sais pas s'il est portable sur d'autres plates-formes exécutant différents compilateurs.


18
Cependant, j'ai utilisé ce qui suit pour «un» et «deux»: typedef char Small; class Big {char dummy [2];} pour éviter toute ambiguïté sur la taille variable dépendante de la plateforme.
user23167

6
Je doute qu'il existe sur terre une plateforme avec le sizeof (char) == sizeof (long)
Nicola Bonelli

17
Je ne suis pas tout à fait sûr, mais je ne pense pas que ce soit portable. typeof est une extension GCC, cela ne fonctionnera pas sur d'autres compilateurs.
Leon Timmermans

56
typeof n'est pas nécessaire - char [sizeof (& C :: helloworld)] fonctionne également. Et pour éviter sizeof (long) == sizeof (char), utilisez une struct {char [2]} ;. Il doit avoir une taille> = 2
MSalters

57
Trivial, mais il m'a fallu un certain temps pour comprendre: remplacer typeofpar decltypelors de l'utilisation de C ++ 0x , par exemple via -std = c ++ 0x.
hrr

265

Cette question est ancienne, mais avec C ++ 11, nous avons obtenu une nouvelle façon de vérifier l'existence de fonctions (ou l'existence de tout membre non-type, vraiment), en s'appuyant à nouveau sur SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Passons maintenant à quelques explications. Tout d'abord, j'utilise l' expression SFINAE pour exclure les serialize(_imp)fonctions de la résolution de surcharge, si la première expression à l'intérieur decltypen'est pas valide (aka, la fonction n'existe pas).

Le void()est utilisé pour créer le type de retour de toutes ces fonctions void.

L' 0argument est utilisé pour préférer la os << objsurcharge si les deux sont disponibles (le littéral 0est de type intet en tant que tel la première surcharge est une meilleure correspondance).


Maintenant, vous voulez probablement un trait pour vérifier si une fonction existe. Heureusement, il est facile d'écrire cela. Notez cependant que vous devez écrire vous-même un trait pour chaque nom de fonction différent que vous souhaitez.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Exemple en direct.

Et passons aux explications. Tout d'abord, sfinae_truec'est un type d'aide, et cela revient au même que l'écriture decltype(void(std::declval<T>().stream(a0)), std::true_type{}). L'avantage est simplement qu'il est plus court.
Ensuite, l' struct has_stream : decltype(...)hérite de l'un std::true_typeou std::false_typede l' autre à la fin, selon que l' decltypearchivage test_streaméchoue ou non.
Enfin, std::declvalvous donne une "valeur" de tout type que vous passez, sans que vous ayez besoin de savoir comment vous pouvez le construire. Notez que cela n'est possible que dans un contexte non évalué, tel que decltype, sizeofet d'autres.


Notez que ce decltypen'est pas nécessairement nécessaire, car sizeof(et tous les contextes non évalués) ont obtenu cette amélioration. C'est juste que decltypefournit déjà un type et en tant que tel est juste plus propre. Voici une sizeofversion de l'une des surcharges:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

le intlong paramètres et sont toujours là pour la même raison. Le pointeur de tableau est utilisé pour fournir un contexte où sizeofpeut être utilisé.


4
L'avantage de decltypeover sizeofest également qu'un temporaire n'est pas introduit par des règles spécialement conçues pour les appels de fonction (vous n'avez donc pas besoin d'avoir des droits d'accès au destructeur du type de retour et ne provoquera pas une instanciation implicite si le type de retour est une instanciation de modèle de classe).
Johannes Schaub - litb

5
Microsoft n'a pas encore implémenté Expression SFINAE dans son compilateur C ++. Imaginez que je puisse aider à gagner du temps, car je ne savais pas pourquoi cela ne fonctionnait pas pour moi. Belle solution cependant, j'ai hâte de l'utiliser dans Visual Studio!
Jonathan

3
Votre premier exemple de lien est rompu
NathanOliver

1
Il faut dire que static_assert(has_stream<X, char>() == true, "fail X");cela compilera et ne s'affirmera pas car char est convertible en int, donc si ce comportement n'est pas souhaité et que tous les types d'arguments correspondent je ne sais pas comment cela peut être réalisé?
Gabriel

4
Si vous êtes aussi perplexe que moi sur les deux arguments de decltype: decltype n'en prend vraiment qu'un; la virgule est un opérateur ici. Voir stackoverflow.com/questions/16044514/…
André

159

C ++ permet d' utiliser SFINAE pour cela (notez qu'avec les fonctionnalités C ++ 11, c'est plus simple car il prend en charge SFINAE étendu sur des expressions presque arbitraires - ce qui suit a été conçu pour fonctionner avec les compilateurs C ++ 03 courants):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

le modèle et la macro ci-dessus essaient d'instancier un modèle, en lui donnant un type de pointeur de fonction membre et le pointeur de fonction membre réel. Si les types ne correspondent pas, SFINAE entraîne l'ignorance du modèle. Utilisation comme ceci:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Mais notez que vous ne pouvez pas simplement appeler cette toStringfonction dans cette branche if. puisque le compilateur vérifiera la validité dans les deux branches, cela échouerait dans les cas où la fonction n'existe pas. Une façon consiste à utiliser SFINAE à nouveau (enable_if peut également être obtenu à partir de boost):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Amusez-vous à l'utiliser. L'avantage est qu'il fonctionne également pour les fonctions membres surchargées, ainsi que pour les fonctions membres const (n'oubliez pas d'utiliser std::string(T::*)() constcomme type de pointeur de fonction membre alors!).


7
J'aime la façon dont type_checkon utilise pour s'assurer que les signatures correspondent exactement. Existe-t-il un moyen de faire en sorte qu'il corresponde à n'importe quelle méthode qui pourrait être appelée de la même manière qu'une méthode avec signature Signpourrait être appelée? (Par exemple, si Sign= std::string(T::*)(), laissez std::string T::toString(int default = 42, ...)correspondre.)
j_random_hacker

5
Je trouve juste quelque chose à ce sujet qui n'était pas immédiatement évident pour moi, donc au cas où cela aiderait les autres: chk n'est pas et n'a pas besoin d'être défini! L'opérateur sizeof détermine la taille de la sortie de chk sans qu'il soit nécessaire d'appeler chk.
SCFrench

3
@ deek0146: Oui, Tne doit pas être un type primitif, car la déclaration du pointeur sur la méthode de T n'est pas soumise à SFINAE et générera une erreur pour tout T. non IMO. la solution la plus simple consiste à combiner avec la is_classvérification à partir de renforcer.
Jan Hudec

2
Comment puis-je faire fonctionner cela si ma toStringest une fonction de modèle?
Frank

4
Est-ce (ou quelque chose d'équivalent) dans Boost?
Dan Nissenbaum

89

C ++ 20 - requiresexpressions

Avec C ++ 20 viennent des concepts et des outils assortis tels que des requiresexpressions qui sont un moyen intégré de vérifier l'existence d'une fonction. Avec eux, vous pouvez réécrire votre optionalToStringfonction comme suit:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Boîte à outils de détection

N4502 propose une boîte à outils de détection à inclure dans la bibliothèque standard C ++ 17 qui a finalement été intégrée dans les principes fondamentaux de la bibliothèque TS v2. Il n'entrera probablement jamais dans la norme car il a été subsumé par les requiresexpressions depuis, mais il résout toujours le problème d'une manière quelque peu élégante. La boîte à outils présente certaines métafonctions, notamment celles std::is_detectedqui peuvent être utilisées pour écrire facilement des métafonctions de détection de type ou de fonction au-dessus. Voici comment vous pouvez l'utiliser:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Notez que l'exemple ci-dessus n'a pas été testé. La boîte à outils de détection n'est pas encore disponible dans les bibliothèques standard, mais la proposition contient une implémentation complète que vous pouvez facilement copier si vous en avez vraiment besoin. Il fonctionne bien avec la fonctionnalité C ++ 17 if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Boost.Hana

Boost.Hana s'appuie apparemment sur cet exemple spécifique et fournit une solution pour C ++ 14 dans sa documentation, donc je vais le citer directement:

[...] Hana fournit une is_validfonction qui peut être combinée avec des lambdas génériques C ++ 14 pour obtenir une implémentation beaucoup plus propre de la même chose:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Cela nous laisse un objet fonction has_toStringqui retourne si l'expression donnée est valide sur l'argument que nous lui passons. Le résultat est renvoyé en tant que IntegralConstant, donc constexpr-ness n'est pas un problème ici car le résultat de la fonction est de toute façon représenté comme un type. Maintenant, en plus d'être moins verbeux (c'est une ligne!), L'intention est beaucoup plus claire. D'autres avantages sont le fait qu'il has_toStringpeut être transmis à des algorithmes d'ordre supérieur et qu'il peut également être défini au niveau de la portée de la fonction, il n'est donc pas nécessaire de polluer la portée de l'espace de noms avec des détails d'implémentation.

Boost.TTI

Une autre boîte à outils quelque peu idiomatique pour effectuer une telle vérification - même si elle est moins élégante - est Boost.TTI , introduite dans Boost 1.54.0. Pour votre exemple, vous devrez utiliser la macro BOOST_TTI_HAS_MEMBER_FUNCTION. Voici comment vous pouvez l'utiliser:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Ensuite, vous pouvez utiliser le boolpour créer un chèque SFINAE.

Explication

La macro BOOST_TTI_HAS_MEMBER_FUNCTIONgénère la métafonction has_member_function_toStringqui prend le type vérifié comme premier paramètre de modèle. Le deuxième paramètre de modèle correspond au type de retour de la fonction membre et les paramètres suivants correspondent aux types des paramètres de la fonction. Le membre valuecontient truesi la classe Ta une fonction membre std::string toString().

Vous has_member_function_toStringpouvez également prendre un pointeur de fonction membre comme paramètre de modèle. Par conséquent, il est possible de remplacer has_member_function_toString<T, std::string>::valuepar has_member_function_toString<std::string T::* ()>::value.


1
plus concis que 03
ZFY

@ZFY Je pense que Boost.TTI fonctionne aussi avec C ++ 03, mais c'est la solution la moins élégante du lot.
Morwenn

La solution C ++ 20 est-elle vraiment valide? Je le voudrais - mais il est refusé par g ++ et msvc - seulement accepté par clang.
Bernd Baumanns

à cppreference, vous pouvez lire: Si une expression-requiert contient des types ou des expressions invalides dans ses exigences, et qu'elle n'apparaît pas dans la déclaration d'une entité basée sur un modèle, alors le programme est mal formé.
Bernd Baumanns

@BerndBaumanns Vraiment? Je l'ai fait fonctionner avec le tronc GCC: godbolt.org/z/CBwZdE Peut-être que vous avez raison, j'ai seulement vérifié que cela fonctionnait, mais je n'ai pas vérifié si c'était légal selon le libellé standard.
Morwenn

56

Bien que cette question ait deux ans, j'oserai ajouter ma réponse. Espérons que cela clarifiera la solution précédente, incontestablement excellente. J'ai pris les réponses très utiles de Nicola Bonelli et Johannes Schaub et les ai fusionnées dans une solution qui est, à mon humble avis, plus lisible, claire et ne nécessite pas l' typeofextension:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Je l'ai vérifié avec gcc 4.1.2. Le crédit revient principalement à Nicola Bonelli et Johannes Schaub, alors donnez-leur un vote si ma réponse vous aide :)


1
Je me demande simplement si cela fait quelque chose que la solution de Konrad Rudolph ci-dessous ne fait pas?
Alastair Irvine

3
@AlastairIrvine, cette solution cache toute la logique à l'intérieur, Konrad's met une partie du fardeau sur l'utilisateur. Bien que courte et beaucoup plus lisible, la solution de Konrad nécessite une spécialisation de modèle distincte pour chaque classe qui en a toString. Si vous écrivez une bibliothèque générique, qui souhaite fonctionner avec n'importe quelle classe (pensez à quelque chose comme boost), demander à l'utilisateur de définir des spécialisations supplémentaires de certains modèles obscurs peut être inacceptable. Parfois, il est préférable d'écrire un code très compliqué pour garder l'interface publique aussi simple que possible.
FireAphis

30

Une solution simple pour C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Mise à jour, 3 ans plus tard: (et ce n'est pas testé). Pour tester l'existence, je pense que cela fonctionnera:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
C'est simple et élégant, mais à proprement parler ne répond pas à la question d'OP: vous ne permettez pas à l'appelant de vérifier l'existence d'une fonction, vous la fournissez toujours . Mais sympa quand même.
Adrian W

@AdrianW, bon point. J'ai mis à jour ma réponse. Je ne l'ai pas testé cependant
Aaron McDaid

Au cas où cela aiderait quelqu'un d'autre, je ne pourrais pas faire ce travail sans template<typename>avant la surcharge variadique: il n'était pas envisagé pour la résolution.
Laboratorio Cobotica

Encore une fois, ce n'est pas valide C ++ 11.
Peter

29

C'est pour cela que les traits de caractères sont là. Malheureusement, ils doivent être définis manuellement. Dans votre cas, imaginez ce qui suit:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
vous devriez préférer l'énumération pour les traits plutôt que les constantes statiques: "Les membres constants statiques sont des valeurs l, ce qui oblige le compilateur à instancier et allouer la définition du membre statique. Par conséquent, le calcul n'est plus limité à un" temps de compilation pur " "effet".
Özgür

5
"Les valeurs d'énumération ne sont pas des valeurs l (c'est-à-dire qu'elles n'ont pas d'adresse). Donc, lorsque vous les transmettez" par référence ", aucune mémoire statique n'est utilisée. C'est presque exactement comme si vous aviez transmis la valeur calculée comme un littéral . Ces considérations nous motivent à utiliser des valeurs d'énumération "Modèles C ++: le guide complet
Özgür

22
Comptrol: non, le passage cité ne s'applique pas ici car les constantes statiques de type entier sont un cas particulier! Ils se comportent exactement comme un enum ici et sont le moyen préféré. L'ancien hack enum n'était nécessaire que sur les compilateurs qui ne respectaient pas la norme C ++.
Konrad Rudolph

3
@Roger Pate: Pas tout à fait. «Utilisé dans le programme» ici est apparemment synonyme de «référencé». La lecture dominante de ce passage, et celle implémentée par tous les compilateurs C ++ modernes, est que vous pouvez prendre la valeur d'une constante statique sans avoir besoin de la déclarer (la phrase précédente dit ceci: «… le membre peut apparaître dans des expressions constantes intégrales … ”). Vous ne devez le définir que si vous prenez son adresse (explicitement via &T::xou implicitement en le liant à une référence).
Konrad Rudolph


25

Eh bien, cette question a déjà une longue liste de réponses, mais je voudrais souligner le commentaire de Morwenn: il existe une proposition pour C ++ 17 qui le rend vraiment beaucoup plus simple. Voir N4502 pour plus de détails, mais comme exemple autonome, considérez ce qui suit.

Cette partie est la partie constante, mettez-la dans un en-tête.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

puis il y a la partie variable, où vous spécifiez ce que vous recherchez (un type, un type de membre, une fonction, une fonction de membre, etc.). Dans le cas de l'OP:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

L'exemple suivant, tiré du N4502 , montre une sonde plus élaborée:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Par rapport aux autres implémentations décrites ci-dessus, celle-ci est assez simple: un ensemble réduit d'outils ( void_tet detect) suffit, pas besoin de macros velues. En outre, il a été signalé (voir N4502 ) qu'il est plus efficace (temps de compilation et consommation de mémoire du compilateur) que les approches précédentes.

Voici un exemple en direct . Cela fonctionne bien avec Clang, mais malheureusement, les versions de GCC antérieures à 5.1 ont suivi une interprétation différente de la norme C ++ 11 qui a causé un void_tdysfonctionnement comme prévu. Yakk a déjà fourni la solution: utilisez la définition suivante de void_t( void_t dans la liste des paramètres fonctionne mais pas comme type de retour ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

Est-il possible de l'étendre pour détecter les fonctions non membres?
plasmacel

Oui bien sûr. Examinez attentivement les exemples: vous fournissez essentiellement une expression et vérifiez si elle est valide. Rien n'exige que cette expression concerne uniquement un appel de fonction membre.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) est la voie de l'avenir ... Je cherchais une manière ordonnée de détecter des choses sur les types et N4502 est la voie aller.
tlonuk

11

Il s'agit d'une solution C ++ 11 pour le problème général si "Si je faisais X, serait-il compilé?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

TRAIT has_to_stringtelle que has_to_string<T>::valueest truesi et seulement si Ta une méthode .toStringqui peut être invoqué avec 0 arguments dans ce contexte.

Ensuite, j'utiliserais la répartition des balises:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

qui a tendance à être plus facile à gérer que les expressions SFINAE complexes.

Vous pouvez écrire ces traits avec une macro si vous vous retrouvez à le faire beaucoup, mais ils sont relativement simples (quelques lignes chacun), alors peut-être que cela n'en vaut pas la peine:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

ce qui précède crée une macro MAKE_CODE_TRAIT. Vous lui passez le nom du trait que vous voulez, et du code qui peut tester le type T. Donc:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

crée la classe de traits ci-dessus.

Soit dit en passant, la technique ci-dessus fait partie de ce que MS appelle «expression SFINAE», et leur compilateur 2013 échoue assez fort.

Notez qu'en C ++ 1y la syntaxe suivante est possible:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

qui est une branche conditionnelle de compilation en ligne qui abuse de nombreuses fonctionnalités C ++. Cela ne vaut probablement pas la peine, car l'avantage (du code étant en ligne) ne vaut pas le coût (si personne ne comprend comment il fonctionne), mais l'existence de la solution ci-dessus peut être intéressante.


Est-ce que cela gère les affaires privées?
tower120

@ tower120 Je devrais expérimenter: comment les modèles interagissent avec privé / public / protégé est un peu obscur pour moi. Peu importe où vous invoquez has_to_stringcependant.
Yakk - Adam Nevraumont

mais vous savez, si regardez de l'autre côté ... Nous pouvons atteindre les membres protégés de la classe Derived. Peut-être que si vous mettez tous ces trucs dans la classe INSIDE et que vous convertissez des structures en fonctions constexpr ...
tower120

Ici, regardez ce coliru.stacked-crooked.com/a/ee94d16e7c07e093 Je ne peux tout simplement pas le faire constexpr
tower120


10

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

Vérifiez le 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 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 xquelle 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;

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)

1
avez-vous une idée pourquoi si nous passons sig_check<func_sig, &T::func_name>à la vérification de fonction gratuite: sig_check<func_sig, &func_name>il ne parvient pas à construire avec "identifiant non déclaré" mentionnant le nom de la fonction que nous voulons vérifier? parce que je m'attendrais à ce que SFINAE n'en fasse PAS une erreur, il ne fait que cela pour les membres, pourquoi pas pour les fonctions gratuites?
v.oddou

Je suppose que cela aurait quelque chose à voir avec le fait qu'une fonction libre n'est pas une classe ou une structure. Cette technique de déduction de la présence d'un membre se concentre vraiment sur le mécanisme d'héritage multiple en C ++ forçant l'ambiguïté entre une classe de stub qui n'existe que dans le but d'héberger le membre que vous recherchez par rapport à la classe que vous recherchez réellement pour le membre C'est une question intéressante cependant, je n'y avais pas pensé. Vous pouvez vérifier les autres techniques de vérification des membres C ++ 11/14, j'ai vu des choses intelligentes dans la nouvelle norme.
Brett Rossier

Merci pour votre réponse, je pense que je devrai peut-être vérifier plus en profondeur les informations que vous donnez sur l'héritage, car jusqu'à présent, je ne voyais aucune corrélation entre le simple fait de s'appuyer sur SFINAE pour créer une expression qui ne serait pas correcte exprimant l'accès à un membre dans un paramètre de type de modèle et l'héritage multiple. Mais je crois complètement qu'en C ++, même des concepts distants peuvent se saigner. Maintenant, pour les fonctions gratuites, cette question est intéressante: stackoverflow.com/questions/26744589 La réponse de TC semble utiliser une astuce pour déclarer un mannequin pour éviter "l'identifiant non déclaré"
v.oddou

8

J'ai écrit une réponse à cela dans un autre fil qui (contrairement aux solutions ci-dessus) vérifie également les fonctions membres héritées:

SFINAE pour vérifier les fonctions membres héritées

Voici quelques exemples de cette solution:

Exemple 1:

Nous recherchons un membre avec la signature suivante: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Veuillez noter qu'il vérifie même la constance de la méthode et fonctionne également avec les types primitifs. (Je veux dire has_const_begin<int>::valueest faux et ne provoque pas d'erreur de compilation.)

Exemple 2

Maintenant, nous recherchons la signature: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

Veuillez noter que MyClass n'a pas besoin d'être constructible par défaut ou de satisfaire un concept spécial. La technique fonctionne également avec les membres du modèle.

J'attends avec impatience des opinions à ce sujet.


7

Maintenant c'était une belle petit puzzle - excellente question!

Voici une alternative à la solution de Nicola Bonelli qui ne dépend pas de l' typeofopérateur non standard .

Malheureusement, il ne fonctionne pas sur GCC (MinGW) 3.4.5 ou Digital Mars 8.42n, mais il fonctionne sur toutes les versions de MSVC (y compris VC6) et sur Comeau C ++.

Le bloc de commentaires plus long contient des détails sur son fonctionnement (ou est censé fonctionner). Comme il est dit, je ne sais pas quel comportement est conforme aux normes - je serais heureux de recevoir des commentaires à ce sujet.


mise à jour - 7 novembre 2008:

Il semble que bien que ce code soit syntaxiquement correct, le comportement que MSVC et Comeau C ++ montrent ne suit pas la norme (merci à Leon Timmermans et litb de m'avoir pointé dans la bonne direction). La norme C ++ 03 dit ce qui suit:

14.6.2 Noms dépendants [temp.dep]

Paragraphe 3

Dans la définition d'un modèle de classe ou d'un membre d'un modèle de classe, si une classe de base du modèle de classe dépend d'un paramètre de modèle, la portée de la classe de base n'est pas examinée lors de la recherche de nom non qualifié au point de définition de la classe modèle ou membre ou lors d'une instanciation du modèle ou membre de classe.

Ainsi, cela ressemble à cela lorsque MSVC ou Comeau considèrent la toString()fonction membre d' Teffectuer une recherche de nom sur le site d'appel dansdoToString() lorsque le modèle est instancié, c'est incorrect (même si c'est en fait le comportement que je cherchais dans ce cas).

Le comportement de GCC et de Digital Mars semble correct - dans les deux cas, la fonction non membre toString()est liée à l'appel.

Rats - J'ai pensé que j'aurais pu trouver une solution intelligente, au lieu de cela j'ai découvert quelques bogues de compilation ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Non, ce n'est pas conforme aux normes, bien que je pense que cela fonctionnera dans GCC si vous activez l'option -fpermissive.
Leon Timmermans

Je sais que les commentaires ne laissent pas beaucoup de place, mais pourriez-vous indiquer pourquoi il n'est pas conforme aux normes? (Je ne discute pas - je suis curieux)
Michael Burr

Mike B: la norme dit en 3.10 p15: "Si un programme tente d'accéder à la valeur stockée d'un objet via une valeur l autre que l'un des types suivants, le comportement n'est pas défini" et cette liste n'inclut en effet pas le cas que vous faire.
Johannes Schaub - litb

4
je ne sais pas pourquoi il n'ajoute pas un autre commentaire de moi: votre appel toString n'est pas qualifié. il appellera donc toujours la fonction libre et jamais celle de la base, car la classe de base dépend d'un paramètre de type de modèle.
Johannes Schaub - litb

@litb: Merci pour les pointeurs. Je ne pense pas que 3.10 s'applique ici. L'appel à toString () à l'intérieur de doToString () n'est pas "l'accès à la valeur stockée d'un objet via une valeur l". Mais votre 2e commentaire est correct. Je mettrai à jour la réponse.
Michael Burr

6

La solution C ++ standard présentée ici par litb ne fonctionnera pas comme prévu si la méthode se trouve être définie dans une classe de base.

Pour une solution qui gère cette situation, reportez-vous à:

En russe: http://www.rsdn.ru/forum/message/2759773.1.aspx

Traduction en anglais par Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

C'est incroyablement intelligent. Cependant, un problème avec cette solution est que cela donne des erreurs de compilation si le type testé est un type qui ne peut pas être utilisé comme classe de base (par exemple, les types primitifs)

Dans Visual Studio, j'ai remarqué que si vous travaillez avec une méthode sans arguments, une paire supplémentaire de redundant () doit être insérée autour des arguments pour deduce () dans l'expression sizeof.


Hmm, ayant développé ma propre version en utilisant des idées, j'ai trouvé que l'idée avait d'autres inconvénients, j'ai donc supprimé à nouveau le code de ma réponse. La première est que toutes les fonctions doivent être publiques dans le type cible. Vous ne pouvez donc pas rechercher une fonction "f" dans ceci: struct g { void f(); private: void f(int); };parce que l'une des fonctions est privée (c'est parce que le code le fait using g::f;, ce qui la fait échouer si aucune fn'est accessible).
Johannes Schaub - litb

6

MSVC a les mots clés __if_exists et __if_not_exists ( Doc ). Avec l'approche typeof-SFINAE de Nicola, j'ai pu créer un chèque pour GCC et MSVC comme l'OP recherché.

Mise à jour: source peut être trouvée ici


6

Un exemple utilisant SFINAE et la spécialisation partielle de modèle, en écrivant une Has_foovérification de concept:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

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

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

J'ai modifié la solution fournie dans https://stackoverflow.com/a/264088/2712152 pour la rendre un peu plus générale. De plus, comme il n'utilise aucune des nouvelles fonctionnalités C ++ 11, nous pouvons l'utiliser avec d'anciens compilateurs et nous devrions également travailler avec msvc. Mais les compilateurs devraient permettre à C99 de l'utiliser car il utilise des macros variadiques.

La macro suivante peut être utilisée pour vérifier si une classe particulière a un typedef particulier ou non.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

La macro suivante peut être utilisée pour vérifier si une classe particulière a une fonction membre particulière ou non avec un nombre donné d'arguments.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Nous pouvons utiliser les 2 macros ci-dessus pour effectuer les vérifications de has_typedef et has_mem_func comme:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Vous pouvez améliorer cela pour prendre en charge les fonctions membres avec des arguments de modèle. Changez le modèle <nom de T> en modèle <nom de T, type ... Args>, puis vous pouvez utiliser "Args ..." dans votre élipse de macro pour créer une structure de vérification avec des arguments de modèle variadiques. par exemple. Détecter la méthode "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

Étrange, personne n'a suggéré la belle astuce suivante que j'ai vue une fois sur ce site:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

Vous devez vous assurer que T est une classe. Il semble que l'ambiguïté dans la recherche de foo soit un échec de substitution. Je l'ai fait fonctionner sur gcc, je ne sais pas si c'est standard.


3

Le modèle générique qui peut être utilisé pour vérifier si une "fonction" est prise en charge par le type:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

Le modèle qui vérifie s'il existe une méthode foocompatible avec la signaturedouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Exemples

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


Existe-t-il un moyen d'intégrer le has_foodans l'appel de modèle de is_supported. Ce que je voudrais est d'appeler quelque chose comme: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. La raison de cela, je veux définir un has_foopour chaque signature de fonction différente que je veux vérifier avant de pouvoir vérifier la fonction?
CJCombrink

2

Et cette solution?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

Échoue si toStringest surchargé, car il &U::toStringest ambigu.
Yakk - Adam Nevraumont

@Yakk Je pense qu'un casting peut résoudre ce problème.
user1095108

2

Il y a beaucoup de réponses ici, mais je n'ai pas réussi à trouver une version qui exécute un ordre de résolution de méthode réelle , sans utiliser les fonctionnalités c ++ les plus récentes (en utilisant uniquement les fonctionnalités c ++ 98).
Remarque: Cette version est testée et fonctionne avec vc ++ 2013, g ++ 5.2.0 et le compilateur onlline.

J'ai donc proposé une version qui n'utilise que sizeof ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Démo en direct (avec vérification du type de retour étendu et solution de contournement vc ++ 2010): http://cpp.sh/5b2vs

Aucune source, car je l'ai inventé moi-même.

Lors de l'exécution de la démonstration en direct sur le compilateur g ++, veuillez noter que les tailles de tableau de 0 sont autorisées, ce qui signifie que le static_assert utilisé ne déclenchera pas d'erreur de compilation, même en cas d'échec.
Une solution de contournement couramment utilisée consiste à remplacer le «typedef» dans la macro par «extern».


Non, mais je le déclare moi-même et il n'utilise pas rvalue (regardez en haut de mon code). Ou vous pourriez simplement vous convaincre et essayer la démonstration en direct en mode c ++ 98. PS: static_assert n'est pas non plus c ++ 98, mais il existe des solutions (démo en direct)
user3296587

d'oh! manqué ça. :-)
Ian Ni-Lewis

Vos assertions statiques ne fonctionnent pas. Vous devez utiliser la taille du tableau -1 au lieu de 0 (essayez de mettre static_assert(false);). J'utilisais cela en relation avec CRTP où je veux déterminer si la classe dérivée a une fonction particulière - qui s'avère ne pas fonctionner, mais vos assertions sont toujours passées. J'ai perdu des cheveux avec celui-là.
le porc

Je suppose que vous utilisez g ++. Veuillez noter que gcc / g ++ a une extension qui permet un tableau de taille nulle ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

Pourriez-vous éventuellement réécrire ceci afin de ne pas surcharger l'opérateur,? par exemple, choisir un autre opérateur? De plus, évitez de polluer l'espace de noms avec autre chose que has_awesome_member?
einpoklum

1

Voici ma version qui gère toutes les surcharges de fonctions membres possibles avec une arité arbitraire, y compris les fonctions membres de modèle, éventuellement avec des arguments par défaut. Il distingue 3 scénarios mutuellement exclusifs lors d'un appel de fonction membre à un type de classe, avec des types d'arg donnés: (1) valide, ou (2) ambigu, ou (3) non viable. Exemple d'utilisation:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Vous pouvez maintenant l'utiliser comme ceci:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Voici le code, écrit en c ++ 11, cependant, vous pouvez facilement le porter (avec des ajustements mineurs) vers non-c ++ 11 qui a des extensions typeof (par exemple gcc). Vous pouvez remplacer la macro HAS_MEM par la vôtre.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Vous pouvez ignorer toutes les métaprogrammations en C ++ 14, et simplement écrire ceci à l'aide fit::conditionalde la bibliothèque Fit :

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Vous pouvez également créer la fonction directement à partir des lambdas:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Cependant, si vous utilisez un compilateur qui ne prend pas en charge les lambdas génériques, vous devrez écrire des objets fonction distincts:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
Est-il facile d'écrire ceci pour ne pas avoir à dépendre d' fitune bibliothèque autre que la norme?
einpoklum

1

Avec C ++ 20, vous pouvez écrire ce qui suit:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

Voici un exemple de code de travail.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrva activer la fonction qui prend un intargument supplémentaire qui a priorité sur la fonction qui prend longlorsqu'elle est appelée avec 0.

Vous pouvez utiliser le même principe pour les fonctions qui retournent truesi la fonction est implémentée.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

J'avais un problème similaire:

Une classe modèle qui peut être dérivée de quelques classes de base, certaines qui ont un certain membre et d'autres qui n'en ont pas.

Je l'ai résolu de manière similaire à la réponse "typeof" (de Nicola Bonelli), mais avec decltype donc il se compile et s'exécute correctement sur MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

Une autre façon de le faire en C ++ 17 (inspiré par boost: hana).

Écrivez-le une fois et utilisez-le plusieurs fois. Il ne nécessite pas de has_something<T>classes de traits de type.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Exemple

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"Nous n'avons besoin d'aucune description de réponse" ... veuillez ajouter une description informative à votre réponse pour l'améliorer. Merci.
YesThatIsMyName
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.