Uso inválido de tipo incompleto
Pergunta
Eu estou tentando usar um typedef de uma subclasse no meu projeto, eu tenho isolado o meu problema no exemplo abaixo.
Alguém sabe onde eu estou indo errado?
template<typename Subclass>
class A {
public:
//Why doesn't it like this?
void action(typename Subclass::mytype var) {
(static_cast<Subclass*>(this))->do_action(var);
}
};
class B : public A<B> {
public:
typedef int mytype;
B() {}
void do_action(mytype var) {
// Do stuff
}
};
int main(int argc, char** argv) {
B myInstance;
return 0;
}
Esta é a saída que eu recebo:
sean@SEAN-PC:~/Documents/LucadeStudios/experiments$ g++ -o test test.cpp
test.cpp: In instantiation of ‘A<B>’:
test.cpp:10: instantiated from here
test.cpp:5: error: invalid use of incomplete type ‘class B’
test.cpp:10: error: forward declaration of ‘class B’
Solução
A razão é que ao instanciar um modelo de classe, todas as suas declarações (não as definições) de suas funções de membro são instanciado também. O modelo de classe é instanciado precisamente quando é necessária a definição completa de uma especialização. Esse é o caso quando ele é usado como uma classe base, por exemplo, como no seu caso.
Então, o que acontece é que A<B>
é instanciado em
class B : public A<B>
altura em que B
não é um tipo completa ainda (isto é, após a chave de fechamento da definição de classe). No entanto, a declaração de A<B>::action
requer B
para ser completa, porque ele está rastejando no âmbito do mesmo:
Subclass::mytype
O que você precisa fazer é atrasar a instanciação até certo ponto em que B
está completa. Uma maneira de fazer isso é modificar a declaração de action
para torná-lo um modelo de membro.
template<typename T>
void action(T var) {
(static_cast<Subclass*>(this))->do_action(var);
}
Ele ainda é tipo seguro porque se var
não é do tipo certo, passando var
para do_action
falhará.
Outras dicas
Você pode contornar isso usando uma classe traços:
Ela exige que você configurar uma classe características especializadas para a para cada classe actuall que você usa.
template<typename SubClass>
class SubClass_traits
{};
template<typename Subclass>
class A {
public:
void action(typename SubClass_traits<Subclass>::mytype var)
{
(static_cast<Subclass*>(this))->do_action(var);
}
};
// Definitions for B
class B; // Forward declare
template<> // Define traits for B. So other classes can use it.
class SubClass_traits<B>
{
public:
typedef int mytype;
};
// Define B
class B : public A<B>
{
// Define mytype in terms of the traits type.
typedef SubClass_traits<B>::mytype mytype;
public:
B() {}
void do_action(mytype var) {
// Do stuff
}
};
int main(int argc, char** argv)
{
B myInstance;
return 0;
}
Você derivar B
de A<B>
, então a primeira coisa que o compilador faz, uma vez que ele vê a definição de B
classe é tentar A<B>
instanciar. Para fazer isso ele precisa B::mytype
conhecido para o parâmetro de action
. Mas desde que o compilador é apenas no processo de descobrir a própria definição de B
, ele não sabe este tipo ainda e você obterá um erro.
Uma maneira de contornar isso é seria declarar o tipo de parâmetro como outro parâmetro do modelo, em vez de dentro da classe derivada:
template<typename Subclass, typename Param>
class A {
public:
void action(Param var) {
(static_cast<Subclass*>(this))->do_action(var);
}
};
class B : public A<B, int> { ... };
Não é exatamente o que você estava fazendo, mas você pode fazer a ação uma função membro template:
template<typename Subclass>
class A {
public:
//Why doesn't it like this?
template<class V> void action(V var) {
(static_cast<Subclass*>(this))->do_action();
}
};
class B : public A<B> {
public:
typedef int mytype;
B() {}
void do_action(mytype var) {
// Do stuff
}
};
int main(int argc, char** argv) {
B myInstance;
return 0;
}
Você precisa usar um ponteiro ou uma referência como o tipo apropriado não é conhecido neste momento o compilador não pode instanciá-lo.
Em vez de tentar:
void action(const typename Subclass::mytype &var) {
(static_cast<Subclass*>(this))->do_action();
}