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’
Foi útil?

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();
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top