34 votes

Comment appeler une fonction modèle 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 utilisant enable_if ferait le travail ici, divisant foo en deux morceaux, mais je n'arrive pas à travailler sur les détails. Quelle est la manière la plus simple d'y parvenir?

33voto

Johannes Schaub - litb Points 256113

Il y a deux recherches qui sont faites pour le nom bar. L'un est l'incompétent de recherche lors de la définition du contexte de l' foo. L'autre argument est dépendante de recherche à chaque instanciation d'un contexte (mais le résultat de la recherche à chaque instanciation d'un contexte n'est pas autorisé à changer de comportement entre les deux instanciation des contextes).

Pour obtenir le comportement désiré, vous pouvez aller et de définir une stratégie de repli en fonction d'un fallback d'espace de noms qui renvoie certains type unique

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

L' bar fonction sera appelée si rien d'autre matchs, car les points de suspension a pire coût de la conversion. Maintenant, comprennent que les candidats dans votre fonction en utilisant la directive de l' fallback, de sorte qu' fallback::bar est inclus en tant que candidat à l'appel à l' bar.

Maintenant, à voir si un appel à l' 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 choisie pourrait être nulle, de sorte que vous avez à faire certains opérateur virgule astuces pour obtenir autour de 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, alors l'opérateur virgule invocation sera de retour d'une référence à l' int. Si non, ou si la fonction sélectionnée est retourné void, puis l'invocation des retours void à son tour. Alors la prochaine invocation avec flag comme deuxième argument sera de retour un type qui a sizeof 1 si notre secours a été sélectionné, et un sizeof plus de 1 (le haut-opérateur virgule sera utilisé car void est dans le mix) si quelque chose d'autre a été sélectionné.

Nous comparons les sizeof et déléguer à une struct.

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 a des points de suspension trop. Mais cela semble être plutôt rare. Test à l'aide de la solution de repli:

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

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

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

Pour tester non qualifiés de recherche lors de la définition du contexte, nous allons définir la fonction suivante au-dessus de foo_impl et foo (mettre le foo_impl modèle ci-dessus foo, de sorte qu'ils ont tous les deux la même définition de contexte)

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

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}

6voto

sbi Points 100828

litb vous a donné une très bonne réponse. Cependant, je me demande si, compte tenu du contexte, nous ne pouvions pas venir avec quelque chose qui est moins générique, mais aussi moins de, messagerie unifiée, élaborer?

Par exemple, quels types peuvent être T? Quoi que ce soit? Quelques types? Très peu de jeu qui vous contrôle? Certaines classes de la conception en conjonction avec la fonction foo? Compte tenu de la dernière, vous pourriez simple de mettre quelque chose comme

typedef boolean<true> has_bar_func;

dans les types et ensuite passer à différents foo surcharges basée sur:

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 - bar/baz de la fonction juste au sujet de toute signature, est-il quelque peu restreint ensemble, ou est-il juste une signature valide? Si ce dernier, litb de l' (excellent) de secours idée, en conjonction avec une méta-fonction employant sizeof peut-être un peu plus simple. Mais ce que je n'ai pas exploré, donc c'est juste une pensée.

2voto

j_random_hacker Points 28473

EDIT: j'ai parlé trop vite!!! litb réponse montre comment cela peut être fait (à l'éventuel coût de votre santé mentale... :-P)

Malheureusement, je pense que le cas général de la vérification "serait-ce de la compilation" est hors de portée de la fonction argument de modèle déduction + SFINAE, ce qui est habituellement le truc pour ce genre de choses. Je pense que le mieux que vous pouvez faire est de créer un "backup" modèle de fonction:

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

Et puis le changement foo() simplement:

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

Cela fonctionne pour la plupart des cas. Parce que l' bar() du modèle type de paramètre est T, il sera considéré comme "moins spécialisées" en comparaison avec toute autre fonction ou la fonction de modèle nommé bar() et va donc céder la priorité à ce pré-existante de la fonction ou de la fonction de modèle lors de la résolution de surcharge. Sauf que:

  • Si la pré-existants bar() est lui-même un modèle de fonction prenant un paramètre de modèle de type T, une ambiguïté à prévoir car ni le modèle de sont plus spécialisés que les autres, et le compilateur va se plaindre.
  • Les conversions implicites aussi de ne pas travailler, et conduira à difficile à diagnostiquer les problèmes: Supposons qu'il existe un pré-existants bar(long) mais foo(123) est appelé. Dans ce cas, le compilateur va choisir tranquillement pour instancier la "sauvegarde" bar() modèle avec T = int au lieu de procéder à l' int->long de la promotion, même si ce dernier aurait dressé et a bien fonctionné!

En bref: il n'y a pas facile, solution complète, et je suis assez sûr il n'y a même difficile-comme-l'enfer, solution complète. :(

2voto

ijprest Points 1629

Si vous souhaitez vous limiter à Visual C ++, vous pouvez utiliser les instructions __if_exists et __if_not_exists .

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

2voto

pgast Points 1240
//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);
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X