Encontrei o seguinte problema, compilando o seguinte exemplo:
template <int N>
class Matrix {
public:
template <int Idx>
int head() {
return Idx;
}
};
template <typename T>
class Test {
static constexpr int RayDim = 3;
public:
int func() const {
Matrix<RayDim> yF;
return yF.head<1>();
// ^ is template keyword required here?
}
};
struct Empty {};
void test() {
Test<Empty> t;
}
Link para o Compiler Explorer: https://godbolt.org/z/js4XaP
O código é compilado com o GCC 9.2 e o MSVC 19.22, mas não com o clang 9.0.0. Clang afirma que uma palavra-chave de modelo é necessária. Se static constexpr int RayDim = 3;
for movido para o int func() const
clang, ele aceita.
Conforme declarado como um comentário no bloco de código, a palavra-chave do modelo é necessária para yF.head<1>()
?
Matrix<RayDim>
não é um tipo dependente e, portanto, a palavra-chave não é necessária. Talvez eu tenha tempo para responder mais tarde.Respostas:
A
template
palavra-chave não deve ser solicitada aqui, portanto clang está incorreto ao rejeitar o programa.Todas as seções e números de parágrafos e citações abaixo do padrão C ++ são iguais para o rascunho N4659 do C ++ 17 e para o rascunho C ++ 20 atualmente vinculado.
O requisito para
template
depois de um.
ou->
ou::
símbolo quando nomear um modelo de membro é em [temp.names] / 4 . O parágrafo primeiro lista os casos em que a palavra-chave não é permitida, depois os casos em que é opcional e não faz diferença;Um "membro de uma especialização desconhecida" é um membro de um tipo dependente que não é "a instanciação atual". Portanto, a questão é se
Matrix<RayDim>
é um tipo dependente. Para isso, olhamos para [temp.dep.type] / 9:Matrix<RayDim>
claramente não é um parâmetro de modelo, qualquer tipo de membro, qualificado para cv, um tipo de matriz, um tipo de função ou especificado pordecltype
. É um tipo composto, mas usa apenas um nome de modelo e uma expressão, portanto, não é construído a partir de nenhum outro tipo.Isso deixa o caso de identificação de modelo simples . O nome do modelo
Matrix
não é um parâmetro do modelo. O argumento do modeloRayDim
é uma expressão, portanto, agora, para ver se é dependente de tipo ou de valor."Depende do tipo" é definido em [temp.dep.expr] . Apenas o parágrafo 3 pode ser aplicado a um identificador único, como
RayDim
:RayDim
certamente não contém qualquer__func__
, modelo-id , a conversão de uma função-id , nested-name-especificador , ou qualificado-id . A pesquisa de nome localiza a declaração de membro estático do modelo de classe. Essa declaração deRayDim
certamente não é um parâmetro de modelo , uma função de membro ou uma declaração de ligação estruturada, e seu tipoconst int
certamente não é um tipo dependente ou de matriz e não contém um tipo de espaço reservado. Portanto,RayDim
não depende do tipo."Dependente do valor" é definido em [temp.dep.constexpr] . Os únicos casos que podem ser aplicados a um identificador único como
RayDim
estão no parágrafo 2:De cima,
RayDim
não depende do tipo. Certamente não é um parâmetro de modelo ou uma função de membro. É um membro de dados estático e membro dependente da instanciação atual, mas é inicializado no membro-declarador . Ou seja, o "= 3
" aparece dentro da definição de classe, não em uma definição de membro separada. É uma constante com tipo literal, mas seu inicializador3
não depende de valor.Portanto,
RayDim
não depende de valor ou de tipo. Portanto,Matrix<RayDim>
não é um tipo dependente,yF.head
não é membro de uma instanciação desconhecida e atemplate
palavra - chave anteriorhead
é opcional, não necessária. (É permitido, pois não está em um "contexto somente de tipo" ehead
, de fato, nomeia um modelo de membro.)fonte
aviso Legal
isso não é uma resposta, mas um longo comentário
Minhas habilidades com advogados de idiomas são muito baixas para entender completamente o padrão, mas aqui estão algumas coisas que descobri ao experimentar o código. Tudo o que se segue é baseado no meu entendimento (longe de ser perfeito) do assunto e provavelmente precisa de algumas revisões.
Investigação
Primeiro, fui para uma versão padrão totalmente definida (C ++ 17), para que operemos em uma implementação bem definida.
Olhando para este código , parece que o MSVC ainda tem alguns problemas (com sua pesquisa, eu acho?) Quando se trata de instanciação e redefinição de modelo. Eu não confiaria muito na MSVC em nosso cenário.
Dito isto, vamos pensar por que precisamos da
template
palavra-chave emNomes dependentes
Às vezes, dentro dos modelos, precisamos ajudar o compilador a decidir se um nome se refere a
int T::x = 0
,struct T::x {};
outemplate <typename U> T::foo<U>();
Se nos referimos a um valor, não fazemos nada. Se nos referimos a um tipo, temos que usar
typename
. E se nos referirmos a um modelo que usamostemplate
. Mais sobre esse assunto pode ser encontrado aqui .Não entendo a especificação padrão quando se trata da definição real de um nome dependente, mas aqui estão algumas observações.
Observações
Vamos dar uma olhada no código de referência
Geralmente
RayDim
deve ser um nome dependente (porque está dentro do modeloTest
), o que causariaMatrix<RayDim>
também um nome dependente. Por enquanto, vamos supor queMatrix<RayDim>
realmente seja um nome dependente. Isso criaMatrix<RayDim>::head
um nome dependente também. ComoMatrix<RayDim>::head
é uma função de modelo, é um modelo em si e as regras dos nomes dependentes acima se aplicam, exigindo o uso datemplate
palavra - chave. É disso que o clang está reclamando.No entanto, como ele
RayDim
é definido por dentroTest
efunc
também é definido dentro do mesmo modelo e não por uma função de modelo em si, não achoRayDim
que seja um nome dependente no contexto defunc
. Além disso,RayDim
não depende dos argumentos do modelo deTest
. Nesse caso,Matrix<RayDim>
eMatrix<RayDim>::head
respectivamente, se tornariam nomes não dependentes, o que nos permite omitir atemplate
palavra - chave. É por isso que o gcc (e o msvc) são compilados.Se fôssemos
RayDim
modelar também, como aquiO gcc também o trataria como um nome dependente (o que é correto, pois pode haver uma especialização de modelo posteriormente, para que não saibamos nesse momento). Enquanto isso, o msvc aceita com satisfação tudo o que jogamos nele.
Conclusão
Parece que está se resumindo a
RayDim
um nome dependente no contextoTest<T>::func
ou não. Clang pensa que é, gcc não. A partir de mais alguns testes, parece que os lados do msvc tocam nesse. Mas também é meio que fazer coisas próprias, então quem sabe?Eu ficaria do lado do gcc aqui, pois não vejo uma maneira possível de me
RayDim
tornar dependente no ponto em quefunc
é instanciado.fonte