Erreur de modèle déroutante

91

J'ai joué avec clang un moment, et je suis tombé sur "test / SemaTemplate / depend-template-recover.cpp" (dans la distribution clang) qui est censé fournir des conseils pour récupérer d'une erreur de template.

Le tout peut être facilement réduit à un exemple minimal:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

Le message d'erreur généré par clang:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

... Mais j'ai du mal à comprendre où exactement on est censé insérer le templatemot - clé pour que le code soit syntaxiquement correct?

Prasoon Saurav
la source
11
Avez-vous essayé de l'insérer là où la flèche pointe?
Mike Seymour
3
Similaire à ceci et ceci
Prasoon Saurav

Réponses:

104

ISO C ++ 03 14.2 / 4:

Lorsque le nom d'une spécialisation de modèle de membre apparaît après. ou -> dans une expression postfixe, ou après le spécificateur de nom imbriqué dans un identifiant qualifié, et l'expression postfixe ou identifiant qualifié dépend explicitement d'un paramètre de modèle (14.6.2), le nom du modèle de membre doit être préfixé par le modèle de mot-clé . Sinon, le nom est supposé nommer un non-modèle.

Dans t->f0<U>(); f0<U>est une spécialisation de modèle de membre qui apparaît après ->et qui dépend explicitement du paramètre de modèle U, donc la spécialisation de modèle de membre doit être préfixée par templatemot-clé.

Alors changez t->f0<U>()pour t->template f0<U>().

Prasoon Saurav
la source
Fait intéressant, j'ai pensé mettre l'expression entre parenthèses: t->(f0<U>())aurait corrigé cela, comme je pensais que cela mettrait f0<U>()en expression autonome ... eh bien, je pensais mal, il semble ...
24
Pouvez-vous expliquer pourquoi c'est le cas? Pourquoi le C ++ exigerait-il ce type de syntaxe?
Curieux
2
Ouais c'est bizarre. La langue peut "détecter" que le mot-clé du modèle doit être présent. S'il peut le faire, il doit simplement "insérer" le mot-clé lui-même.
Enrico Borba le
26

En plus des points que d'autres ont soulevés, notez que parfois le compilateur ne pouvait pas se décider et que les deux interprétations peuvent produire des programmes valides alternatifs lors de l'instanciation

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

Ceci s'imprime 0lors de l'omission templateavant f<int()>mais 1lors de son insertion. Je laisse cela comme un exercice pour comprendre ce que fait le code.

Johannes Schaub - litb
la source
3
Voilà un exemple diabolique!
Matthieu M.
1
Je ne peux pas reproduire le comportement que vous décrivez dans Visual Studio 2013. Il appelle f<U>et s'imprime toujours 1, ce qui me semble parfaitement logique. Je ne comprends toujours pas pourquoi le templatemot-clé est requis et quelle différence cela fait.
Violet Giraffe
@Violet le compilateur VSC ++ n'est pas un compilateur C ++ conforme. Une nouvelle question est nécessaire si vous voulez savoir pourquoi VSC ++ imprime toujours 1.
Johannes Schaub - litb
1
Cette réponse explique pourquoi templateest nécessaire: stackoverflow.com/questions/610245/… sans se fier uniquement à des termes standards difficiles à comprendre. Veuillez signaler si quelque chose dans cette réponse prête encore à confusion.
Johannes Schaub - litb
@ JohannesSchaub-litb: Merci, une excellente réponse. Il s'avère que je l'ai déjà lu car il a déjà été voté par moi. Apparemment, ma mémoire est meh.
Violet Giraffe
12

Insérez-le juste avant le point où se trouve le curseur:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

Edit: la raison de cette règle devient plus claire si vous pensez comme un compilateur. Les compilateurs ne regardent généralement en avant qu'un ou deux jetons à la fois, et ne «regardent pas en avant» le reste de l'expression. [Modifier: voir le commentaire] La raison du mot-clé est la même que la raison pour laquelle vous avez besoin du typenamemot - clé pour indiquer les noms de types dépendants: il dit au compilateur "hé, l'identifiant que vous êtes sur le point de voir est le nom d'un modèle, plutôt que le nom d'un membre de données statique suivi d'un signe inférieur à ".

Doug
la source
1
Je n'aurais jamais pu deviner ça ... mais merci ;-). il y a clairement toujours quelque chose à apprendre sur le C ++!
3
Même avec une anticipation infinie, vous en aurez toujours besoin template. Il y a des cas où les deux avec et sans templatedonneront des programmes valides avec un comportement différent. Il ne s'agit donc pas seulement d'un problème de syntaxe (il t->f0<int()>(0)est syntaxiquement valide pour la version de la liste d'arguments inférieur à et du modèle).
Johannes Schaub - litb le
@Johannes Schaub - litb: C'est vrai, c'est donc plus un problème d'attribution d'une signification sémantique cohérente à l'expression, que de regarder vers l'avenir.
Doug
11

Extrait de modèles C ++

La construction .template Un problème très similaire a été découvert après l'introduction de typename. Prenons l'exemple suivant en utilisant le type de jeu de bits standard:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

La construction étrange dans cet exemple est .template. Sans cette utilisation supplémentaire du modèle, le compilateur ne sait pas que le jeton inférieur à (<) qui suit n'est pas vraiment «inférieur à» mais le début d'une liste d'arguments de modèle. Notez qu'il s'agit d'un problème uniquement si la construction avant la période dépend d'un paramètre de modèle. Dans notre exemple, le paramètre bs dépend du paramètre de modèle N.

En conclusion, la notation .template (et les notations similaires telles que -> template) ne doivent être utilisées qu'à l'intérieur des templates et uniquement si elles suivent quelque chose qui dépend d'un paramètre de template.

Chubsdad
la source