Pregunta

Estoy tratando de usar un typedef de una subclase en mi proyecto, he aislado mi problema en el siguiente ejemplo.

¿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;
}

Esta es la salida 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’
¿Fue útil?

Solución

La razón es que cuando se crea una instancia de una plantilla de clase, también se instancian todas sus declaraciones (no las definiciones) de sus funciones miembro. La plantilla de clase se instancia precisamente cuando se requiere la definición completa de una especialización. Ese es el caso cuando se usa como una clase base, por ejemplo, como en su caso.

Entonces, lo que sucede 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 de la llave de cierre de la definición de clase). Sin embargo, la declaración de A<B>::action requiere que action esté completa, porque se está rastreando en su alcance:

Subclass::mytype

Lo que debe hacer es retrasar la creación de instancias en algún punto en el que var se complete. Una forma de hacerlo es modificar la declaración de do_action para que sea una plantilla miembro.

template<typename T>
void action(T var) {
    (static_cast<Subclass*>(this))->do_action(var);
}

Todavía es de tipo seguro porque si <=> no es del tipo correcto, pasará <=> a <=> fallará.

Otros consejos

Puede evitar esto utilizando una clase de rasgos:
Requiere que configure una clase de rasgos especializados para cada clase real que utilice.

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;
} 

Deriva B de A<B>, así que lo primero que hace el compilador, una vez que ve la definición de la clase B::mytype es intentar instanciar action. Para hacer esto, necesita conocer <=> para el parámetro de <=>. Pero dado que el compilador está en proceso de descubrir la definición real de <=>, todavía no conoce este tipo y 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> { ... };

No es exactamente lo que estaba preguntando, pero puede hacer que la acción sea una función 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;
}

Debe utilizar un puntero o una referencia, ya que no se conoce el tipo adecuado en este momento, el compilador no puede crear una instancia.

En su lugar, intente:

void action(const typename Subclass::mytype &var) {
            (static_cast<Subclass*>(this))->do_action();
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top