Se as classes abaixo não fossem modelos, eu poderia simplesmente ter x
na derived
classe. No entanto, com o código abaixo, eu tenho que usar this->x
. Por quê?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
c++
templates
inheritance
c++-faq
Todos
fonte
fonte
x
withthis->
, a saber: 1) Use o prefixobase<T>::x
, 2) Adicione uma instruçãousing base<T>::x
, 3) Use uma opção de compilador global que habilite o modo permissivo. Os prós e contras dessas soluções são descritos em stackoverflow.com/questions/50321788/...Respostas:
Resposta curta: para criar
x
um nome dependente, para que a pesquisa seja adiada até que o parâmetro do modelo seja conhecido.Resposta longa: quando um compilador vê um modelo, ele deve executar determinadas verificações imediatamente, sem ver o parâmetro do modelo. Outros são adiados até que o parâmetro seja conhecido. É chamado de compilação em duas fases e o MSVC não faz isso, mas é exigido pelo padrão e implementado pelos outros principais compiladores. Se desejar, o compilador deve compilar o modelo assim que o visualizar (para algum tipo de representação interna da árvore de análise) e adiar a compilação da instanciação para mais tarde.
As verificações executadas no próprio modelo, e não em instâncias específicas, exigem que o compilador seja capaz de resolver a gramática do código no modelo.
Em C ++ (e C), para resolver a gramática do código, às vezes você precisa saber se algo é do tipo ou não. Por exemplo:
se A é um tipo, que declara um ponteiro (com nenhum efeito além de sombrear o global
x
). Se A é um objeto, isso é multiplicação (e barrar algum operador sobrecarregar é ilegal, atribuir a um rvalor). Se estiver errado, esse erro deve ser diagnosticado na fase 1 , é definido pelo padrão como um erro no modelo , não em uma instanciação específica dele. Mesmo que o modelo nunca seja instanciado, se A é umint
, o código acima está mal formado e deve ser diagnosticado, como seria sefoo
não fosse um modelo, mas uma função simples.Agora, o padrão diz que nomes que não são dependentes dos parâmetros do modelo devem ser resolvidos na fase 1.
A
aqui não é um nome dependente, ele se refere à mesma coisa, independentemente do tipoT
. Portanto, ele precisa ser definido antes que o modelo seja definido para ser encontrado e verificado na fase 1.T::A
seria um nome que depende de T. Não podemos saber na fase 1 se esse é um tipo ou não. O tipo que eventualmente será usado comoT
em uma instanciação provavelmente ainda não está definido e, mesmo que não seja, não sabemos que tipo (s) será usado como nosso parâmetro de modelo. Mas temos que resolver a gramática para fazer nossas preciosas verificações de fase 1 quanto a modelos mal formados. Portanto, o padrão possui uma regra para nomes dependentes - o compilador deve assumir que eles não são do tipo, a menos que sejam qualificadostypename
para especificar que são do tipo ou usados em determinados contextos inequívocos. Por exemplotemplate <typename T> struct Foo : T::A {};
, em ,T::A
é usado como uma classe base e, portanto, é um tipo sem ambiguidade. SeFoo
for instanciado com algum tipo que tenha um membro de dadosA
em vez de um tipo aninhado A, isso é um erro no código que executa a instanciação (fase 2), não um erro no modelo (fase 1).Mas e um modelo de classe com uma classe base dependente?
A é um nome dependente ou não? Com as classes base, qualquer nome pode aparecer na classe base. Então, poderíamos dizer que A é um nome dependente e tratá-lo como um não-tipo. Isso teria o efeito indesejável de que todo nome no Foo é dependente e, portanto, todos os tipos usados no Foo (exceto os tipos incorporados) ser qualificados. Dentro do Foo, você teria que escrever:
porque
std::string
seria um nome dependente e, portanto, assumido como não-tipo, a menos que especificado de outra forma. Ai!Um segundo problema com a permissão do seu código preferido (
return x;
) é que, mesmo queBar
seja definido antesFoo
ex
não seja um membro nessa definição, alguém poderá posteriormente definir uma especializaçãoBar
para algum tipoBaz
, comoBar<Baz>
um membro de dadosx
, e instanciarFoo<Baz>
. Portanto, nessa instanciação, seu modelo retornaria o membro de dados em vez de retornar o globalx
. Ou, inversamente, se a definição do modelo baseBar
tivessex
, eles poderiam definir uma especialização sem ela, e seu modelo procuraria um globalx
para retornarFoo<Baz>
. Eu acho que isso foi considerado tão surpreendente e angustiante quanto o problema que você tem, mas é silenciosamente surpreendente, em vez de lançar um erro surpreendente.Para evitar esses problemas, o padrão em vigor diz que classes base dependentes de modelos de classe simplesmente não são consideradas para pesquisa, a menos que seja explicitamente solicitado. Isso impede que tudo seja dependente apenas porque pode ser encontrado em uma base dependente. Ele também tem o efeito indesejável que você está vendo - você precisa qualificar as coisas da classe base ou elas não foram encontradas. Existem três maneiras comuns de tornar
A
dependentes:using Bar<T>::A;
na classe -A
agora se refere a algo dentroBar<T>
, portanto dependente.Bar<T>::A *x = 0;
no ponto de uso - Mais uma vez,A
é definitivamente noBar<T>
. Isso é multiplicação, uma vez quetypename
não foi usado, possivelmente um mau exemplo, mas teremos que esperar até a instanciação para descobrir seoperator*(Bar<T>::A, x)
retorna um rvalor. Quem sabe, talvez sim ...this->A;
no ponto de uso -A
é um membro, por isso, se não estiver emFoo
estiver, deve estar na classe base, novamente o padrão diz que isso o torna dependente.A compilação em duas fases é complicada e difícil e apresenta alguns requisitos surpreendentes para palavreado extra no seu código. Mas, assim como a democracia, é provavelmente a pior maneira possível de fazer as coisas, além de todas as outras.
Você poderia argumentar razoavelmente que, no seu exemplo,
return x;
não faz sentido sex
é um tipo aninhado na classe base, então o idioma deve (a) dizer que é um nome dependente e (2) tratá-lo como um não-tipo e seu código funcionaria semthis->
. Até certo ponto, você é vítima de danos colaterais da solução para um problema que não se aplica ao seu caso, mas ainda há o problema de sua classe base potencialmente introduzir nomes sob você que sombream globais ou não ter nomes que você pensou eles tinham, e um global sendo encontrado.Você também pode argumentar que o padrão deve ser o oposto dos nomes dependentes (assuma o tipo, a menos que seja especificado de algum modo como um objeto), ou que o padrão seja mais sensível ao contexto (em
std::string s = "";
,std::string
pode ser lido como um tipo, pois nada mais torna gramatical sentido, mesmo sendostd::string *s = 0;
ambíguo). Mais uma vez, não sei bem como as regras foram acordadas. Meu palpite é que o número de páginas de texto que seriam necessárias mitigou a criação de muitas regras específicas para quais contextos assumem um tipo e quais não são.fonte
-fpermissive
ou similares, sim, é possível. Não sei os detalhes de como ele foi implementado, mas o compilador deve estar adiando a resoluçãox
até conhecer a classe base real do tempateT
. Portanto, em princípio, no modo não permissivo, ele pode registrar o fato de que foi adiado, adiado, fazer a pesquisa assim que for realizadaT
e, quando a pesquisa for bem-sucedida, emita o texto que você sugere. Seria uma sugestão muito precisa, se isso fosse feito apenas nos casos em que funciona: as chances de o usuário ter significado outrox
de outro escopo são bem pequenas!(Resposta original de 10 de janeiro de 2011)
Acho que encontrei a resposta: Problema no GCC: usando um membro de uma classe base que depende de um argumento de modelo . A resposta não é específica para o gcc.
Atualização: em resposta ao comentário de mmichael , do rascunho N3337 da norma C ++ 11:
Se "porque o padrão diz isso" conta como resposta, eu não sei. Agora podemos perguntar por que o padrão exige isso, mas, como a excelente resposta de Steve Jessop e outros apontam, a resposta a esta última pergunta é bastante longa e discutível. Infelizmente, quando se trata do padrão C ++, muitas vezes é quase impossível fornecer uma explicação curta e independente de por que o padrão exige algo; isso se aplica também à última questão.
fonte
O
x
está oculto durante a herança. Você pode reexibir via:fonte