Pourquoi alias template donne-t-il une déclaration contradictoire?

Le port d’un code C ++ 11 de Clang à g ++

template using value_t = typename T::value_type; template struct S { using value_type = int; static value_type const C = 0; }; template value_t<S> // gcc error, typename S::value_type does work const S::C; int main() { static_assert(S::C == 0, ""); } 

donne un comportement différent pour Clang (versions 3.1 à tronc SVN) par rapport à toute version de g ++. Pour ce dernier je reçois des erreurs comme celle-ci

 prog.cc:13:13: error: conflicting declaration 'value_t<S > S<  >::C' const S::C; ^ prog.cc:8:29: note: previous declaration as 'const value_type S<  >::C' static value_type const C = 0; ^ prog.cc:13:13: error: declaration of 'const value_type S<  >::C' outside of class is not definition [-fpermissive] const S::C; 

Si à la place de l’alias du modèle value_t<S> j’utilise le nom complet typename S::value_type alors g ++ fonctionne également .

Question : les alias de template ne sont-ils pas supposés être complètement interchangeables avec leur expression sous-jacente? Est-ce un bug de g ++?

Mise à jour : Visual C ++ accepte également le modèle d’alias dans la définition hors classe.

Le problème repose sur SFINAE. Si vous réécrivez votre fonction membre comme étant value_t> , comme la déclaration externe, alors GCC le comstackra avec plaisir:

 template struct S { using value_type = int; static const value_t> C = 0; }; template const value_t> S::C; 

Parce que l’expression est maintenant fonctionnellement équivalente. Des choses comme l’ échec de la substitution entrent en jeu dans les modèles d’alias, mais comme vous le voyez, la fonction membre value_type const C n’a pas le même “prototype” que value_t> const S::C Le premier n’a pas à exécuter SFINAE, alors que le second l’exige. Il est donc clair que les deux déclarations ont des fonctionnalités différentes, d’où la crise de GCC.

Fait intéressant, Clang le comstack sans signe d’anomalie. Je suppose que l’ordre des parsings de Clang est inversé par rapport à celui des GCC. Une fois que l’expression alias-template est résolue et fine (c’est-à-dire qu’elle est bien formée), clang compare alors les deux déclarations et vérifie qu’elles sont équivalentes (dans ce cas elles le sont, les deux expressions étant value_type en value_type ).

Maintenant, lequel est correct du regard du standard? La question de savoir si le modèle SFNIAE de alias-template fait partie de la fonctionnalité de sa déclaration rest un problème non résolu. Citant [temp.alias] / 2 :

Lorsqu’un template-id fait référence à la spécialisation d’un modèle d’alias, il est équivalent au type associé obtenu par substitution de ses arguments de template pour les parameters de template dans l’identificateur de type du modèle d’alias.

En d’autres termes, ces deux sont équivalents:

 template struct Alloc { /* ... */ }; template using Vec = vector>; Vec v; vector> u; 

Vec et vector> sont des types équivalents, car une fois la substitution effectuée, les deux types sont vector> . Notez comment “après substitution” signifie que l’équivalence n’est vérifiée que lorsque tous les arguments du modèle sont remplacés par les parameters du modèle. C’est-à-dire que la comparaison commence lorsque T dans le vector> est remplacé par int partir de Vec . Peut-être que c’est ce que fait Clang avec value_t> ? Mais voici la citation suivante de [temp.alias] / 3 :

Cependant, si l’identificateur de modèle est dépendant, la substitution d’arguments de modèle subséquente s’applique toujours à l’identificateur de modèle. [Exemple:

 template using void_t = void; template void_t f(); f(); // error, int does not have a nested type foo 

– exemple de fin]

Voici le problème: l’expression doit être bien formée, le compilateur doit donc vérifier si la substitution est correcte. Lorsqu’il existe une dépendance pour effectuer une substitution d’argument de modèle (par exemple, typename T::foo ), la fonctionnalité de l’expression entière change et la définition de “équivalence” diffère. Par exemple, le code suivant ne sera pas compilé (GCC et Clang):

 struct X { template  auto foo(T) -> std::enable_if_t; }; template  auto X::foo(T) -> void {} 

Parce que le prototype de foo extérieur est fonctionnellement différent de celui intérieur. Faire auto X::foo(T) -> std::enable_if_t au lieu de cela rend le code comstackr correctement. C’est parce que le type de retour de foo est une expression qui dépend du résultat de sizeof(T) == 4 , donc après la substitution de modèle, son prototype pourrait être différent de chaque instance. Alors que le type de retour auto X::foo(T) -> void n’est jamais différent, ce qui est en conflit avec la déclaration à l’intérieur de X C’est le même problème que vous rencontrez avec votre code. Donc, GCC semble être correct dans ce cas.