J'essaye de comprendre pourquoi ce code ne compile pas:
std::enable_if<std::is_void<T>::value>
J'obtiens une erreur de compilation: "aucun type nommé type".
Même erreur même si je modifie la version A du code avec
// test.h struct Base { virtual ~Base{}; virtual void execute() {} virtual void execute(int) {} virtual void execute(double) {} } template<class T> struct Test : Base { void execute(typename std::enable_if<std::is_void<T>::value, void>::type) { // Do A } void execute(typename std::enable_if<!std::is_void<T>::value, int>::type t) { // Do B } }; // main.cpp Test<void> t;
Le but est de créer une classe qui en fonction du paramètre T crée une fonction membres différents. Dans ce cas 2, mais je serais également intéressé par plus.
[Modifier] J'ai ajouté la partie héritage dont je parlais dans les commentaires.
4 Réponses :
Remarque: cette réponse est précieuse pour une précédente modification de la question. La récente modification a radicalement changé la question et cette réponse n'est plus adéquate.
Puisque execute
n'est pas une fonction de modèle, il ne peut y avoir d'involevd SFINAE. En effet, chaque fois que Test
est instancié, les deux versions de execute
le sont, ce qui conduit à une erreur qui n'est pas un échec de déduction de modèle.
Vous avez besoin d'un template de fonction (appelons le paramètre template U
) pour bénéficier de SFINAE; et comme vous devez utiliser le même argument de modèle de type que Test
( T
), vous pouvez fournir un argument par défaut U = T
):
Solution :
template<class T> struct Test { template<class U = T> std::enable_if_t<std::is_void_v<U>> execute() { std::cout << "is_void\n"; } template<class U = T> std::enable_if_t<!std::is_void_v<U>> execute() { std::cout << "!is_void\n"; } };
Merci pour votre retour. Est-ce la seule façon de le faire fonctionner? Supposons que la classe Test hérite de TestBase et que execute () est virtuel, y aurait-il des problèmes avec cela?
@svoltron Oui. La fonction virtuelle existe déjà et sfinae intervient lorsqu'un modèle est instancié. Ce que vous pouvez faire est de fournir des modèles de fonction sfinae appelant la fonction virtuelle. Mais cela sort du cadre de cette question. Vous pouvez en demander un autre.
Mmm j'essaierai d'y réfléchir et si je ne l'obtiens pas je poserai une autre question alors, merci
@svoltron De rien. Remarque: il est prévu que vous acceptiez une réponse en cliquant sur la coche sous le score de la réponse;)
@Oliv True - mais les fonctions virtuelles ont été ajoutées à la question après cette réponse.
La question a été modifiée pour que execute
soit désormais des fonctions virtuelles. Cette solution ne fonctionne plus car un membre de fonction de modèle ne peut pas être un substitut.
mon erreur, désolé pour ça
Oui, merci @Oliv. C'est un vote contre ma question. OP aurait dû poser une autre question à mon avis. Ce genre de modification invalide le travail effectué pour la première version.
Lorsque vous instanciez Test
, vous avez également instancié les déclarations de toutes ses fonctions membres. C'est juste une instanciation de base. Quelles déclarations cela vous donne-t-il? Quelque chose comme ceci:
template<typename T> struct TestBase : Base { void execute(T t) override { } }; template<> struct TestBase<void> : Base { void execute() override { } };
Si vous vous attendiez à ce que SFINAE supprime silencieusement la surcharge mal formée, vous devez vous rappeler que le S signifie «substitution». La substitution d'arguments de modèle dans les paramètres d'un modèle de fonction (membre) . Ni execute
n'est un modèle de fonction membre. Ce sont tous les deux des fonctions membres régulières d'une spécialisation de modèle.
Vous pouvez résoudre ce problème de plusieurs manières. Une façon serait de créer ces deux modèles, de faire SFINAE correctement et de laisser la résolution de surcharge vous emmener à partir de là. @YSC vous montre déjà comment faire.
Une autre façon est d'utiliser un modèle d'aide. De cette façon, vous obtenez votre objectif initial, pour qu'une seule fonction membre existe à tout moment.
template<typename T> struct TestBase { void execute(T t) { } }; template<> struct TestBase<void> { void execute() { } }; template<class T> struct Test : private TestBase<T> { using TestBase<T>::execute; };
Vous pouvez choisir celle qui correspond le mieux à vos besoins.
Pour répondre à votre modification. Je pense que la deuxième approche répond mieux à vos besoins.
void execute(void); void execute(<ill-formed> t);
TestBase
est l’intermédiaire qui accomplit ce que vous semblez vouloir rechercher. P >
Merci pour votre avis. Solution intéressante en effet. Le seul problème avec ceci est que s'il y a d'autres membres de fonction en plus de execute (), je dois les écrire à nouveau pour la spécialisation. Cependant, peut-être que cette approche fonctionne pour le problème d'héritage que j'avais en tête: faire de Test :: execute le remplacement d'une fonction virtuelle TestBase :: execute. Dans l'autre sens, il semble que le mécanisme virtuel ne fonctionne pas
@svoltron - Je ne connais pas tout votre problème, mais je ne vois pas pourquoi vous devez ajouter des fonctions virtuelles au mix. TestBase
peut également contenir des membres de données pour accomplir la tâche execute
est censée faire. Si vous le faites virtaul, vous revenez également à votre problème d'origine. Vous devez déterminer quel exécuter
se trouve dans TestBase
afin de le remplacer!
Comme je l'ai écrit dans le commentaire de la réponse @Angew, je vais injecter la classe de base d'une certaine manière, il y a un sens, croyez-moi ou supposez qu'il y en a. Héritant directement de ce type de classe de base et en utilisant l'approche Angew / YSC, l'appel virtuel n'est pas exécuté. Au lieu de cela, l'appel de la fonction de classe de base est.
@svoltron - Euh. Je ne suis pas sûr d'avoir tout à fait compris votre approche dans le commentaire. Je pense que vous devriez suivre les conseils de YSC. Si vous posez une autre question (avec un peu de chance, une sur le sujet de Stack Overflow ), vous serez en mesure d’en expliquer davantage.
bien sûr, merci quand même. J'ai édité la question, mais j'en poserai une autre si c'est possible
Bien sûr que ça aide, je n'ai pas encore essayé mais je te fais confiance ehhe. Vraiment intéressant. J'espérais que c'était possible avec enable_if mais cela ne semble pas. Merci beaucoup
Vous pouvez encapsuler les différentes surcharges de execute
dans un ensemble de classes d'assistance associées, quelque chose comme ceci:
template <class T, class Self> struct TestHelper : Base { void execute(int) override { Self& self = static_cast<Self&>(*this); // Now access all members through `self.` instead of directly } }; template <class Self> struct TestHelper<void, self> : Base { void execute() override { Self& self = static_cast<Self&>(*this); // Now access all members through `self.` instead of directly } }; template <class T> struct Test : TestHelper<T, Test> { // Other Test stuff here };
Si les implémentations de execute dépendent en fait des "autres éléments de test" qui devraient être partagés entre eux, vous pouvez également utiliser CRTP :
template <class T> struct TestHelper : Base { void execute(int) override {} }; template <> struct TestHelper<void> : Base { void execute() override {} }; template <class T> struct Test : TestHelper<T> { // Other Test stuff here };
Merci encore pour les commentaires. Cela semble similaire à celui de @StoryTeller, mais inclut également la partie injection CRTP. Donc cela semble être le seul moyen
@svoltron J'espère que cela aide. Mais essayez d'éviter les problèmes XY dans les prochaines questions, ils sont très frustrants répondre.
Il existe également une autre option, moins élégante que celle utilisant CRTP. Il consiste à choisir dans le corps du overrider où il transmet à l'implémentation de base ou fournit une nouvelle implémentation de la fonction.
Si vous utilisiez c ++ 17 cela pourrait être simple grâce à if constexpr code>. En C ++ 11, l'alternative est d'utiliser la répartition des balises:
template<class T> struct Test : Base { void execute(){ if constexpr (is_void_v<T>){ Base::execute(); } else{ /* implementation */ } } void execute(int t){ if constexpr (!is_void_v<T>){ Base::execute(t); } else{ /* implementation */ } } };
Avec C ++ 17 if constexpr
cela pourrait sembler plus élégant que la solution CRTP:
template<class T> struct Test : Base { void execute() { void do_execute(std::integral_constant<bool,std::is_void<T>::value>{}); } void execute(int t) { void do_execute(std::integral_constant<bool,!std::is_void<T>::value>{}, t); } private: void do_execute(std::integral_constant<bool,true>){ /*implementation*/ } void do_execute(std::integral_constant<bool,false>){ Base::execute();//Call directly the base function execute. //Such call does not involve the devirtualization //process. } void do_execute(std::integral_constant<bool,true>,int t){ /*implementation*/ } void do_execute(std::integral_constant<bool,false>,int t){ Base::execute(t);//Call directly the base function execute. //Such call does not involve the devirtualization //process. } };
J'utilise C ++ 11 et probablement le CRTP est plus élégant que vous le dites. Avec C ++ 17, c'est bien mieux
Copie possible de Sélection d'une fonction membre en utilisant différentes conditions enable_if a >
question modifiée @DanielLangr
Veuillez ne pas modifier une question de manière à rendre les réponses existantes incorrectes ou incomplètes. Pour des détails supplémentaires comme celui-ci, ouvrez une deuxième question de suivi, qui peut être liée à la première. (Mais laissez celui-ci tel quel, maintenant qu'il existe des réponses concernant les deux versions.)
Ok, c'est tout à fait logique. Merci pour la suggestion
Difficile de choisir la meilleure réponse, merci à tous et désolé pour la modification