invalid use of incomplete type
Asked Answered
C

5

60

I'm trying to use a typedef from a subclass in my project, I've isolated my problem in the example below.

Does anyone know where I'm going wrong?

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

This is the output I get:

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’
Cataclysmic answered 16/3, 2009 at 21:7 Comment(0)
Y
73

The reason is that when instantiating a class template, all its declarations (not the definitions) of its member functions are instantiated too. The class template is instantiated precisely when the full definition of a specialization is required. That is the case when it is used as a base class for example, as in your case.

So what happens is that A<B> is instantiated at

class B : public A<B>

at which point B is not a complete type yet (it is after the closing brace of the class definition). However, A<B>::action's declaration requires B to be complete, because it is crawling in the scope of it:

Subclass::mytype

What you need to do is delaying the instantiation to some point at which B is complete. One way of doing this is to modify the declaration of action to make it a member template.

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

It is still type-safe because if var is not of the right type, passing var to do_action will fail.

Yuille answered 16/3, 2009 at 21:36 Comment(1)
I settled for a slight restructuring of my code (due to some other related issues that I did not describe here) but I tested this approach and it does indeed fix the problem. Thanks!Cataclysmic
I
30

You can get around this by using a traits class:
It requires you set up a specialsed traits class for each actuall class you use.

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;
} 
Interlaken answered 16/3, 2009 at 22:7 Comment(2)
Why the typedef SubClass_traits<B>::mytype mytype; in the derived class B? I'm implementing something like this, and I wonder whether it's not clearer to use the actual type in B.Basalt
@RL-S. So if you change the type (one year later while updating the code) you only have to change it in one place. The reason for the typedef inside B was to make the function call do_action(mytype var) easier to read as the alternative is do_action(typename SubClass_traits<B>::mytype var)Interlaken
D
4

You derive B from A<B>, so the first thing the compiler does, once it sees the definition of class B is to try to instantiate A<B>. To do this it needs to known B::mytype for the parameter of action. But since the compiler is just in the process of figuring out the actual definition of B, it doesn't know this type yet and you get an error.

One way around this is would be to declare the parameter type as another template parameter, instead of inside the derived class:

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> { ... };
Dichogamy answered 16/3, 2009 at 21:29 Comment(0)
I
1

You need to use a pointer or a reference as the proper type is not known at this time the compiler can not instantiate it.

Instead try:

void action(const typename Subclass::mytype &var) {
            (static_cast<Subclass*>(this))->do_action();
    }
Incredulity answered 16/3, 2009 at 21:17 Comment(1)
I tried changing it to a reference and then a pointer, and the error is still the same. I understand your point though.Cataclysmic
B
1

Not exactly what you were asking, but you can make action a template member function:

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;
}
Butt answered 16/3, 2009 at 21:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.