Search code examples
c++oopdesign-patternsforward-declaration

C++ Two Classes Template Methods Reference (Not Compose) Each Other


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:

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.

Solution

  • 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