Comment vérifier le type d'un paramètre de modèle?

95

Supposons que j'ai une fonction de modèle et deux classes

class animal {
}
class person {
}

template<class T>
void foo() {
  if (T is animal) {
    kill();
  }
}

Comment vérifier si T est un animal? Je ne veux pas avoir quelque chose qui vérifie pendant l'exécution. Merci

WhatABeautifulWorld
la source
58
Je mettrais "pet" au lieu de "kill" :-)
JimBamFeng

Réponses:

132

Utilisez is_same:

#include <type_traits>

template <typename T>
void foo()
{
    if (std::is_same<T, animal>::value) { /* ... */ }  // optimizable...
}

Habituellement, c'est une conception totalement irréalisable, cependant, et vous voulez vraiment vous spécialiser :

template <typename T> void foo() { /* generic implementation  */ }

template <> void foo<animal>()   { /* specific for T = animal */ }

Notez également qu'il est inhabituel d'avoir des modèles de fonctions avec des arguments explicites (non déduits). Ce n'est pas rare, mais il existe souvent de meilleures approches.

Kerrek SB
la source
2
Merci! En fait, ils partagent BEAUCOUP de code, donc je ne peux pas vraiment le dupliquer
WhatABeautifulWorld
3
@WhatABeautifulWorld: Vous pouvez toujours factoriser votre code afin que la partie dépendante du type puisse être reléguée à une fonction spécialisable ...
Kerrek SB
1
Un suivi rapide, si j'utilise std :: is_same, cela ne ralentira PAS le code pour les autres paramètres du modèle, non?
WhatABeautifulWorld
1
@WhatABeautifulWorld: Les valeurs des traits sont toutes statiquement connues. Il ne devrait y avoir aucun coût d'exécution, à condition que votre compilateur soit à moitié décent. Vérifiez l'assemblage en cas de doute.
Kerrek SB
2
@ AdriC.S .: Puisque ce Tn'est pas déduit, vous ne pouvez pas faire grand-chose. Vous pouvez laisser le modèle principal non implémenté et créer une spécialisation, ou vous pouvez ajouter une assertion statique avec is_same.
Kerrek SB
35

Je pense qu'aujourd'hui, il vaut mieux l'utiliser, mais uniquement avec C ++ 17.

#include <type_traits>

template <typename T>
void foo() {
    if constexpr (std::is_same_v<T, animal>) {
        // use type specific operations... 
    } 
}

Si vous utilisez des opérations spécifiques au type dans le corps de l'expression if sans constexpr, ce code ne sera pas compilé.

Константин Гудков
la source
8
au lieu de std::is_same<T, U>::valuevous pourriez utiliser plus court:std::is_same_v<T, U>
Fureeish
9

En C ++ 17, nous pouvons utiliser des variantes .

Pour l'utiliser std::variant, vous devez inclure l'en-tête:

#include <variant>

Après cela, vous pouvez ajouter std::variantvotre code comme ceci:

using Type = std::variant<Animal, Person>;

template <class T>
void foo(Type type) {
    if (std::is_same_v<type, Animal>) {
        // Do stuff...
    } else {
        // Do stuff...
    }
}
Edwin Pratt
la source
8
Comment T et Type sont-ils connectés?
mabraham
4
Cette réponse pose plusieurs problèmes. Outre les erreurs réelles ( typequi est la valeur de type Typeou un modèle qui n'a pas de sens ici) is_same_vne sont pas significatives dans le contexte de variant. Le "trait" correspondant est holds_alternative.
Pixelchemist
std::variantest totalement inutile ici
tjysdsg le
7

Vous pouvez spécialiser vos modèles en fonction de ce qui est passé dans leurs paramètres comme ceci:

template <> void foo<animal> {

}

Notez que cela crée une fonction entièrement nouvelle basée sur le type passé en tant que T. Ceci est généralement préférable car cela réduit l'encombrement et c'est essentiellement la raison pour laquelle nous avons des modèles en premier lieu.

modèle garçon
la source
Hmm. Cette méthode est-elle vraiment le seul moyen préférable de spécialiser l'argument de modèle? Disons que j'ai 10 classes enfants différentes que je dois gérer dans la fonction de modèle. Dois-je vraiment écrire 10 fonctions de modèle différentes pour la classe respective? Je pense que je manque peut-être le point central ici.
Volkan Güven
Cela semble vraiment être une bonne idée, si quelqu'un ne veut pas utiliser type_traits. Comme quelqu'un l'a mentionné, la logique principale peut être effectuée dans une fonction différente, qui accepte un drapeau supplémentaire pour indiquer le type, et cette déclaration spécialisée peut simplement définir le drapeau en conséquence et transmettre directement tous les autres arguments sans rien toucher. Donc, si 10 classes différentes doivent être gérées, c'est fondamentalement 10 lignes pour 10 définitions de fonctions différentes. Mais cela deviendra très compliqué s'il y a plus d'une variable de modèle.
Harish Ganesan