I am confused about why my code is not producing the error invalid use of incomplete type
, while all the reading I have done about this error suggests it should.
The question stemmed from this error showing up (as expected) in the part of my code with a similar structure, but I can not reproduce it in a small example (please see disclaimer at the end of the question).
Summary of what I am trying to do:
Tree
), and I want to assign different objects of base type First
to it.First
have different return values, so two levels of indirection are used:First
is an abstract base class, and First *
are used to handle different concrete instances.template <typename Type> class TypedFirst : public First
is an abstract type that defines the function with return type Type
.ConcreteFirstX
are concrete specializations of TypedFirst<Type>
.In tree.tpp
, why does the call to new TF(this)
not produce the invalid use of incomplete type
error? (the spot is marked in the code) I think the error should be there because, while TF
is a template, when I using ConcreteFirstA
, the tree.tpp
is not aware of it (it does not include concretefirsta.h
or even first.h
, it only forward declares First
)
The full, compilable and runnable code for this example can be found here on pastebin. Here, I will exclude #define
guards and similar things, for brevity. The code is as follows:
// tree.h
class First;
class Tree{
public:
Tree() {}
~Tree() {}
template<class TF> // where TF is a ConcreteFirst
void addFirstToTree();
private:
std::map<std::string, First *> firstCollection; // <- "First"'s here
};
#include "tree.tpp"
// tree.tpp
#include "tree.h"
template <class TF> // where TF is a ConcreteFirst
void Tree::addFirstToTree(){
this->firstCollection[TF::name] = new TF(this); // <--- Why does this work?
// ^^^^^^^^^^^^^
}
// first.h
class Tree;
class First{
public:
static const std::string name;
First(const Tree *baseTree) : myTree(baseTree) {}
virtual ~First();
protected:
const Tree *myTree;
};
template <typename Type> class TypedFirst : public First{
public:
static const std::string name;
TypedFirst(const Tree *baseTree) : First(baseTree) {}
Type &value() {return this->_value;}
private:
Type _value;
};
#include "first.tpp"
// first.tpp
#include "first.h"
template <typename Type>
const std::string TypedFirst<Type>::name = "default typed";
// first.cpp
#include "first.h"
First::~First() {}
const std::string First::name = "default";
// concretefirsta.h
#include "first.h"
class ConcreteFirstA : public TypedFirst<int>{
public:
static const std::string name;
ConcreteFirstA(const Tree *baseTree) : TypedFirst<int>(baseTree) {}
~ConcreteFirstA() {}
};
// concretefirsta.cpp
#include "concretefirsta.h"
const std::string ConcreteFirstA::name = "firstA";
Finally, the code that brings all that together and makes the (in)appropriate function calls:
// main.cpp
#include "tree.h"
#include "first.h"
#include "concretefirsta.h"
int main(){
Tree *myTree = new Tree();
myTree->addFirstToTree<ConcreteFirstA>(); // <-- here! why is this working?
delete myTree;
return 0;
}
DISCLAIMER This question was actually motivated by a bigger problem I had, that I deemed too big and unanswerable in Stack Overflow format. Even though I originally tried asking about it, the question was closed as too broad and I am now trying to salvage it by asking only a part of the question.
My problem is that I keep getting the error in a piece of code with identical structure to this one: but, I can not reproduce it in a small example.
Thus, I am asking why the following piece of code is not producing the error invalid use of incomplete type
(as I would expect), and I hope that will help me understand and solve my actual problem.
Please, do not tell me that this is a case of the XY problem: I know I am not asking about my actual problem, because I (and the community) deemed it too big for this format.
main.cpp
includes tree.h
, which contains (indirectly) the problematic addFirstToTree()
template, and concretefirsta.h
, which contains the definition of ConcreteFirstA
.
Later in main.cpp
, addFirstToTree()
is instantiated for the ConcreteFirstA
type:
myTree->addFirstToTree<ConcreteFirstA>(); // <-- here! why is this working?
This is the point where the compiler needs to know enough enough about the types used as template parameters to be able to compiler addFirstToTree()
. And it does know enough. ConcreteFirstA
is a complete type here, since concretefirsta.h
got included and contains the class definition.
Earlier on in tree.tpp
where addFirstToTree()
gets defined, ConcreteFirstA
is not defined yet, but that doesn't matter. The compiler sees a template and doesn't know for what template parameters that template will be instantiated later on. Any functions/... that depend on a template parameter ("dependent names") cannot be resolved without knowing for which parameters the template will be instantiated, so that name lookup/... is put off until later.
Once the template is instantiated and the compiler resolves all the dependent names and compiles the template for the specific template parameters. Since ConcreteFirstA
is not an incomplete type at that point this works without errors.
Because templates are not compiled until you instanciate them with concrete arguments.
When the compiler comes to the line:
myTree->addFirstToTree<ConcreteFirstA>();
it compiles the function addFirstToTree
for the first time with the argument ConcreteFirstA
, which is a completely known type there.
See cplusplus.com - Templates
They are compiled on demand, meaning that the code of a template function is not compiled until an instantiation with specific template arguments is required. At that moment, when an instantiation is required, the compiler generates a function specifically for those arguments from the template.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With