0

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.
KingCoder11
  • 415
  • 4
  • 19
  • You have cyclic dependencies between classes (Application depends on Assets which depends on Application). This is merely bad. OTOH a cyclic dependency between header files is fatal. Fortunately you can forward-declare at least one f the classes instead of including its header. A call to `mainApp->myLogger->error` doesn't need to be a part of a template, so it can be moved to a .cpp file – n. m. could be an AI Sep 05 '21 at 22:29
  • So you suggest moving the logger call to a forwarder function which is defined in Assets.hpp and implemented in Assets.cpp then? That works since in this scenario I'm only using one function from Application.cpp, but in a scenario where more functions are used relying on forwards could get messy fast – KingCoder11 Sep 05 '21 at 22:36

1 Answers1

1

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

danielschemmel
  • 10,885
  • 1
  • 36
  • 58
  • Thanks for the quick response! Could you show a syntax example of how to split the definition of a class into multiple parts as unlike languages such as swift I don't believe c++ supports anything similar to extensions? So its a bit confusing to me how you could have the template constructor defined in a different header file than the rest of the class. – KingCoder11 Sep 05 '21 at 22:34
  • Ah whoops just saw the you can see it in action button, apologies for the other comment. I was unaware that one could split a template constructor definition like that, thanks for bringing that to light! – KingCoder11 Sep 05 '21 at 22:42