0

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.

the_mackster
  • 153
  • 2
  • 9
  • Does `"MenuEntry.h"` or `"MenuEntryKey.h"` include `"Menu.h"`? – NathanOliver Feb 06 '20 at 17:01
  • 1
    How including things work is really more of a question of how the *preprocesor* works than how the *compiler* works. The compiler doesn't deal with `#include` *at all*. That's all handled by the preprocesor before the result is passed to the compiler. Remember that C++ compilation is done in distinct phases; first preprocessing, then compilation (which itself consists of phases; parsing, optimization, code generation) and then the assembler generates an object file and the linker then combines object files into libraries or executables. – Jesper Juhl Feb 06 '20 at 17:02
  • As shown, dataloader.h should have no includes and simply be `class Menu; namespace DataLoader { const Menu &createMenu(); }` extra include should go in cpp though. – Jarod42 Feb 06 '20 at 17:03
  • @NathanOliver nope. `MenuEntry.h` includes ``, and `MenuEntryKey.h` only uses primitives. – the_mackster Feb 06 '20 at 17:03
  • @JesperJuhl you're absolutely correct, my apologies for that slip-up. – the_mackster Feb 06 '20 at 17:04
  • @Jarod42 would you mind explaining why? I've always tried to keep my `#include` statements within header files; not really sure why or where I got that habit. Is it pretty common for `.cpp` files to include headers? – the_mackster Feb 06 '20 at 17:06
  • In `DataLoader.cpp`, did you try including the two files? – ChrisMM Feb 06 '20 at 17:08
  • 4
    We try to minimize dependencies in header, `const Menu &createMenu();` only requires declaration of `Menu`. All your `#include` should go in cpp when required for implementation. – Jarod42 Feb 06 '20 at 17:10
  • @ChrisMM including `MenuEntry.h` and `MenuEntryKey.h` in either `DataLoader.h` or `DataLoader.cpp` seems to do the trick. This question isn't about solving an issue; it's more about investigating **how** the preprocessor works, and why it is not able to find a complete definition for classes that are included by `Menu.h`. – the_mackster Feb 06 '20 at 17:10
  • @the_mackster, was just curious if placing back in the .cpp (not .h) fixed the issue. It does seem like a circular include issue, but I'm not seeing it in the code you provided. – ChrisMM Feb 06 '20 at 17:11
  • 2
    @the_mackster If you value your project compiling quickly, then try to *reduce* includes in headers. Prefer forward declarations where possible and only include the full header in the source file. Sometimes you *have* to include the full header/type definition in a header, but keep doing that to when absolutely required. Just like you should *never* put `using namespace ...` in a header, since it will polute all users of the header, unnecessary includes in headers leads to unnecessary includes in all users of that header. In a big project this can mean *huge* slowdowns of compilation time. – Jesper Juhl Feb 06 '20 at 17:11
  • @JesperJuhl thank you for the heads up, really appreciate it. Any idea as to why the preprocessor can't find a complete definition for `MenuEntry.h` and `MenuEntryKey.h` in `DataLoader.cpp` (which includes `Menu.h`) when in fact both "incomplete" files are included in `Menu.h`? – the_mackster Feb 06 '20 at 17:14
  • @ChrisMM It's not a circular issue, it's a matter of the preprocessor not being able to find complete definitions. – the_mackster Feb 06 '20 at 17:15
  • 1
    `#include` is mostly copy pasting file content. With include guard (which are good) and cycling dependency (which have to be avoided), order of include is important. (-> means `#include` here) With menu.h -> menu_entry.h (-> menu.h discarded thanks to include guard but then missing from menu_entry.h point of view. Whereas menu_entry.h -> menu.h -> (-> menu_entry.h discarded [..]) has problem in other direction. – Jarod42 Feb 06 '20 at 17:17
  • @Jarod42 I will definitely need to look into include guards, but I am not sure how what you've said here relates to my question. – the_mackster Feb 06 '20 at 17:23
  • 2
    Please provide a [mre], remove any irrelevant code and provide all code, I can't reproduce the problem with the given code: https://godbolt.org/z/3-8ZKU. My suspicion would be duplicate include guards in one of your headers. – Alan Birtles Feb 06 '20 at 17:27
  • 1
    As you say your sample doesn't fail, go back to your original code, see what the difference is that's where your problem will lie – Alan Birtles Feb 06 '20 at 18:26
  • the_mackster This answer gives a simple example of what @Jarod42 is describing: https://stackoverflow.com/a/628079/4581301 Here is another one that goes through the process in even more detail: https://stackoverflow.com/a/32129089/4581301 – user4581301 Feb 06 '20 at 18:27
  • @AlanBirtles Not sure if I can find the difference, but I think I have come up with a good approach: use forward declarations wherever possible (especially in **header files**); only include other headers when it is absolutely necessary (i.e. in **.cpp files). With this approach, `Menu.h` no longer includes `MenuEntry.h` and `MenuEntryKey.h`, it just uses fwd declaractions. That said, including all three in `DataLoader.cpp` can no longer introduce repeat includes. – the_mackster Feb 06 '20 at 19:38
  • @user4581301 Thanks for sharing, I appreciate it. – the_mackster Feb 06 '20 at 19:38

0 Answers0