Utilisation de modèles d'alias pour sfinae: le langage le permet-il?

Je viens de découvrir la technique suivante. Il semble très proche de l'un des concepts proposés syntaxe, fonctionne parfaitement sur Clang, GCC et MSVC.

template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;

template <typename T>
void foo(require_rvalue<T> val);

J'ai essayé de le trouver avec des requêtes de recherche comme "SFINAE IN TYPE alias" et n'ai rien obtenu. Est-il un nom pour cette technique et le langage permet-il?


L'exemple complet:

#include <type_traits>

template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;

template <typename T>
void foo(require_rvalue<T>)
{
}

int main()
{
    int i = 0;
    const int ic = 0;
    foo(i);            // fail to compile, as desired
    foo(ic);           // fail to compile, as desired
    foo(std::move(i)); // ok
    foo(123);          // ok
}
25
demandé sur StoryTeller 2018-08-28 04:37:47

2 réponses

[...] est-ce que la langue le permet réellement?

Ne peut rien dire sur le nom, mais cela me semble être un oui.

Le libellé pertinent est [temp.alias]/2:

Lorsque template-id fait référence à la spécialisation d'un alias de modèle, c'est équivalent au type associé obtenus par substitution de ses template-arguments de la template-paramètres dans le type-id de l'alias modèle.

Et la règle sfinae, [temp.déduire]/8:

Seuls les types et expressions non valides dans le contexte immédiat du type de fonction, de ses types de paramètres de modèle et de son explicit-specifier peuvent entraîner un échec de déduction.

Prendre un argument de type require_rvalue<T> se comporte comme si nous substituions cet alias, ce qui nous donne soit un T&& ou un échec de substitution-et cet échec de substitution est sans doute dans l'immédiat contexte de la substitution et est donc "SFINAE-friendly" par opposition à être une erreur difficile. Notez que même si l'argument type par défaut est inutilisé, à la suite de CWG 1558 (la règle void_t), nous avons obtenu l'ajout de [temp.alias]/3:

Cependant, si le template-id {[19] } est dépendant, la substitution d'argument de modèle suivante s'applique toujours au template-id .

Cela garantit que nous substituons toujours dans le argument de type par défaut pour déclencher l'échec de substitution requis.

La deuxième partie non dite de la question Est de savoir si cela peut réellement se comporter comme une référence de transfert. La règle est dans [temp.déduire.appel]/3:

Une référence forwarding est une référence rvalue à un paramètre de modèle cv-unqualified qui ne représente pas un paramètre de modèle d'un modèle de classe (pendant la déduction de l'argument de modèle de classe ([over.correspondre.classe.déduire])). SI P est un transfert de référence et l'argument est une lvalue, le type "lvalue référence à Un" est utilisé au lieu d'Un pour le type de déduction.

Un modèle d'alias avec un paramètre de modèle dont le type associé est une référence rvalue à son paramètre de modèle cv-unqualified est-il considéré comme une référence de transfert? Eh bien, [temp.alias]/2 dit que require_rvalue<T> est équivalent à T&& et T&& est la bonne chose. Donc, sans doute... ouais.

Et tous les compilateurs le traitent comme tel, ce qui est certainement une belle validation à avoir.


bien que, notez l'existence de CWG 1844 et l'absence de définition réelle pour le contexte immédiat, et l'exemple là-bas qui repose également sur un échec de substitution d'un argument par défaut-que les états de question a divergence de mise en œuvre.
14
répondu Barry 2018-08-28 03:01:44

Cela fonctionne et autorisé car il relaie sur des fonctionnalités c++ largement utilisées autorisées par la norme:

  1. SFINAE dans les paramètres de fonction ( [temp.plus]/1, [temp.déduire]/6, [temp.déduire]/8):

    template <typename T>
    void foo(T&& v, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr)
    { /* ... */ }
    

    nous ne pouvons pas déduire sur le paramètre réel comme void foo(typename std::enable_if<std::is_rvalue_reference<T&&>::value, T>::type&&) (CWG # 549), mais il est possible de contourner cette limitation avec des alias de modèle (c'est l'astuce que j'ai présentée dans mon question)

  2. SFINAE dans la déclaration des paramètres du modèle ( [temp.déduire]/7):

    template <typename T, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr>
    void foo(T&& v)
    { /* ... */ }
    
  3. Modèles D'Alias dans les paramètres de fonction ( [temp.alias]/2):

    template<class T> struct Alloc { /* ... */ };
    template<class T> using Vec = vector<T, Alloc<T>>;
    
    template<class T>
      void process(Vec<T>& v)
      { /* ... */ }
    
  4. Les modèles D'Alias peuvent avoir des paramètres par défaut ( [temp.param]/12, [temp.param]/15, [temp.param]/18)

  5. Les paramètres de modèle des modèles d'alias paramétrés avec des types déductibles peuvent encore être déduits ( [temp.déduire.type]/17):

j'ai accepté la réponse de @Barry et mis celle-ci (avec des informations concentrées et sur tous les aspects que l'astuce utilise) parce que beaucoup de gens (y compris moi) ont peur du langage vaudou standard C++ sur les trucs de déduction de modèle.

3
répondu Nikita Kniazev 2018-08-28 15:23:52