I've gotten into a bit of a design block in a C program of mine as two different header files are required to reference each other. Typically a forward declaration would be used here, but since both classes use template functions/constructors a forward declaration cannot be used as methods/variables from both classes need to be used.
For example consider the following scenario (this is pseudo code as an example, it may/may not compile. The objects are representative of my actual application so if a redesign is necessary then I'd love to understand the design philosophies of what I did wrong)
// Application.hpp
#include <Assets.hpp>
#include <Logger.hpp>
class Application {
public:
// Some brilliant code here ...
Logger myLogger;
template <int someArrayLen> Application(std::array<int, someArrayLen> myArr, SomeOtherTypes someOtherStuff) : myLogger(stuffHere) {
mainAssets = new Assets(myArr);
}
~Application(); // Assume this is implemented in Application.cpp and has delete mainAssets;
};
extern Application* mainApp; // Assume Application* mainApp = nullptr; in Application.cpp
// Assets.hpp
// #include <Application.hpp> ???? The issue lies here
class Assets {
private:
// Random data structures/stuff for holding shaders/textures/etc
protected:
template <int someArrayLen> Assets(std::array<int, someArrayLen> myArr) {
if (!shadersSupported()) {
// Main app is an unknown symbol
mainApp->myLogger->error("Your GPU is too old/whatever!");
}
// Random code for loading assets based on my template stuff
}
friend class Application;
public:
// Get assets/whatever here
};
extern Assets* mainAssets; // Assume Assets* mainAssets = nullptr; in Assets.cpp
How can I fix the compile error regarding mainApp
being an unknown symbol? Any feedback/help is appreciated, thanks!
I've already looked through all the following questions but none address this unique scenario:
- two classes referencing each other
- This question had no use of templates so forward declarations could be used as the method bodies weren't defined in the headers
- Two classes referencing each other with hash template specialization
- The solution from this question cannot be used as here the compiler was unable to figure out how much memory to allocate, whereas in my question the issue isn't regarding the compiler being confused with how much to allocate but rather what to reference
- Two template classes being composed of a member of each other
- This question addressed a design flaw of circular dependencies which my application does not have, both classes are stored globally, they are just instantiated in separate constructors which reference each other.
- Two classes that refer to each other
- This question provides forward declarations as a solution which cannot be used here due to the requirement for using the class methods/constructors in template function definitions.
I've also already considered the following:
- Trying to change from std::array to pointers, this wouldn't work as my Assets constructor does rely on the lengths of the array.
- Trying to change from std::array to std::vector, I want to stick to aggregate initialization so it can be done at compile time, I believe vectors/lists would be too heavy for this.
CodePudding user response:
Forward declarations will indeed work for your problem. The key is that function templates can be defined out of line (i.e., not in your class ... { };
declaration) legally. The same can be achieved for arbitrary functions using the inline
keyword.
To now solve your specific problem, just split Application.hpp
into Applicaton_fwd.hpp
and Application.hpp
- similar to iosfwd
. Application_fwd.hpp
contains almost all the code and Application.hpp
includes Application_fwd.hpp
and Assets.hpp
before defining the Application::Application
function template (just like you would define a function in a *.cpp
file).
In Assets.hpp
, you can simply use Application_fwd.hpp
as long as you do not use the constructor. If you also use the Application
constructor in Assets.hpp
, things become a bit more complicated in that you need to very carefully consider all possible inclusion scenarios (i.e., what happens exactly every time one of your headers is included by themselves or a user) to make sure that it resolves in the order that you need it to without the guards causing trouble.
You can see it in action here