Je demande une astuce de modèle pour détecter si une classe a une fonction membre spécifique d'une signature donnée.
Le problème est similaire à celui cité ici http://www.gotw.ca/gotw/071.htm mais pas le même: dans l'item du livre de Sutter, il a répondu à la question qu'une classe C DOIT FOURNIR une fonction membre avec une signature particulière, sinon le programme ne compilera pas. Dans mon problème, je dois faire quelque chose si une classe a cette fonction, sinon faire «autre chose».
Un problème similaire a été rencontré par boost :: serialization mais je n'aime pas la solution qu'ils ont adoptée: une fonction modèle qui invoque par défaut une fonction libre (que vous devez définir) avec une signature particulière à moins que vous ne définissiez une fonction membre particulière ( dans leur cas "sérialiser" qui prend 2 paramètres d'un type donné) avec une signature particulière, sinon une erreur de compilation se produira. Cela consiste à implémenter une sérialisation à la fois intrusive et non intrusive.
Je n'aime pas cette solution pour deux raisons:
- Pour être non intrusif, vous devez remplacer la fonction globale "sérialiser" qui se trouve dans l'espace de noms boost :: serialization, vous avez donc DANS VOTRE CODE CLIENT pour ouvrir le boost d'espace de noms et la sérialisation de l'espace de noms!
- La pile pour résoudre ce désordre était de 10 à 12 appels de fonctions.
J'ai besoin de définir un comportement personnalisé pour les classes qui n'ont pas cette fonction membre, et mes entités sont à l'intérieur d'espaces de noms différents (et je ne veux pas remplacer une fonction globale définie dans un espace de noms pendant que je suis dans un autre)
Pouvez-vous me donner un indice pour résoudre ce puzzle?
Réponses:
Je ne sais pas si je vous comprends bien, mais vous pouvez exploiter SFINAE pour détecter la présence de fonction au moment de la compilation. Exemple de mon code (teste si la classe a une fonction membre size_t used_memory () const).
la source
size_t(std::vector::*p)() = &std::vector::size;
,.Voici une implémentation possible reposant sur les fonctionnalités de C ++ 11. Il détecte correctement la fonction même si elle est héritée (contrairement à la solution dans la réponse acceptée, comme l'observe Mike Kinghan dans sa réponse ).
La fonction testée par cet extrait de code s'appelle
serialize
:Usage:
la source
serialize
lui - même accepte un modèle. Existe-t-il un moyen de tester l'serialize
existence sans taper le type exact?La réponse acceptée à cette question de l'introspection des fonctions membres du temps de compilation, bien qu'elle soit à juste titre populaire, présente un hic qui peut être observé dans le programme suivant:
Construit avec GCC 4.6.3, les sorties du programme
110
- nous informant queT = std::shared_ptr<int>
ne fournit pasint & T::operator*() const
.Si vous n'êtes pas déjà avisé de ce piège, alors un coup d'oeil à la définition de
std::shared_ptr<T>
dans l'en-tête vous<memory>
éclairera. Dans cette implémentation,std::shared_ptr<T>
est dérivé d'une classe de base dont il hériteoperator*() const
. Ainsi, l'instanciation de modèleSFINAE<U, &U::operator*>
qui constitue la "recherche" de l'opérateur pourU = std::shared_ptr<T>
ne se produira pas, car ellestd::shared_ptr<T>
n'a pasoperator*()
en soi et l'instanciation de modèle ne "fait pas d'héritage".Cet hic n'affecte pas l'approche bien connue de SFINAE, utilisant "The sizeof () Trick", pour détecter simplement si
T
a une fonction membremf
(voir par exemple cette réponse et commentaires). Mais établir ce quiT::mf
existe n'est souvent (généralement?) Pas assez bon: vous devrez peut-être également établir qu'il a la signature souhaitée. C'est là que la technique illustrée se démarque. La variante pointée de la signature souhaitée est inscrite dans un paramètre d'un type de modèle qui doit être satisfait par&T::mf
pour que la sonde SFINAE réussisse. Mais cette technique d'instanciation de modèle donne la mauvaise réponse lorsqu'elleT::mf
est héritée.Une technique SFINAE sûre pour l'introspection du temps de compilation de
T::mf
doit éviter d'utiliser&T::mf
dans un argument de modèle pour instancier un type dont dépend la résolution de modèle de fonction SFINAE. Au lieu de cela, la résolution de la fonction de modèle SFINAE ne peut dépendre que des déclarations de type exactement pertinentes utilisées comme types d'argument de la fonction de sonde SFINAE surchargée.En guise de réponse à la question qui respecte cette contrainte, je vais illustrer pour la détection à la compilation de
E T::operator*() const
, pour arbitraireT
etE
. Le même modèle s'appliquera mutatis mutandis à la recherche de toute autre signature de méthode membre.Dans cette solution, la fonction de sonde SFINAE surchargée
test()
est "invoquée de manière récursive". (Bien sûr, il n'est pas du tout invoqué; il a simplement les types de retour des invocations hypothétiques résolues par le compilateur.)Nous devons sonder au moins un et au plus deux points d'information:
T::operator*()
Existe- t- il du tout? Sinon, nous avons terminé.T::operator*()
existe, est sa signatureE T::operator*() const
?Nous obtenons les réponses en évaluant le type de retour d'un seul appel à
test(0,0)
. Cela se fait par:Cet appel peut être résolu en
/* SFINAE operator-exists :) */
surcharge detest()
, ou il peut résoudre en/* SFINAE game over :( */
surcharge. Il ne peut pas résoudre la/* SFINAE operator-has-correct-sig :) */
surcharge, car celui-ci n'attend qu'un seul argument et nous en passons deux.Pourquoi passons-nous deux? Simplement pour forcer la résolution à exclure
/* SFINAE operator-has-correct-sig :) */
. Le deuxième argument n'a pas d'autre signification.Cet appel à
test(0,0)
se résoudra au/* SFINAE operator-exists :) */
cas où le premier argument 0 satisferait le premier type de paramètre de cette surcharge, qui estdecltype(&A::operator*)
, avecA = T
. 0 satisfera ce type juste au cas où ilT::operator*
existe.Supposons que le compilateur dise Oui à cela. Ensuite, il va avec
/* SFINAE operator-exists :) */
et il doit déterminer le type de retour de l'appel de fonction, qui dans ce cas estdecltype(test(&A::operator*))
- le type de retour d'un autre appel àtest()
.Cette fois, nous ne passons qu'un seul argument,
&A::operator*
dont nous savons maintenant qu'il existe, sinon nous ne serions pas là. Un appel àtest(&A::operator*)
peut résoudre soit à, soit à/* SFINAE operator-has-correct-sig :) */
nouveau à pourrait résoudre à/* SFINAE game over :( */
. L'appel correspondra/* SFINAE operator-has-correct-sig :) */
juste au cas où&A::operator*
satisfait le type de paramètre unique de cette surcharge, qui estE (A::*)() const
, avecA = T
.Le compilateur dira Oui ici s'il
T::operator*
a la signature souhaitée, puis à nouveau doit évaluer le type de retour de la surcharge. Plus de "récursions" maintenant: ça l'eststd::true_type
.Si le compilateur ne choisit pas
/* SFINAE operator-exists :) */
pour l'appeltest(0,0)
ou ne choisit pas/* SFINAE operator-has-correct-sig :) */
pour l'appeltest(&A::operator*)
, dans les deux cas, il va avec/* SFINAE game over :( */
et le type de retour final eststd::false_type
.Voici un programme de test qui montre le modèle produisant les réponses attendues dans un échantillon varié de cas (GCC 4.6.3 à nouveau).
Y a-t-il de nouveaux défauts dans cette idée? Peut-il être rendu plus générique sans tomber à nouveau sous le coup de l'accident qu'il évite?
la source
Voici quelques extraits d'utilisation: * Les tripes pour tout cela sont plus bas
Recherchez un membre
x
dans une classe donnée. Peut être var, func, class, union ou enum:Vérifiez la fonction du 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
indépendamment de 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
Cela devrait être suffisant si vous connaissez le nom de la fonction membre que vous attendez. (Dans ce cas, la fonction bla ne parvient pas à instancier s'il n'y a pas de fonction membre (en écrire une qui fonctionne de toute façon est difficile car il y a un manque de spécialisation partielle de fonction. Vous devrez peut-être utiliser des modèles de classe) En outre, la structure enable (qui est similaire à enable_if) pourrait également être basé sur le type de fonction que vous voulez qu'elle ait en tant que membre.
la source
Voici une version plus simple de la réponse de Mike Kinghan. Cela détectera les méthodes héritées. Il vérifiera également la signature exacte (contrairement à l'approche de jrok qui permet les conversions d'arguments).
Exemple exécutable
la source
using
pour apporter des surcharges à partir de la classe de base. Cela fonctionne pour moi sur MSVC 2015 et avec Clang-CL. Cela ne fonctionne pas avec MSVC 2012 cependant.Vous pouvez utiliser std :: is_member_function_pointer
la source
&A::foo
sera- t-il pas une erreur de compilation s'il n'y a pasfoo
du toutA
? J'ai lu la question d'origine comme étant censée fonctionner avec n'importe quelle classe d'entrée, pas seulement celles qui ont une sorte de membre nomméfoo
.Je suis venu avec le même genre de problème moi-même, et j'ai trouvé les solutions proposées ici très intéressantes ... mais avait l'exigence d'une solution qui:
J'ai trouvé un autre fil proposant quelque chose comme ça, basé sur une discussion BOOST . Voici la généralisation de la solution proposée sous forme de déclaration de deux macros pour la classe de traits, suivant le modèle des classes boost :: has_ * .
Ces macros se développent en une classe de traits avec le prototype suivant:
Alors, quelle est l'utilisation typique que l'on peut faire de cela?
la source
Pour ce faire, nous devrons utiliser:
type_traits
tête, nous voudrons retourner untrue_type
oufalse_type
de nos surchargestrue_type
surcharge attendueint
et lafalse_type
surcharge attendue des paramètres Variadic à exploiter: «La priorité la plus basse de la conversion des points de suspension dans la résolution de surcharge»true_type
fonction, nous utiliseronsdeclval
etdecltype
nous permettant de détecter la fonction indépendamment des différences de type de retour ou des surcharges entre les méthodesVous pouvez en voir un exemple en direct ici .Mais je vais également l'expliquer ci-dessous:
Je veux vérifier l'existence d'une fonction nommée
test
qui prend un type convertibleint
, alors j'aurais besoin de déclarer ces deux fonctions:decltype(hasTest<a>(0))::value
istrue
(Notez qu'il n'est pas nécessaire de créer des fonctionnalités spéciales pour gérer lavoid a::test()
surcharge, levoid a::test(int)
est accepté)decltype(hasTest<b>(0))::value
istrue
(Carint
est convertible endouble
int b::test(double)
est accepté, indépendamment du type de retour)decltype(hasTest<c>(0))::value
estfalse
(c
n'a pas de méthode nomméetest
qui accepte un type convertible à partir de là,int
ce n'est pas accepté)Cette solution présente 2 inconvénients:
test()
méthode?Il est donc important que ces fonctions soient déclarées dans un espace de noms de détails, ou idéalement si elles ne doivent être utilisées qu'avec une classe, elles doivent être déclarées en privé par cette classe. À cette fin, j'ai écrit une macro pour vous aider à résumer ces informations:
Vous pouvez utiliser ceci comme:
Appelant par la suite
details::test_int<a>::value
oudetails::test_void<a>::value
produiraittrue
oufalse
aux fins de code en ligne ou de méta-programmation.la source
Pour ne pas être intrusif, vous pouvez également mettre
serialize
dans l'espace de noms de la classe en cours de sérialisation, ou de la classe archive, grâce à la recherche Koenig . Voir Espaces de noms pour les remplacements de fonctions gratuits pour plus de détails. :-)Ouvrir un espace de noms donné pour implémenter une fonction gratuite est tout simplement faux. (par exemple, vous n'êtes pas censé ouvrir un espace
std
de noms pour implémenterswap
pour vos propres types, mais devriez plutôt utiliser la recherche Koenig.)la source
Vous semblez vouloir l'idiome du détecteur. Les réponses ci-dessus sont des variantes qui fonctionnent avec C ++ 11 ou C ++ 14.
La
std::experimental
bibliothèque a des fonctionnalités qui font essentiellement cela. Retravailler un exemple d'en haut, cela pourrait être:Si vous ne pouvez pas utiliser std :: experimental, une version rudimentaire peut être créée comme ceci:
Puisque has_serialize_t est en réalité soit std :: true_type soit std :: false_type, il peut être utilisé via n'importe lequel des idiomes SFINAE courants:
Ou en utilisant la répartition avec résolution de surcharge:
la source
D'accord. Deuxième essai. Ce n'est pas grave si vous n'aimez pas celui-ci non plus, je cherche plus d'idées.
L'article de Herb Sutter parle des traits. Ainsi, vous pouvez avoir une classe de traits dont l'instanciation par défaut a le comportement de secours, et pour chaque classe où votre fonction membre existe, alors la classe de traits est spécialisée pour appeler la fonction membre. Je crois que l'article de Herb mentionne une technique pour faire cela afin qu'il n'implique pas beaucoup de copier-coller.
Comme je l'ai dit, cependant, peut-être que vous ne voulez pas du travail supplémentaire impliqué avec les classes de "marquage" qui implémentent ce membre. Dans ce cas, je cherche une troisième solution ...
la source
Sans le support C ++ 11 (
decltype
), cela pourrait fonctionner:SSCCE
Comment ça marche j'espère
A
,Aa
etB
sont les classes en question,Aa
étant la classe spéciale qui hérite du membre que nous recherchons.Dans le
FooFinder
latrue_type
etfalse_type
sont les remplacements pour le C ++ correspondant 11 classes. Aussi pour la compréhension de la métaprogrammation modèle, ils révèlent la base même de la SFINAE-sizeof-trick.Le
TypeSink
est une structure de modèle qui est utilisée ultérieurement pour intégrer le résultat intégral de l'sizeof
opérateur dans une instanciation de modèle pour former un type.La
match
fonction est un autre type de modèle SFINAE qui n'a pas d'équivalent générique. Il ne peut donc être instancié que si le type de son argument correspond au type pour lequel il était spécialisé.Les deux
test
fonctions ainsi que la déclaration enum forment finalement le modèle SFINAE central. Il y en a un générique utilisant une ellipse qui renvoie lefalse_type
et un homologue avec des arguments plus spécifiques pour avoir la priorité.Pour pouvoir instancier la
test
fonction avec un argument de modèle deT
, lamatch
fonction doit être instanciée, car son type de retour est requis pour instancier l'TypeSink
argument. La mise en garde est que&U::foo
, étant enveloppé dans un argument de fonction, n'est pas référencé à partir d'une spécialisation d'argument de modèle, donc la recherche de membre héritée a toujours lieu.la source
Si vous utilisez la folie de Facebook, leur macro est prête à l'emploi pour vous aider:
Bien que les détails d'implémentation soient les mêmes que ceux de la réponse précédente, utiliser une bibliothèque est plus simple.
la source
J'avais un besoin similaire et suis tombé sur ce SO. Il existe de nombreuses solutions intéressantes / puissantes proposées ici, même si elles sont un peu longues pour juste un besoin spécifique: détecter si une classe a une fonction membre avec une signature précise. J'ai donc fait quelques lectures / tests et suis venu avec ma version qui pourrait être intéressante. Il détecte:
avec une signature précise. Comme je n'ai pas besoin de capturer de signature (qui nécessiterait une solution plus compliquée), celle-ci me convient . Il a essentiellement utilisé enable_if_t .
Production :
la source
En me basant sur la réponse de jrok , j'ai évité d'utiliser des classes et / ou des fonctions de modèle imbriquées.
Nous pouvons utiliser les macros ci-dessus comme ci-dessous:
Les suggestions sont les bienvenues.
la source