c++ - uso no válido de tipo incompleto
templates typedef (5)
La razón es que al crear una instancia de una plantilla de clase, todas sus declaraciones (no las definiciones) de sus funciones miembro también se instancian. La plantilla de clase se instancia exactamente cuando se requiere la definición completa de una especialización. Ese es el caso cuando se utiliza como una clase base, por ejemplo, como en su caso.
Entonces, ¿qué pasa es que A<B>
se instancia en
class B : public A<B>
en cuyo punto B
todavía no es un tipo completo (es después del corchete de cierre de la definición de clase). Sin embargo, la declaración de A<B>::action
requiere que B
esté completo, porque se está rastreando en el alcance de la misma:
Subclass::mytype
Lo que debe hacer es retrasar la instanciación en algún punto en el que B
esté completo. Una forma de hacerlo es modificar la declaración de action
para convertirla en una plantilla de miembro.
template<typename T>
void action(T var) {
(static_cast<Subclass*>(this))->do_action(var);
}
Todavía es seguro para tipos porque si var
no es del tipo correcto, do_action
var
a do_action
fallará.
Estoy tratando de usar un typedef de una subclase en mi proyecto, he aislado mi problema en el ejemplo a continuación.
¿Alguien sabe dónde me estoy equivocando?
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;
}
Este es el resultado que obtengo:
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’
Necesita utilizar un puntero o una referencia ya que no se conoce el tipo correcto en este momento, el compilador no puede crear una instancia.
En cambio, intente:
void action(const typename Subclass::mytype &var) {
(static_cast<Subclass*>(this))->do_action();
}
No es exactamente lo que estaba preguntando, pero puede hacer que la acción sea una función de miembro de plantilla:
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;
}
Obtienes B
de A<B>
, así que lo primero que hace el compilador, una vez que ve la definición de clase B
es intentar crear A<B>
instancia de A<B>
. Para hacer esto, necesita conocer B::mytype
para el parámetro de action
. Pero dado que el compilador está en el proceso de descifrar la definición real de B
, aún no conoce este tipo y se obtiene un error.
Una forma de evitar esto sería declarar el tipo de parámetro como otro parámetro de plantilla, en lugar de dentro de la clase 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> { ... };
Puedes evitar esto usando una clase de rasgos:
Requiere que establezcas una clase de rasgos especiales para cada clase real que uses.
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;
}