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 T
a 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".
Réponses:
Oui, avec SFINAE, vous pouvez vérifier si une classe donnée fournit une certaine méthode. Voici le code de travail:
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.
la source
typeof
pardecltype
lors de l'utilisation de C ++ 0x , par exemple via -std = c ++ 0x.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:
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érieurdecltype
n'est pas valide (aka, la fonction n'existe pas).Le
void()
est utilisé pour créer le type de retour de toutes ces fonctionsvoid
.L'
0
argument est utilisé pour préférer laos << obj
surcharge si les deux sont disponibles (le littéral0
est de typeint
et 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.
Exemple en direct.
Et passons aux explications. Tout d'abord,
sfinae_true
c'est un type d'aide, et cela revient au même que l'écrituredecltype(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'unstd::true_type
oustd::false_type
de l' autre à la fin, selon que l'decltype
archivagetest_stream
échoue ou non.Enfin,
std::declval
vous 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 quedecltype
,sizeof
et d'autres.Notez que ce
decltype
n'est pas nécessairement nécessaire, carsizeof
(et tous les contextes non évalués) ont obtenu cette amélioration. C'est juste quedecltype
fournit déjà un type et en tant que tel est juste plus propre. Voici unesizeof
version de l'une des surcharges:le
int
long
paramètres et sont toujours là pour la même raison. Le pointeur de tableau est utilisé pour fournir un contexte oùsizeof
peut être utilisé.la source
decltype
oversizeof
est é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).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é?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):
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:
Mais notez que vous ne pouvez pas simplement appeler cette
toString
fonction 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):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::*)() const
comme type de pointeur de fonction membre alors!).la source
type_check
on 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 signatureSign
pourrait être appelée? (Par exemple, siSign
=std::string(T::*)()
, laissezstd::string T::toString(int default = 42, ...)
correspondre.)T
ne 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 lais_class
vérification à partir de renforcer.toString
est une fonction de modèle?C ++ 20 -
requires
expressionsAvec C ++ 20 viennent des concepts et des outils assortis tels que des
requires
expressions qui sont un moyen intégré de vérifier l'existence d'une fonction. Avec eux, vous pouvez réécrire votreoptionalToString
fonction comme suit: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
requires
expressions 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 cellesstd::is_detected
qui 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: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
: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:
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:Ensuite, vous pouvez utiliser le
bool
pour créer un chèque SFINAE.Explication
La macro
BOOST_TTI_HAS_MEMBER_FUNCTION
génère la métafonctionhas_member_function_toString
qui 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 membrevalue
contienttrue
si la classeT
a une fonction membrestd::string toString()
.Vous
has_member_function_toString
pouvez également prendre un pointeur de fonction membre comme paramètre de modèle. Par conséquent, il est possible de remplacerhas_member_function_toString<T, std::string>::value
parhas_member_function_toString<std::string T::* ()>::value
.la source
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'
typeof
extension: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 :)
la source
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.Une solution simple pour C ++ 11:
Mise à jour, 3 ans plus tard: (et ce n'est pas testé). Pour tester l'existence, je pense que cela fonctionnera:
la source
template<typename>
avant la surcharge variadique: il n'était pas envisagé pour la résolution.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:
la source
&T::x
ou implicitement en le liant à une référence).type traits
de la compilation conditionnelle en C ++ 11Eh 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.
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:
L'exemple suivant, tiré du N4502 , montre une sonde plus élaborée:
Par rapport aux autres implémentations décrites ci-dessus, celle-ci est assez simple: un ensemble réduit d'outils (
void_t
etdetect
) 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_t
dysfonctionnement comme prévu. Yakk a déjà fourni la solution: utilisez la définition suivante devoid_t
( void_t dans la liste des paramètres fonctionne mais pas comme type de retour ):la source
Il s'agit d'une solution C ++ 11 pour le problème général si "Si je faisais X, serait-il compilé?"
TRAIT
has_to_string
telle quehas_to_string<T>::value
esttrue
si et seulement siT
a une méthode.toString
qui peut être invoqué avec 0 arguments dans ce contexte.Ensuite, j'utiliserais la répartition des balises:
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:
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 typeT
. Donc: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:
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.
la source
has_to_string
cependant.Voici quelques extraits d'utilisation: * Les tripes pour tout cela sont plus bas
Vérifiez le membre
x
dans une classe donnée. Peut être var, func, class, union ou enum:Vérifiez la fonction membre
void x()
:Vérifiez la variable membre
x
:Vérifiez la classe de membre
x
:Vérifiez le syndicat membre
x
:Vérifiez l'énumération des membres
x
:Vérifiez toute fonction membre
x
quelle que soit la signature:OU
Détails et noyau:
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
la source
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?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
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>::value
est faux et ne provoque pas d'erreur de compilation.)Exemple 2
Maintenant, nous recherchons la signature:
void foo(MyClass&, unsigned)
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.
la source
Maintenant c'était une belle petit puzzle - excellente question!
Voici une alternative à la solution de Nicola Bonelli qui ne dépend pas de l'
typeof
opé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:
Ainsi, cela ressemble à cela lorsque MSVC ou Comeau considèrent la
toString()
fonction membre d'T
effectuer 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 ...
la source
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.
la source
struct g { void f(); private: void f(int); };
parce que l'une des fonctions est privée (c'est parce que le code le faitusing g::f;
, ce qui la fait échouer si aucunef
n'est accessible).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
la source
Un exemple utilisant SFINAE et la spécialisation partielle de modèle, en écrivant une
Has_foo
vérification de concept:la source
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.
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.
Nous pouvons utiliser les 2 macros ci-dessus pour effectuer les vérifications de has_typedef et has_mem_func comme:
la source
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" ); };
Étrange, personne n'a suggéré la belle astuce suivante que j'ai vue une fois sur ce site:
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.
la source
Le modèle générique qui peut être utilisé pour vérifier si une "fonction" est prise en charge par le type:
Le modèle qui vérifie s'il existe une méthode
foo
compatible avec la signaturedouble(const char*)
Exemples
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
la source
has_foo
dans l'appel de modèle deis_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 unhas_foo
pour chaque signature de fonction différente que je veux vérifier avant de pouvoir vérifier la fonction?Et cette solution?
la source
toString
est surchargé, car il&U::toString
est ambigu.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 ():
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».
la source
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à.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:
Vous pouvez maintenant l'utiliser comme ceci:
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.
la source
Vous pouvez ignorer toutes les métaprogrammations en C ++ 14, et simplement écrire ceci à l'aide
fit::conditional
de la bibliothèque Fit :Vous pouvez également créer la fonction directement à partir des lambdas:
Cependant, si vous utilisez un compilateur qui ne prend pas en charge les lambdas génériques, vous devrez écrire des objets fonction distincts:
la source
fit
une bibliothèque autre que la norme?Avec C ++ 20, vous pouvez écrire ce qui suit:
la source
Voici un exemple de code de travail.
toStringFn<T>* = nullptr
va activer la fonction qui prend unint
argument supplémentaire qui a priorité sur la fonction qui prendlong
lorsqu'elle est appelée avec0
.Vous pouvez utiliser le même principe pour les fonctions qui retournent
true
si la fonction est implémentée.la source
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:
la source
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.Exemple
la source
la source