Thanks for the replies. I may be answering my own question here, but it sounds like template definitions inside declaration header files will work in 90% of cases, however I was able to find an edge case where template definitions inside separate files is imperative.
A.h
1 2 3 4 5 6 7 8
|
#pragma once
#include "C.h"
class A {
public:
C c;
int a;
};
|
B.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
#pragma once
template<typename T>
class B {
public:
T t;
int getMember_a();
};
#include "A.h"
template<typename T>
int B<T>::getMember_a() {
return t.a;
}
|
C.h
1 2 3 4 5 6
|
#pragma once
#include "B.h"
#include "D.h"
class C: public B<D>{
};
|
D.h
1 2 3 4 5
|
#pragma once
class D {
int a;
}
|
An error occurs when C.cpp is compiled:
1 2 3 4 5 6 7
|
$ gcc -c -o C.o C.cpp
In file included from B.h:11:0,
from C.h:2,
from C.cpp:1:
A.h:6:2: error: 'C' does not name a type
C c;
^
|
The root of the problem is in B.h:11:0, where it must
#include "A.h"
(because it needs to access members of A). A.h must
#include "C.h"
because A is composed of C. Because A.h was included before C was defined, the compiler does not recognize it, and
#pragma once
prevents the preprocessor from including C.h again before the definition of A. Removing
#pragma once
would allow A to be completely defined, but a redefinition would happen once the include tree returns from C.h:2 and the rest of C.h is compiled.
It sounds like a classic circular dependency at first, but the following demonstrates that the problem is related to the defining of template member functions in B.h (more specifically, the inclusions that those definitions require). Here it is with a separate B.cpp file for definitions:
B.cpp (new):
1 2 3 4 5
|
#include "A.h"
template<typename T>
int B<T>::getMember_a() {
return t.a;
}
|
B.h (modified):
1 2 3 4 5 6 7 8
|
#pragma once
template<typename T>
class B {
public:
T t;
int getMember_a();
};
|
Compilation succeeds:
I conclude from this that the solution is composed of two rules:
1. Place definitions of member functions of template classes in a file separate from the declaration of the class.
2. Never
#include
definition files from header files, only from .cpp files where they are needed.
Once again, definitions in header files will probably work in 90% of cases. The classes in this example are tightly coupled, and the fact that the problem arose at all may be a symptom of bad design in the first place. Unusual as this problem is, it did come up in a real program I was writing.
Disclaimer: I've only been thinking about this problem for a short time, and I came up with the example on the spot, so if you see any mistakes, please point them out. I could be wrong.