Comment appeler une fonction template si elle existe, et autre chose autrement?

Je veux faire quelque chose comme

template <typename T>
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}

Je pensais que quelque chose en utilisant enable_if ferait le travail ici, en divisant foo en deux morceaux, mais je n'arrive pas à comprendre les détails. Quel est le moyen le plus simple d'y parvenir?

32
demandé sur Jesse Beder 2009-09-06 21:23:16

7 réponses

Deux recherches sont effectuées pour le nom bar. L'un est la recherche non qualifiée dans le contexte de définition de foo. L'autre est la recherche dépendante de l'argument à chaque contexte d'instanciation (mais le résultat de la recherche à chaque contexte d'instanciation n'est pas autorisé à changer de comportement entre deux contextes d'instanciation différents).

Pour obtenir le comportement souhaité, vous pouvez définir une fonction de secours dans un espace de noms fallback qui renvoie un type

namespace fallback {
  // sizeof > 1
  struct flag { char c[2]; };
  flag bar(...);
}

La fonction bar sera appelée si rien d'autre ne correspond car les points de suspension ont le plus mauvais coût de conversion. Maintenant, incluez ces candidats dans votre fonction par une directive using de fallback, de sorte que fallback::bar est inclus en tant que candidat dans l'appel à bar.

Maintenant, pour voir si l'appel de bar résout à votre fonction, vous l'appellerez, et de vérifier si le type de retour est flag. Le type de retour d'une fonction autrement choisie pourrait être void, donc vous devez faire quelques astuces d'opérateur de virgule pour contourner cela.

namespace fallback {
  int operator,(flag, flag);

  // map everything else to void
  template<typename T> 
  void operator,(flag, T const&);

  // sizeof 1
  char operator,(int, flag);
}

Si notre fonction a été sélectionnée, l'appel de l'opérateur virgule retournera une référence à int. Si ce n'est pas le cas ou si la fonction sélectionnée renvoie void, l'appel renvoie void à son tour. Ensuite, l'invocation suivante avec flag comme deuxième argument retournera un type qui a sizeof 1 si notre repli a été sélectionné, et un sizeof plus grand 1 (l'opérateur virgule intégré sera utilisé car void est dans le mix) si quelque chose d'autre a été sélectionné.

Nous comparons le sizeof et le delegate à une structure.

template<bool>
struct foo_impl;

/* bar available */
template<>
struct foo_impl<true> {
  template<typename T>
  static void foo(T const &t) {
    bar(t);
  }
};

/* bar not available */
template<>
struct foo_impl<false> {
  template<typename T>
  static void foo(T const&) {
    std::cout << "not available, calling baz...";
  }
};

template <typename T>
void foo(const T& t) {
   using namespace fallback;

   foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
     ::foo(t);
}

Cette solution est ambiguë si la fonction existante a aussi une ellipse. Mais cela semble être plutôt rare. Test en utilisant le repli:

struct C { };
int main() {
  // => "not available, calling baz..."
  foo(C());
}

Et si un candidat est trouvé en utilisant la recherche dépendante de l'argument

struct C { };
void bar(C) {
  std::cout << "called!";
}
int main() {
  // => "called!"
  foo(C());
}

Pour tester la recherche non qualifiée au contexte de définition, définissons la fonction suivante au-dessus de foo_impl et foo (Placez le modèle foo_impl au-dessus de foo, de sorte qu'ils ont les deux le même contexte de définition)

void bar(double d) {
  std::cout << "bar(double) called!";
}

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}
31
répondu Johannes Schaub - litb 2009-09-07 15:24:46

Litb vous a donné une très bonne réponse. Cependant, je me demande si, étant donné plus de contexte, nous ne pourrions pas trouver quelque chose de moins générique, mais aussi moins, euh, élaboré ?

Par exemple, quels types peuvent être T? Quoi que ce soit? Quelques types? Un ensemble très restreint sur lequel vous avez le contrôle? Certaines classes que vous concevez en conjonction avec la fonction foo? Compte tenu de ce dernier, vous pouvez simplement mettre quelque chose comme

typedef boolean<true> has_bar_func;

Dans les types, puis passez à différentes surcharges foo basées sur cela:

