Définition hors classe C ++ 20 dans une classe modèle

12

Jusqu'à la norme C ++ 20 de C ++, lorsque nous voulions définir un opérateur hors classe qui utilise certains membres privés d'une classe de modèle, nous utilisions une construction similaire à ceci:

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

Depuis C ++ 20, cependant, nous pouvons omettre la déclaration hors classe, donc aussi la déclaration avant, afin que nous puissions nous contenter de:

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

Maintenant, ma question est, quelle partie de C ++ 20 nous permet de le faire? Et pourquoi cela n'était-il pas possible dans les normes C ++ antérieures?


Comme cela a été souligné dans les commentaires, clang n'accepte pas ce code présenté dans la démo, ce qui suggère que cela pourrait en fait être un bogue dans gcc.

J'ai déposé un rapport de bug sur le bugzilla de gcc

ProXicT
la source
2
Personnellement, je préfère la définition de classe, en évitant la fonction de modèle (et les "problèmes" de déduction (pas de correspondance pour "c string" == Foo<std::string>("foo"))).
Jarod42
@ Jarod42 Je suis totalement d'accord, je préfère également la définition en classe. J'ai été juste surpris de découvrir que C ++ 20 nous permet de ne pas répéter la signature de fonction trois fois lors de sa définition ouf-of-class, ce qui peut être utile dans une API publique où l'implémentation se trouve dans un fichier .inl caché.
ProXicT
Je n'ai pas remarqué que c'était impossible. Comment se fait-il que je l'ai utilisé jusqu'à présent sans problème?
ALX23z
1
Hmmm, dans temp.friend , pas beaucoup changé, surtout pas 1.3 qui devrait être responsable de ce comportement. Puisque clang n'accepte pas votre code, je penche pour que gcc ait un bug.
n314159
@ ALX23z Il fonctionne sans la déclaration hors classe si la classe n'est pas basée sur des modèles.
ProXicT

Réponses:

2

GCC a un bug.

La recherche de nom est toujours effectuée pour les noms de modèle apparaissant avant a <, même lorsque le nom en question est le nom déclaré dans une déclaration (ami, spécialisation explicite ou instanciation explicite).

Étant donné que le nom operator==dans la déclaration d'ami est un nom non qualifié et est soumis à la recherche de nom dans un modèle, les règles de recherche de nom en deux phases s'appliquent. Dans ce contexte, operator==n'est pas un nom dépendant (il ne fait pas partie d'un appel de fonction, donc ADL ne s'applique pas), donc le nom est recherché et lié au point où il apparaît (voir [temp.nondep] paragraphe 1). Votre exemple est mal formé car cette recherche de nom ne trouve aucune déclaration de operator==.

Je m'attendrais à ce que GCC accepte cela en mode C ++ 20 en raison de P0846R0 , qui permet (par exemple) operator==<T>(a, b)d'être utilisé dans un modèle même si aucune déclaration préalable de operator==modèle n'est visible.

Voici un testcase encore plus intéressant:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

Avec -DWRONG_DECL, GCC et Clang conviennent que ce programme est mal formé: la recherche non qualifiée de la déclaration d'ami # 2, dans le contexte de la définition du modèle, trouve la déclaration # 1, qui ne correspond pas à l'ami instancié de Foo<int>. La déclaration n ° 3 n'est même pas prise en compte, car la recherche non qualifiée dans le modèle ne la trouve pas.

Avec -UWRONG_DECL, GCC (en C ++ 17 et versions antérieures) et Clang conviennent que ce programme est mal formé pour une raison différente: la recherche non qualifiée de la operator==ligne # 2 ne trouve rien.

Mais avec -UWRONG_DECL, GCC en mode C ++ 20 semble décider qu'il est OK que la recherche non qualifiée operator==dans # 2 échoue (probablement en raison de P0846R0), puis semble refaire la recherche à partir du contexte d'instanciation du modèle, trouvant maintenant # 3, dans violation de la règle de recherche de nom en deux phases normale pour les modèles.

Richard Smith
la source
Merci pour cette explication détaillée, très bien mise!
ProXicT