Utilisation des valeurs d'énumération en combinaison avec SFINAE
Je connais déjà SFINAE et comment il peut être utilisé pour activer des modèles spécifiques basés sur le type passé (en utilisant std :: enable_if). Cependant, j'ai récemment commencé à travailler sur un projet dans lequel j'aimerais faire ce qui suit: créer une spécialisation de classe basée sur l'énumération VALUE fournie tout en utilisant SFINAE. Maintenant, je sais qu'il est possible de faire des spécialisations basées sur une valeur enum étant donné que j'ai déjà fait cela (comme ça):
enum Specifier
{
One,
Two,
Three
}
template <Specifier>
class Foo
{
public:
void Bar();
}
template<>
void Foo<Specifier::One>::Bar()
{
}
Cependant, j'aimerais maintenant utiliser SFINAE pour utiliser une spécialisation spécifique de Bar()
pour plusieurs valeurs d'énumération. Quelque chose comme ça:
template <Specifier Type>
class Foo
{
public:
template <typename std::enable_if<Type == Specifier::Two || Type == Specifier::One, void>::type>
void Bar();
template <typename std::enable_if<Type == Specifier::Three, void>::type>
void Bar();
}
Une idée si c'est possible, et si oui, comment pourrais-je procéder?
Réponses
C ++ 17: constexpr si
À partir de C ++ 17 et au-delà, vous pouvez utiliser une seule surcharge de fonction membre (au lieu de plusieurs surcharges présentes ou non présentes via SFINAE) dont le corps exploite constexpr si:
#include <iostream>
enum class Specifier { One, Two, Three };
template <Specifier S> class Foo {
public:
static constexpr int bar() {
if constexpr ((S == Specifier::One) || (S == Specifier::Two)) {
return 12;
} else if constexpr (S == Specifier::Three) {
return 3;
}
}
};
int main() {
std::cout << Foo<Specifier::One>::bar() << "\n" // 12
<< Foo<Specifier::Two>::bar() << "\n" // 12
<< Foo<Specifier::Three>::bar(); // 3
}
C ++ 11: SFINAE et std::enable_if
( _t
) (C ++ 14)
Vous pouvez également utiliser SFINAE avec l'exigence que vos fonctions membres non-modèles doivent être des modèles de fonctions membres avec un paramètre de modèle factice, car SFINAE doit être appliqué à un nom dépendant dans chaque déclaration de fonction, et un modèle de classe (type ou non-type) n'est naturellement pas un nom dépendant dans la déclaration d'une fonction membre non-modèle :
template <Specifier S> class Foo {
public:
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::One) || (S_ == Specifier::Two)>
* = nullptr>
static constexpr int bar() {
return 12;
}
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::Three)> * = nullptr>
static constexpr int bar() {
return 3;
}
};
Notez que l'exemple ci-dessus utilise le modèle d'alias d'assistance std::enable_if_t
qui a été introduit dans C ++ 14. Si vous utilisez C ++ 11, vous devrez utiliser à la typename std::enable_if<..>::type
place.
De plus, notez que comme nous devons templetize les fonctions membres, un utilisateur abusif pourrait choisir de remplacer l'argument de modèle par défaut pour le paramètre de modèle (factice) non-type S_
:
Foo<Specifier::One>::bar<Specifier::Three>(); // 3
Nous pouvons donc souhaiter ajouter une condition AND supplémentaire au std::enable_if_t
prédicat pour chaque surcharge, à savoir (S_ == S) && (... predicate as above)
. Comme nous le verrons dans la section qui suit, ce n'est plus un problème en C ++ 20, car nous pouvons éviter de transformer des fonctions membres non-modèles en modèles uniquement pour appliquer SFINAE.
Alternative utilisant des spécialisations plutôt que la surcharge
Comme je l'ai également montré dans la réponse suivante à une question de suivi à cette question, vous pouvez également appliquer SFINAE dans la liste d'arguments de modèle (au modèle de classe étant partiellement spécialisé) d'une spécialisation:
template <Specifier, typename = void> struct Foo {
static constexpr int bar() { return 1; } // default
};
template <Specifier S>
struct Foo<S,
std::enable_if_t<(S == Specifier::One) || (S == Specifier::Two)>> {
static constexpr int bar() { return 12; }
};
C ++ 20: les fonctions membres non-template des modèles de classe peuvent utiliser la clause requires : s
À partir de C ++ 20, vous pouvez surcharger et contraindre une fonction membre non-modèle d'un modèle de classe à l'aide d'une clause requiert de fin avec des contraintes exclusives mutuelles pour chaque surcharge:
template <Specifier S> class Foo {
public:
static constexpr int bar() requires((S == Specifier::One) ||
(S == Specifier::Two)) {
return 12;
}
static constexpr int bar() requires(S == Specifier::Three) { return 3; }
};