Scott Meyers a publié le contenu et le statut de son prochain livre EC ++ 11. Il a écrit qu'un élément du livre pourrait être "Eviter std::enable_if
les signatures de fonction" .
std::enable_if
peut être utilisé comme argument de fonction, comme type de retour ou comme modèle de classe ou paramètre de modèle de fonction pour supprimer conditionnellement des fonctions ou des classes de la résolution de surcharge.
Dans cette question, les trois solutions sont présentées.
En tant que paramètre de fonction:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, int>::value >::type* = 0) { return 42; }
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};
En tant que paramètre de modèle:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if<
std::is_same<U, int>::value, int>::type = 0>
U read() { return 42; }
template<typename U = T, typename std::enable_if<
std::is_same<U, double>::value, int>::type = 0>
U read() { return 3.14; }
};
Comme type de retour:
template<typename T>
struct Check3
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
return 42;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
return 3.14;
}
};
- Quelle solution privilégier et pourquoi éviter les autres?
- Dans quels cas "Eviter
std::enable_if
dans les signatures de fonction" concerne l'utilisation comme type de retour (qui ne fait pas partie de la signature de fonction normale mais des spécialisations de modèle)? - Existe-t-il des différences entre les modèles de fonctions membres et non membres?
std::enable_if
encombrer mes signatures de fonction (en particulier la version laide de l'nullptr
argument de fonction supplémentaire ) parce que cela ressemble toujours à ce que c'est, un étrange hack (pour quelque chose quistatic if
pourrait faire beaucoup plus beau et propre) en utilisant un modèle de magie noire pour exploiter une fonctionnalité de langage intéressante. C'est pourquoi je préfère l'envoi de balises chaque fois que possible (enfin, vous avez encore des arguments étranges supplémentaires, mais pas dans l'interface publique et aussi beaucoup moins moche et cryptique ).=0
danstypename std::enable_if<std::is_same<U, int>::value, int>::type = 0
accomplir? Je n'ai pas trouvé de ressources correctes pour le comprendre. Je sais que la première partie avant=0
a un type de membreint
siU
etint
est le même. Merci beaucoup!Réponses:
Mettez le hack dans les paramètres du modèle .
L'
enable_if
approche des paramètres sur modèle présente au moins deux avantages par rapport aux autres:lisibilité : l'utilisation de enable_if et les types de retour / d'argument ne sont pas fusionnés en un seul morceau désordonné de désambiguïsateurs de nom de type et d'accès de type imbriqués; même si l'encombrement du désambiguïsateur et du type imbriqué peut être atténué avec des modèles d'alias, cela fusionnerait toujours deux éléments non liés ensemble. L'utilisation de enable_if est liée aux paramètres du modèle et non aux types de retour. Les avoir dans les paramètres du modèle signifie qu'ils sont plus proches de ce qui compte;
applicabilité universelle : les constructeurs n'ont pas de types de retour, et certains opérateurs ne peuvent pas avoir d'arguments supplémentaires, donc aucune des deux autres options ne peut être appliquée partout. Mettre enable_if dans un paramètre de modèle fonctionne partout car vous ne pouvez de toute façon utiliser SFINAE que sur des modèles.
Pour moi, l'aspect lisibilité est le grand facteur de motivation dans ce choix.
la source
FUNCTION_REQUIRES
macro ici la rend beaucoup plus agréable à lire, et elle fonctionne également dans les compilateurs C ++ 03, et elle repose sur l'utilisationenable_if
dans le type de retour. De plus, l'utilisationenable_if
de paramètres de modèle de fonction entraîne des problèmes de surcharge, car désormais la signature de la fonction n'est plus unique, ce qui entraîne des erreurs de surcharge ambiguës.enable_if
avec un paramètre de modèle non type par défaut, ce qui permet la surcharge. Ieenable_if_t<condition, int> = 0
au lieu detypename = enable_if_t<condition>
.flamingdangerzone
lien dans votre commentaire semble conduire à une page d'installation de logiciels espions maintenant. Je l'ai signalé à l'attention du modérateur.std::enable_if
repose sur le principe « L'échec de la sous-station n'est pas une erreur » (alias SFINAE) lors de la déduction des arguments de modèle . Il s'agit d'une fonctionnalité de langage très fragile et vous devez faire très attention pour bien faire les choses.enable_if
contient un modèle imbriqué ou une définition de type (indice: recherchez des::
jetons), alors la résolution de ces tempatles ou types imbriqués est généralement un contexte non déduit . Tout échec de substitution sur un tel contexte non déduit est une erreur .enable_if
surcharges ne peuvent pas avoir de chevauchement car la résolution de surcharge serait ambiguë. C'est quelque chose que vous devez vérifier vous-même en tant qu'auteur, même si vous obtenez de bons avertissements du compilateur.enable_if
manipule l'ensemble des fonctions viables pendant la résolution de surcharge qui peuvent avoir des interactions surprenantes en fonction de la présence d'autres fonctions qui sont importées d'autres portées (par exemple via ADL). Cela le rend peu robuste.En bref, quand cela fonctionne, cela fonctionne, mais quand ce n'est pas le cas, cela peut être très difficile à déboguer. Une très bonne alternative est d'utiliser la distribution de balises , c'est-à-dire de déléguer à une fonction d'implémentation (généralement dans un
detail
espace de noms ou dans une classe d'assistance) qui reçoit un argument factice basé sur la même condition de compilation que vous utilisez dans leenable_if
.La distribution de balises ne manipule pas l'ensemble de surcharge, mais vous aide à sélectionner exactement la fonction que vous voulez en fournissant les arguments appropriés via une expression à la compilation (par exemple dans un trait de type). D'après mon expérience, c'est beaucoup plus facile à déboguer et à faire correctement. Si vous êtes un rédacteur de bibliothèque en herbe de traits de type sophistiqués, vous en aurez peut-être besoin
enable_if
, mais pour la plupart des utilisations régulières des conditions de compilation, ce n'est pas recommandé.la source
enable_if
le faire correctement?is_f_able
est quelque chose que je considère comme une tâche pour les rédacteurs de bibliothèques qui peuvent bien sûr utiliser SFINAE quand cela leur donne un avantage, mais pour les utilisateurs "réguliers" et étant donné un traitis_f_able
, je pense que l'envoi de balises est plus facile.Le paramètre de modèle
Il peut facilement être mal utilisé et générer des erreurs avec des surcharges:
Avis
typename = std::enable_if_t<cond>
au lieu de corrigerstd::enable_if_t<cond, int>::type = 0
type de retour:
Enfin, dans le paramètre de fonction:
+
,-
,*
, ...)void* = nullptr
) (donc le pointeur de fonction serait différent, et ainsi de suite)Il existe des différences subtiles avec l'héritage et
using
:Selon le
using-declarator
(c'est moi qui souligne):namespace.udecl
Ainsi, pour l'argument de modèle et le type de retour, les méthodes sont masquées est le scénario suivant:
Démo (gcc trouve mal la fonction de base).
Alors qu'avec un argument, un scénario similaire fonctionne:
Démo
la source