Ok, so before you all go pointing me to the different posts where this question has already been addressed, I wish to disclaim that I have indeed solved my issue following this post here.
This post is more about digging beneath the surface of how the compiler works.
Here we go...
Menu.h includes MenuEntry.h and MenuEntryKey.h, like so:
#ifndef MENU_H
#define MENU_H
#include <cstdlib>
#include <map>
#include <string>
#include "MenuEntry.h"
#include "MenuEntryKey.h"
class Menu
{
public:
// ctor, dtor, copy control
Menu(const std::map<MenuEntryKey, MenuEntry> &opts = std::map<MenuEntryKey, MenuEntry>(),
const unsigned numF = 0, const unsigned numD = 0) : options_(opts),
numFoodOptions_(numF),
numDrinkOptions_(numD) {}
~Menu() {}
Menu(const Menu &);
Menu &operator=(const Menu &);
// entry select funcs: both simply return a random menu selection's name, based on the provided entry type.
inline const std::string selectOption(const char);
private:
const std::map<MenuEntryKey, MenuEntry> options_;
const unsigned numFoodOptions_;
const unsigned numDrinkOptions_;
// private accessors; Guests must only be able to select a valid option. This can be changed later, if need be.
inline const std::map<MenuEntryKey, MenuEntry> &getOptions() const { return options_; }
inline const std::map<MenuEntryKey, MenuEntry> &getOptions()
{
return (static_cast<const Menu &>(*this)).getOptions();
}
inline const unsigned getNumFoodOptions() const { return numFoodOptions_; }
inline const unsigned getNumDrinkOptions() const { return numDrinkOptions_; }
};
#endif // MENU_H
Showing you MenuEntry.h and MenuEntryKey.h is really not relevant to this question; they are both just standalone components used to build the Menu.
Next, I have a class called DataLoader, which creates a Menu based on plaintext metadata. Here is DataLoader.h:
#ifndef DATALOADER_H
#define DATALOADER_H
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <ctype.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "Menu.h"
#include "MenuEntryKey.h"
#include "MenuEntry.h"
class Menu;
namespace DataLoader
{
const Menu &createMenu();
} // namespace DataLoader
#endif // DATALOADER_H
As it stands, there are no errors in DataLoader.cpp; however, as soon as I remove the MenuEntry.h and MenuEntryKey.h includes from DataLoader.h, like so:
#ifndef DATALOADER_H
#define DATALOADER_H
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <ctype.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "Menu.h"
// Note the missing includes that used to be here.
// "" ""
class Menu;
namespace DataLoader
{
const Menu &createMenu();
} // namespace DataLoader
#endif // DATALOADER_H
I get incomplete type is not allowed (DataLoader.cpp):
#include "DataLoader.h"
const Menu &DataLoader::createMenu()
{
std::ifstream in;
in.open("../../../../meta/menu.md");
if (!in)
{
std::cout << "Unable to open file.\n";
exit(-1);
}
std::map<MenuEntryKey, MenuEntry> options;
std::string line;
while (std::getline(in, line))
{
if (line.size() == 0)
continue;
if (line[0] == '-')
{
char entryType = toupper(line[1]);
unsigned num = 0;
std::vector<std::string> tokens;
std::getline(in, line);
while (line[0] != '^')
{
boost::algorithm::split(tokens, line, boost::algorithm::is_any_of(" "));
MenuEntryKey key; //INCOMPLETE TYPE ERROR HERE
}
}
} // end while
}
The reason this is so baffling to me is Menu.h includes MenuEntry.h and MenuEntryKey.h! Would that not imply that including Menu.h would also include the latter two? Please enlighten me on this strange occurrence.
The reason I DON'T want to include MenuEntry.h and MenuEntryKey.h in DataLoader.h is because I have learned it is generally a good practice to avoid including things that have already been included, which is something I am currently trying to get a better grip on.
Note that if you look at my headers closely, you'll see that I double include some STL headers, which was no accident; I just have yet to find and integrate a clean method of taking care of it.
Let me know if you need more information. Hopefully, this question is appropriate for SO and I have provided enough detail.
Cheers
EDIT:
I am seeing a little bit of confusion in the comments, so I am going to try to clarify my question as best as I can.
• Menu.h includes BOTH MenuEntry.h and MenuEntryKey.h.
• MenuEntry.h only includes and MenuEntryKey.h includes literally nothing. AFAIK, There are no circular dependencies between Menu.h, MenuEntry.h, and MenuEntryKey.h.
• When DataLoader.cpp includes ONLY Menu.h, the preprocessor cannot find a complete definition for MenuEntryKey.h OR MenuEntry.h, even though both of these files are included by Menu.h.
If including works similarly to copying and pasting code, then why can't the preprocessor find complete definitions for MenuEntry and MenuEntryKey in DataLoader.cpp? Shouldn't including Menu.h copy and paste the code from that header, which in turn, copies and pastes the headers MenuEntry.h and MenuEntryKey.h???
Hope this helps.
As requested, I will provide a basic working example of what I am talking about. Please give me 5 minutes.
EDIT #2:
Here is your minimal reproducible example.
MenuEntryKey.h:
#ifndef MENUENTRYKEY_H
#define MENUENTRYKEY_H
class MenuEntryKey
{
public:
MenuEntryKey(char type = '!', unsigned num = 0) : type_(type), num_(num) {}
private:
const char type_;
const unsigned num_;
};
#endif // MENUENTRYKEY_H
MenuEntry.h:
#ifndef MENUENTRY_H
#define MENUENTRY_H
#include <string>
class MenuEntry
{
const std::string name_;
};
#endif // MENUENTRY_H
Menu.h:
#ifndef MENU_H
#define MENU_H
#include "MenuEntry.h"
#include "MenuEntryKey.h"
class Menu
{
public:
private:
const unsigned numFoodOptions_;
const unsigned numDrinkOptions_;
};
#endif // MENU_H
DataLoader.h:
#ifndef DATALOADER_H
#define DATALOADER_H
class Menu;
namespace DataLoader
{
const Menu &createMenu();
} // namespace DataLoader
#endif // DATALOADER_H
DataLoader.cpp:
#include "DataLoader.h"
#include "Menu.h"
// #include "MenuEntry.h"
// #include "MenuEntryKey.h"
const Menu &DataLoader::createMenu()
{
MenuEntryKey key;
}
Amazingly enough, this skeleton example produces no incomplete type error. Note the two include statements that are commented out.
Why would this work now, when before it didn't? It must have something to do with included headers; could STL header includes cause this, or only includes of user-defined headers? Would include guards stop this from happening?
Thanks for all your help, I really appreciate it.