Partager via


La classe enable_if

Crée une instance d’un type pour une résolution de surcharge SFINAE sous certaines conditions. Le typedef enable_if<Condition,Type>::type imbriqué existe et est synonyme de Type si et seulement si Condition a la valeur true.

Syntaxe

template <bool B, class T = void>
struct enable_if;

Paramètres

B
Valeur qui détermine l'existence du type obtenu.

T
Type à instancier si B c’est true.

Notes

Si B c’est truele cas, enable_if<B, T> a un typedef imbriqué nommé « type » synonyme de T.

Si B c’est falsele cas, enable_if<B, T> n’a pas de typedef imbriqué nommé « type ».

Le modèle d'alias suivant est fourni :

template <bool B, class T = void>
using enable_if_t = typename enable_if<B,T>::type;

En C++, l’échec de substitution des paramètres de modèle n’est pas une erreur en soi, ce qui est appelé SFINAE (échec de substitution n’est pas une erreur). En règle générale, enable_if permet de supprimer des candidats de la résolution de surcharge (autrement dit, elle trie l'ensemble des surcharges) pour qu'une définition puisse être rejetée en faveur d'une autre. Cette opération est conforme au comportement SFINAE. Pour plus d’informations sur SFINAE, voir Échec de substitution n’est pas une erreur sur Wikipédia.

Voici quatre exemples de scénarios :

  • Scénario 1 : Encapsulation du type de retour d'une fonction :
    template <your_stuff>
typename enable_if<your_condition, your_return_type>::type
    yourfunction(args) {// ...
}
// The alias template makes it more concise:
    template <your_stuff>
enable_if_t<your_condition, your_return_type>
yourfunction(args) {// ...
}
  • Scénario 2 : Ajout d'un paramètre de fonction qui a un argument par défaut :
    template <your_stuff>
your_return_type_if_present
    yourfunction(args, enable_if_t<your condition, FOO> = BAR) {// ...
}
  • Scénario 3 : Ajout d'un paramètre de modèle qui a un argument par défaut :
    template <your_stuff, typename Dummy = enable_if_t<your_condition>>
rest_of_function_declaration_goes_here
  • Scénario 4 : Si votre fonction a un argument non basé sur un modèle, vous pouvez encapsuler son type :
    template <typename T>
void your_function(const T& t,
    enable_if_t<is_something<T>::value, const string&>
s) {// ...
}

Le scénario 1 ne fonctionne pas avec des constructeurs ni des opérateurs de conversion, car ils n'ont pas de types de retour.

Le scénario 2 laisse le paramètre sans nom. Vous pouvez dire ::type Dummy = BAR, mais le nom Dummy n'est pas pertinent et l'affectation d'un nom risque de déclencher un avertissement de type « paramètre non référencé ». Vous devez choisir un type de paramètre de fonction FOO et un argument par défaut BAR. Vous pouvez dire int et 0, mais les utilisateurs de votre code peuvent alors par inadvertance passer à la fonction un entier supplémentaire qui serait ignoré. Nous vous conseillons plutôt d'utiliser void ** et 0 ou nullptr car presque rien n'est convertible en void **:

template <your_stuff>
your_return_type_if_present
yourfunction(args, typename enable_if<your_condition, void **>::type = nullptr) {// ...
}

Le scénario 2 fonctionne également pour les constructeurs ordinaires. Toutefois, il ne fonctionne pas pour les opérateurs de conversion, car ils ne peuvent pas accepter de paramètres supplémentaires. Il ne fonctionne pas non plus pour variadic les constructeurs, car l’ajout de paramètres supplémentaires rend le pack de paramètres de fonction un contexte non déduit et défait ainsi l’objectif de enable_if.

Le scénario 3 utilise le nom Dummy, mais il est facultatif. « typename = typename » seul fonctionne, mais si cela vous semble bizarre, vous pouvez utiliser un nom « factice » : simplement, n’utilisez pas de nom qui pourrait aussi être utilisé dans la définition de fonction. Si vous n'affectez pas de type à enable_if, la valeur par défaut est void et cela est parfaitement judicieux car vous ne vous souciez pas de Dummy. Cela fonctionne pour tout, y compris les opérateurs de conversion et variadic les constructeurs.

Le scénario 4 fonctionne dans les constructeurs sans type de retour et résout ainsi la limite d’encapsulation du scénario 1. Toutefois, le scénario 4 est limité à des arguments de fonction non basés sur un modèle, qui ne sont pas toujours disponibles. (L'utilisation du scénario 4 sur un argument de fonction basé sur un modèle empêche le fonctionnement de la déduction de l'argument de modèle.)

enable_if est puissante, mais aussi dangereuse en cas d'utilisation inappropriée. Comme son objectif est de faire disparaître les candidats avant la résolution de surcharge, ses effets peuvent être très déroutants si elle est mal utilisée. Voici quelques recommandations :

  • N’utilisez enable_if pas pour sélectionner entre les implémentations au moment de la compilation. N'écrivez jamais une classe enable_if pour CONDITION et une autre pour !CONDITION. Utilisez plutôt un modèle de répartition d’étiquette, par exemple, un algorithme qui sélectionne des implémentations en fonction des points forts des itérateurs donnés.

  • N’utilisez enable_if pas pour appliquer les exigences. Si vous souhaitez valider les paramètres du modèle et si la validation échoue, provoquez une erreur au lieu de sélectionner une autre implémentation, utilisez static_assert.

  • Utilisez enable_if quand un ensemble de surcharges rend ambigu un code par ailleurs pertinent. Cela se produit le plus souvent dans des constructeurs de conversion implicite.

Exemple

Cet exemple explique comment la fonction std::make_pair() de modèle de bibliothèque standard C++ tire parti de enable_if.

void func(const pair<int, int>&);

void func(const pair<string, string>&);

func(make_pair("foo", "bar"));

Dans cet exemple, make_pair("foo", "bar") retourne pair<const char *, const char *>. La résolution de surcharge doit déterminer l'élément func() voulu. pair<A, B> a un constructeur de conversion implicite de pair<X, Y>. Cela n'est pas nouveau et figurait déjà dans C++98. Toutefois, dans C++98/03, la signature du constructeur de conversion implicite existe toujours, même s'il s'agit de pair<int, int>(const pair<const char *, const char *>&). La résolution de surcharge ne se soucie pas de savoir si une tentative d'instanciation de ce constructeur explose littéralement, car const char * n'est pas implicitement convertible en int ; elle examine uniquement les signatures avant que les définitions de fonctions ne soient instanciées. Ainsi, l'exemple de code est ambigu, car des signatures existent pour convertir pair<const char *, const char *> à la fois en pair<int, int> et pair<string, string>.

C++11 a résolu cette ambiguïté en utilisant enable_if pour garantir que pair<A, B>(const pair<X, Y>&) existe uniquement quand const X& est implicitement convertible en A et que const Y& est implicitement convertible en B. Cela permet à la résolution de surcharge de déterminer qui pair<const char *, const char *> n’est pas convertible en pair<int, int> et que la surcharge qui prend pair<string, string> est viable.

Spécifications

En-tête : <type_traits>

Espace de noms : std

Voir aussi

<type_traits>