4
votes

enable_if dans les membres de fonction pour void et héritage

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 :


4
votes

8 commentaires

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.



4
votes

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 >


6 commentaires

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



1
votes

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
};


2 commentaires

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.



2
votes

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.
       }
  };


1 commentaires

J'utilise C ++ 11 et probablement le CRTP est plus élégant que vous le dites. Avec C ++ 17, c'est bien mieux