template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);

template <typename T>
void foo(const T& t) {
  foo_impl( t, typename T::has_bar_func() );
}

Aussi, peut le bar/baz la fonction a à peu près n'importe quelle signature, y a-t-il un ensemble quelque peu restreint, ou y a-t-il juste une signature valide? Si ce dernier, l'idée de repli (excellente) de litb, en conjonction avec une méta-fonction utilisant sizeof pourrait être un peu plus simple. Mais ce que je n'ai pas exploré, donc c'est juste une pensée.

6
répondu sbi 2017-05-23 10:32:29

Je pense que la solution de litb fonctionne, mais est trop complexe. La raison en est qu'il introduit une fonction fallback::bar(...) qui agit comme une "fonction de dernier recours", puis s'efforce de ne pas l'appeler. Pourquoi? Il semble que nous ayons un comportement parfait pour cela:

namespace fallback {
    template<typename T>
    inline void bar(T const& t, ...)
    {
        baz(t);
    }
}
template<typename T>
void foo(T const& t)
{
    using namespace fallback;
    bar(t);
}

Mais comme je l'ai indiqué dans un commentaire au post original de litb, il y a plusieurs raisons pour lesquelles bar(t) pourrait échouer à compiler, et je ne suis pas certain que cette solution gère les mêmes cas. Il va certainement échouer sur un private bar::bar(T t)

3
répondu MSalters 2009-09-07 10:48:58

Si vous êtes prêt à vous limiter à Visual C++, vous pouvez utiliser le ___si il existe et ___si non_existe consolidés.

Pratique dans un pincement, mais spécifique à la plate-forme.

2
répondu ijprest 2009-09-06 19:13:36

EDIT: j'ai parlé trop tôt! la réponse de litb montre comment cela peut réellement être fait (au coût possible de votre santé mentale... :- P)

Malheureusement, je pense que le cas général de la vérification "cette compilation" est hors de portée de déduction de l'argument du modèle de fonction + SFINAE, qui est l'astuce habituelle pour ce genre de choses. Je pense que le mieux que vous pouvez faire est de créer un modèle de fonction "sauvegarde":

template <typename T>
void bar(T t) {   // "Backup" bar() template
    baz(t);
}

Puis changez foo() pour tout simplement:

template <typename T>
void foo(const T& t) {
    bar(t);
}

Cela fonctionnera dans la plupart des cas. Étant donné que le type de paramètre du modèle bar() est T, il sera considéré comme "moins spécialisé" par rapport à toute autre fonction ou modèle de fonction nommé bar() et cédera donc la priorité à cette fonction ou modèle de fonction préexistant lors de la résolution de surcharge. Sauf que:

  • si le bar() préexistant est lui-même un modèle de fonction prenant un paramètre de modèle de type T, une ambiguïté surgira car aucun modèle n'est plus spécialisé que l'autre, et le compilateur se plaindra.
  • les conversions implicites ne fonctionneront pas non plus, et conduiront à des problèmes difficiles à diagnostiquer: supposons qu'il y ait un bar(long) préexistant mais que foo(123) soit appelé. Dans ce cas, le compilateur choisira tranquillement d'instancier le modèle "backup" bar() avec T = int au lieu d'effectuer la promotion int->long, Même si cette dernière aurait compilé et fonctionné correctement!

En bref: il n'y a pas facile, solution complète, et je suis sûr qu'il n'y a même pas une solution complète et délicate. :(

2
répondu j_random_hacker 2017-05-23 10:27:35
//default

////////////////////////////////////////// 
    template <class T>
    void foo(const T& t){
        baz(t);
    }

//specializations
//////////////////////////////////////////  

    template <>
    void foo(const specialization_1& t){
        bar(t);
    }
    ....
    template <>
    void foo(const specialization_n& t){
        bar(t);
    }
2
répondu pgast 2009-09-06 21:07:04

N'êtes-vous pas capable d'utiliser une spécialisation complète ici (ou une surcharge) sur foo. Par exemple avoir la barre d'appel de modèle de fonction mais pour certains types se spécialiser complètement pour appeler baz?

0
répondu 2009-09-06 17:32:03