Questão do CCG:usando um membro de uma classe base que depende de um argumento de modelo
-
08-06-2019 - |
Pergunta
O código a seguir não é compilado com o gcc, mas sim com o Visual Studio:
template <typename T> class A {
public:
T foo;
};
template <typename T> class B: public A <T> {
public:
void bar() { cout << foo << endl; }
};
Eu recebo o erro:
teste.cpp:Na função membro ‘void B::bar()’:
teste.cpp:11:erro:'foo' não foi declarado neste escopo
Mas deveria ser!Se eu mudar bar
para
void bar() { cout << this->foo << endl; }
então isso faz compilar, mas acho que não preciso fazer isso.Há algo nas especificações oficiais do C++ que o GCC está seguindo aqui ou é apenas uma peculiaridade?
Solução
Isso mudou em gcc-3.4.O analisador C++ ficou muito mais rigoroso nessa versão - de acordo com as especificações, mas ainda um pouco irritante para pessoas com bases de código legadas ou multiplataforma.
Outras dicas
David Joyner tinha a história, aqui está o motivo.
O problema ao compilar B<T>
é que sua classe base A<T>
é desconhecido do compilador, sendo uma classe de modelo, portanto, não há como o compilador conhecer quaisquer membros da classe base.
Versões anteriores fizeram algumas inferências analisando a classe do modelo base, mas ISO C++ afirmou que essa inferência pode levar a conflitos onde não deveriam haver.
A solução para referenciar um membro da classe base em um modelo é usar this
(como você fez) ou nomeie especificamente a classe base:
template <typename T> class A {
public:
T foo;
};
template <typename T> class B: public A <T> {
public:
void bar() { cout << A<T>::foo << endl; }
};
Mais informações em manual do gcc.
Uau.C++ nunca para de me surpreender com sua estranheza.
Em uma definição de modelo, nomes não qualificados não encontrarão mais membros de uma base dependente (conforme especificado por [temp.dep]/3 no padrão C++).Por exemplo,
template <typename T> struct B {
int m;
int n;
int f ();
int g ();
};
int n;
int g ();
template <typename T> struct C : B<T> {
void h ()
{
m = 0; // error
f (); // error
n = 0; // ::n is modified
g (); // ::g is called
}
};
Você deve tornar os nomes dependentes, por ex.prefixando-os com isto->.Aqui está a definição corrigida de C::h,
template <typename T> void C<T>::h ()
{
this->m = 0;
this->f ();
this->n = 0
this->g ();
}
Como uma solução alternativa (infelizmente não compatível com versões anteriores do GCC 3.3), você pode usar declarações em vez disso->:
template <typename T> struct C : B<T> {
using B<T>::m;
using B<T>::f;
using B<T>::n;
using B<T>::g;
void h ()
{
m = 0;
f ();
n = 0;
g ();
}
};
Isso é todo tipo de loucura.Obrigado, Davi.
Aqui está a seção "temp.dep/3" da norma [ISO/IEC 14882:2003] à qual eles se referem:
Na definição de um modelo de classe ou de um membro de um modelo de classe, se uma classe base do modelo de classe depende de um parâmetro de modelo, o escopo da classe base não é examinado durante a pesquisa de nome não qualificado no ponto de definição da classe modelo ou membro ou durante uma instanciação do modelo ou membro de classe.[Exemplo:
typedef double A;
template<class T> class B {
typedef int A;
};
template<class T> struct X : B<T> {
A a; // a has typedouble
};
O nome do tipo
A
na definição deX<T>
liga-se ao nome typedef definido no escopo do namespace global, não ao nome typedef definido na classe baseB<T>
.] [Exemplo:
struct A {
struct B { /* ... */ };
int a;
int Y;
};
int a;
template<class T> struct Y : T {
struct B { /* ... */ };
B b; //The B defined in Y
void f(int i) { a = i; } // ::a
Y* p; // Y<T>
};
Y<A> ya;
Os membros
A::B
,A::a
, eA::Y
do argumento do modeloA
não afetam a vinculação de nomes emY<A>
. ]
A principal razão pela qual o C++ não pode assumir nada aqui é que o modelo base pode ser especializado para um tipo posteriormente.Continuando o exemplo original:
template<>
class A<int> {};
B<int> x;
x.bar();//this will fail because there is no member foo in A<int>
O VC não implementa pesquisa em duas fases, enquanto o GCC o faz.Portanto, o GCC analisa os modelos antes de serem instanciados e, portanto, encontra mais erros que o VC.No seu exemplo, foo é um nome dependente, pois depende de 'T'.A menos que você diga ao compilador de onde ele vem, ele não poderá verificar a validade do modelo antes de instanciá-lo.É por isso que você precisa informar ao compilador de onde vem.