Erro de modelo confuso

91

Estou brincando com o clang há algum tempo e me deparei com "test / SemaTemplate / dependente-template-recover.cpp" (na distribuição do clang), que supostamente fornece dicas para se recuperar de um erro de modelo.

A coisa toda pode ser facilmente reduzida a um exemplo mínimo:

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>();
    }
};

A mensagem de erro gerada pelo clang:

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

... Mas tenho dificuldade em entender onde exatamente se deve inserir a templatepalavra-chave para que o código seja sintaticamente correto?

Prasoon Saurav
fonte
11
Você tentou inseri-lo onde a seta está apontando?
Mike Seymour
3
Semelhante a este e este
Prasoon Saurav

Respostas:

104

ISO C ++ 03 14.2 / 4:

Quando o nome de uma especialização de modelo de membro aparece depois. ou -> em uma expressão pós-fixada, ou depois de um especificador de nome aninhado em um id qualificado, e a expressão postfix ou id qualificado depende explicitamente de um parâmetro-modelo (14.6.2), o nome do modelo do membro deve ser prefixado pelo modelo de palavra-chave . Caso contrário, o nome é assumido para nomear um não modelo.

In t->f0<U>(); f0<U>é uma especialização de modelo de membro que aparece depois ->e que depende explicitamente do parâmetro do modelo U, portanto, a especialização de modelo de membro deve ser prefixada por templatepalavra-chave.

Portanto, mude t->f0<U>()para t->template f0<U>().

Prasoon Saurav
fonte
Curiosamente, pensei em colocar a expressão entre parênteses: t->(f0<U>())teria consertado isso, pois pensei que colocaria f0<U>()em expressão autônoma ... bem, pensei errado, ao que parece ...
26
Você poderia comentar sobre o porquê disso? Por que o C ++ requer esse tipo de sintaxe?
Curioso
2
Sim, isso é estranho. O idioma pode "detectar" que a palavra-chave do modelo precisa estar presente. Se puder fazer isso, basta "inserir" a palavra-chave lá.
Enrico Borba
26

Além dos pontos que outros fizeram, observe que às vezes o compilador não conseguia se decidir e ambas as interpretações podem gerar programas alternativos válidos ao instanciar

#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;
}

Imprime 0ao omitir templateantes, f<int()>mas 1ao inseri-lo. Deixo isso como um exercício para descobrir o que o código faz.

Johannes Schaub - litb
fonte
3
Esse é um exemplo diabólico!
Matthieu M.
1
Não consigo reproduzir o comportamento que você está descrevendo no Visual Studio 2013. Ele sempre chama f<U>e sempre imprime 1, o que faz todo o sentido para mim. Ainda não entendo por que a templatepalavra-chave é necessária e que diferença faz.
Violet Giraffe
@Violet, o compilador VSC ++ não é um compilador C ++ compatível. Uma nova pergunta é necessária se você quiser saber por que o VSC ++ sempre imprime 1.
Johannes Schaub - litb
1
Esta resposta explica por que templateé necessário: stackoverflow.com/questions/610245/… sem depender apenas dos termos padrão que são difíceis de entender. Relate se alguma coisa nessa resposta ainda estiver confusa.
Johannes Schaub - litb
@ JohannesSchaub-litb: Obrigado, uma ótima resposta. Acontece que eu li isso antes porque já foi aprovado por mim. Aparentemente, minha memória é meh.
Violet Giraffe
12

Insira-o imediatamente antes do ponto onde o cursor é:

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

Editar: o motivo desta regra fica mais claro se você pensar como um compilador. Os compiladores geralmente olham apenas um ou dois tokens de uma vez e geralmente não "olham para frente" para o resto da expressão. [Editar: ver comentário] O motivo da palavra-chave é o mesmo por que você precisa da typenamepalavra-chave para indicar nomes de tipo dependente: está dizendo ao compilador "ei, o identificador que você está prestes a ver é o nome de um modelo, em vez de o nome de um membro de dados estáticos seguido por um sinal de menor que ".

Doug
fonte
1
Eu nunca teria sido capaz de adivinhar isso ... mas obrigado ;-). claramente sempre há algo para aprender sobre C ++!
3
Mesmo com infinita previsão, você ainda precisará template. Há casos em que com e sem templateproduzirão programas válidos com comportamento diferente. Portanto, este não é apenas um problema sintático ( t->f0<int()>(0)é válido sintaticamente para a versão da lista de argumentos menor que e do modelo).
Johannes Schaub - litb
@Johannes Schaub - litb: Certo, então é mais um problema de atribuir um significado semântico consistente à expressão do que de olhar para frente.
Doug
11

Trecho de modelos C ++

A construção .template Um problema muito semelhante foi descoberto após a introdução de typename. Considere o seguinte exemplo usando o tipo de bitset padrão:

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

A construção estranha neste exemplo é .template. Sem esse uso extra do modelo, o compilador não sabe que o token menor que (<) que se segue não é realmente "menor que", mas o início de uma lista de argumentos do modelo. Observe que isso é um problema apenas se a construção antes do período depender de um parâmetro de modelo. Em nosso exemplo, o parâmetro bs depende do parâmetro de modelo N.

Em conclusão, a notação .template (e notações semelhantes como -> template) deve ser usada apenas dentro de templates e somente se eles seguirem algo que dependa de um parâmetro de template.

Chubsdad
fonte