Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid use of incomplete type - why no error in this case?

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:

  • I have a structure (Tree), and I want to assign different objects of base type First to it.
  • Concrete implementations of 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.
  • Finally 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.

like image 860
penelope Avatar asked Sep 25 '14 13:09

penelope


2 Answers

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.

like image 176
sth Avatar answered Sep 19 '22 16:09

sth


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.

like image 35
Ripple Avatar answered Sep 22 '22 16:09

Ripple