Desambiguador de modelo para nomes dependentes

8

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() constclang, ele aceita.

Conforme declarado como um comentário no bloco de código, a palavra-chave do modelo é necessária para yF.head<1>()?

Jodebo
fonte
Eu acho que você pode estar executando em um problema específico do clang implementação onde combinando tipos de modelo aos parâmetros ainda não está totalmente implementado: clang.llvm.org/cxx_status.html#p0522
Eddy Luten
Eu acho que Matrix<RayDim>não é um tipo dependente e, portanto, a palavra-chave não é necessária. Talvez eu tenha tempo para responder mais tarde.
aschepler
O @EddyLuten P0522 trata dos parâmetros do modelo e não há nenhum neste exemplo.
aschepler
Um exemplo simplificado que parece estar relacionado: godbolt.org/z/KpXpYs
Evg

Respostas:

1

A templatepalavra-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 templatedepois 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;

Em todos os outros contextos, ao nomear uma especialização de modelo de um membro de uma especialização desconhecida ( [temp.dep.type] ), o nome do modelo do membro deve ser prefixado pela palavra-chave template.

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:

Um tipo é dependente se for

  • um parâmetro de modelo,
  • um membro de uma especialização desconhecida,
  • uma classe ou enumeração aninhada que é um membro dependente da instanciação atual,
  • um tipo qualificado de CV em que o tipo não qualificado de CV é dependente,
  • um tipo composto construído a partir de qualquer tipo dependente,
  • um tipo de matriz cujo tipo de elemento é dependente ou cujo limite (se houver) é dependente de valor,
  • um tipo de função cuja especificação de exceção depende do valor,
  • uma identificação de modelo simples, na qual o nome do modelo é um parâmetro do modelo ou qualquer um dos argumentos do modelo é um tipo dependente ou uma expressão que depende do tipo ou depende do valor ou é uma expansão do pacote, ou
  • denotado pela decltype(expressão) , em que a expressão depende do tipo.

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 por decltype. É 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 Matrixnão é um parâmetro do modelo. O argumento do modelo RayDimé 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:

Uma expressão de identificação depende do tipo, se contiver

  • um identificador associado à pesquisa de nome com uma ou mais declarações declaradas com um tipo dependente,
  • um identificador associado à pesquisa de nome com um parâmetro de modelo que não seja do tipo declarado com um tipo que contém um tipo de espaço reservado,
  • um identificador associado à pesquisa de nome com uma variável declarada com um tipo que contém um tipo de espaço reservado ([dcl.spec.auto]) em que o inicializador depende do tipo,
  • um identificador associado pela pesquisa de nome a uma ou mais declarações de funções-membro da instanciação atual declarada com um tipo de retorno que contém um tipo de espaço reservado,
  • um identificador associado pela pesquisa de nome a uma declaração de ligação estruturada cujo colchete ou iniciador igual depende do tipo,
  • o identificador __func__([dcl.fct.def.general]), em que qualquer função envolvente é um modelo, um membro de um modelo de classe ou uma lambda genérica,
  • um ID de modelo dependente,
  • um ID da função de conversão que especifica um tipo dependente ou
  • um especificador de nome aninhado ou um ID qualificado que nomeie um membro de uma especialização desconhecida;

ou se ele nomear um membro dependente da instanciação atual que é um membro de dados estático do tipo "matriz de limite desconhecido de T" para alguns T([temp.static]).

RayDimcertamente 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 de RayDimcertamente não é um parâmetro de modelo , uma função de membro ou uma declaração de ligação estruturada, e seu tipo const intcertamente não é um tipo dependente ou de matriz e não contém um tipo de espaço reservado. Portanto, RayDimnão depende do tipo.

"Dependente do valor" é definido em [temp.dep.constexpr] . Os únicos casos que podem ser aplicados a um identificador único como RayDimestão no parágrafo 2:

Uma expressão id depende do valor se:

  • depende do tipo,
  • é o nome de um parâmetro de modelo não-tipo,
  • nomeia um membro de dados estático que é um membro dependente da instanciação atual e não é inicializado em um membro-declarador ,
  • nomeia uma função de membro estático que é um membro dependente da instanciação atual ou
  • é uma constante com tipo literal e é inicializada com uma expressão dependente de valor.

De cima, RayDimnã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 inicializador 3não depende de valor.

Portanto, RayDimnão depende de valor ou de tipo. Portanto, Matrix<RayDim>não é um tipo dependente, yF.headnão é membro de uma instanciação desconhecida e a templatepalavra - chave anterior headé opcional, não necessária. (É permitido, pois não está em um "contexto somente de tipo" e head, de fato, nomeia um modelo de membro.)

aschepler
fonte
0

aviso Legal

isso não é uma resposta, mas um longo comentário

Minhas habilidades com 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 templatepalavra-chave em

return yF.template head<1>();

Nomes dependentes

Às vezes, dentro dos modelos, precisamos ajudar o compilador a decidir se um nome se refere a

  1. um valor int T::x = 0,
  2. um tipo struct T::x {};ou
  3. Uma amostra template <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 usamos template. 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

template <int N>
struct Matrix 
{
    template <int Idx>
    int head() { return Idx; }
};


template <typename T>
struct Test 
{    
    static constexpr int RayDim = 3;

    int func() const 
    {
        Matrix<RayDim> yF;
        return yF.head<1>(); // clang complains, gcc and msvc are ok
    }
};


struct Empty {};

int test() 
{
    Test<Empty> t;
    return t.func();
}

Geralmente RayDimdeve ser um nome dependente (porque está dentro do modelo Test), o que causaria Matrix<RayDim>também um nome dependente. Por enquanto, vamos supor que Matrix<RayDim>realmente seja um nome dependente. Isso cria Matrix<RayDim>::headum nome dependente também. Como Matrix<RayDim>::headé uma função de modelo, é um modelo em si e as regras dos nomes dependentes acima se aplicam, exigindo o uso da templatepalavra - chave. É disso que o clang está reclamando.

No entanto, como ele RayDimé definido por dentro Teste functambém é definido dentro do mesmo modelo e não por uma função de modelo em si, não acho RayDimque seja um nome dependente no contexto de func. Além disso, RayDimnão depende dos argumentos do modelo de Test. Nesse caso, Matrix<RayDim>e Matrix<RayDim>::headrespectivamente, se tornariam nomes não dependentes, o que nos permite omitir a templatepalavra - chave. É por isso que o gcc (e o msvc) são compilados.

Se fôssemos RayDimmodelar também, como aqui

template <typename>
static constexpr int RayDim = 3;

O 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 RayDimum nome dependente no contexto Test<T>::funcou 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 RayDimtornar dependente no ponto em que funcé instanciado.

Timo
fonte