There is a similar rule in most style guides:
Include the .hpp in the corresponding .cpp as the first substantive line of code. Even if the .cpp is otherwise empty.
The latter sentence is popularized by John Lakos. See e.g. CppCon 2016: John Lakos “Advanced Levelization Techniques (part 1 of 3)", at 7:28:
This rule ensures, that our headers are self-contained, i.e. they compile in isolation, or in other words they don't require other headers in front of them to compile.
But, does this ensure, that we will never have include order dependencies?
It ensures in most cases that we will not have include order dependencies, and does not ensure it in at least the following cases.
There are three different roles in this story.
N::MyClass class, which is intended to be used with FooBarBaz lib.N::MyClass with FooBarBaz, but runs into problems.Alfred plays first. FooBarBaz, this magnificent library has some functions which operate on client types, which are accepted as template parameters. The client's types are supposed to have a function named FBBSize() which
int, andFBBSize() with ADL.The library calls FBBSize() in its function templates, and it even has a concept SupportsFBBSize which tells if a type has a corresponding FBBSize() function. Alfred's header-only library FBB.hpp might look something like this:
// --- FBB.hpp ---
namespace FBB {
template <class T>
concept SupportsFBBSize = requires(const T& value) { FBBSize(value); };
template <class T>
void SomeFunction(const T& value) {
FBBSize(value);
}
}
Then, Bob writes a class MyClass in namespace N and wants to support its usage with the FooBarBaz lib. Thus, he declares an FBBSize() function in the header, but erroneously he puts the function into the global namespace. His MyClass.hpp looks like this:
// --- MyClass.hpp ---
namespace N {
class MyClass {};
}
inline int FBBSize(const N::MyClass&) { return 123; }
Now, both headers compile in isolation, but if Goofy wants to use FBB::SomeFunction() with N::MyClass, only this include order will compile:
#include "MyClass.hpp"
#include "FBB.hpp"
int main() {
N::MyClass mc;
FBB::SomeFunction(mc);
}
If you swap the #include directives, you get a compilation error.
The reason is arcane. In SomeFunction() FBBSize() is an unqualified dependent name, which is looked up using
In the order as above, FBBSize() is found using ordinary lookup (contrary to Alfred's intention) in the context of the definition of SomeFunction(), i.e. "above" SomeFunction(). ADL finds nothing, because FBBSize() isn't in namespace N. However, if you swap the #include directives, FBBSize() will be declared "below" SomeFunction(), so neither lookup will find it, and compilation will fail.
It's even harder to notice the bug, if instead calling FBB::SomeFunction() Goofy uses the concept FBB::SupportsFBBSize. The main() function will compile in both include orders, however, the value of the concept will depend on the order:
#include "MyClass.hpp"
#include "FBB.hpp"
int main() {
return FBB::SupportsFBBSize<N::MyClass>;
}
The above main() function returns true, but if you swap the #include directives, it will return false.
Goofy would have got the same result, i.e. the same compilation errors, and the same value for the concept, in both orders of the #include directives,
MyClass.hpp Bob would have put FBBSize() into namespace N, orFBB.hpp Alfred would have somehow "disabled" ordinary lookup of FBBSize().The problem with ordinary lookup is not that it's ordinary, but that it's executed in the template definition context. If FBBSize() is to be found "above" the template definition, FBB.hpp will require the header of the client's class in front of it to work as intended.
This is not an actual, but a potential include order dependency. We could say, FBB.hpp has the potential in it, to depend on the header of the client's class.
To eliminate that potential, Alfred has to stop ordinary lookup of FBBSize() at the scope of namespace FBB. He could put a dummy declaration of an FBBSize() function in namespace FBB before all facilities of the header:
// --- FBB.hpp ---
namespace FBB {
int FBBSize(...) = delete; // declare FBBSize() in the same file and namespace as your type
template <class T>
concept SupportsFBBSize = requires(const T& value) { FBBSize(value); };
template <class T>
void SomeFunction(const T& value) {
FBBSize(value);
}
}
The dummy declaration has to be a function. If it would be a variable or a type, ADL would not be executed.
Now, when Goofy tries to compile his 1st example (which uses FBB::SomeFunction()), it won't compile in any include order, because ADL finds nothing. It will always work the same way in both include orders, because FBBSize() can only be found in the instantiation context.
The comment after the dummy declaration is a reminder for the clients. The compiler will show it in the error message when it cites that line from the code. It's a workaround until you cannot use = delete("custom error message"), see EWG152.
Goofy's 2nd example (which uses FBB::SupportsFBBSize) will also work the same way in both include orders. The value of the concept will be false in both cases.
You can play with the above code snippets on Godbolt.
Recall that the problem with ordinary lookup is not that it's ordinary, but that it's executed in the template definition context. Qualified lookup is also executed in the template definition context, and introduces the same potential include order dependency.
I mention this example for the sake of completeness, but I don't think, that it's a real life problem.
There are the same three actors in this play.
N::MyClass class, which is intended to be used with FooBarBaz lib.N::MyClass with FooBarBaz, but runs into problems.This time Alfred wants the client types to have a Size() function, which
int, andFBB).Alfred's header:
// --- FBB.hpp ---
namespace FBB {
template <class T>
requires requires { T::Size; }
int Size(const T&) {
return T::Size;
}
template <class T>
concept SupportsFBBSize = requires(const T& value) { FBB::Size(value); };
template <class T>
void SomeFunction(const T& value) {
FBB::Size(value);
}
}
He implemented a "default" Size() function. This way the client can put a public Size constant in his class instead of declaring a Size() function. Another reason for having this function in front of every call to it, is that without it FBB.hpp won't compile in isolation. Compilers would notice, that there is no Size in namespace FBB, therefore they can prove, that no specialization of these templates compile, and report the error early.
Bob does everything according to FooBarBaz's documentation:
// --- MyClass.hpp ---
namespace N {
class MyClass {};
}
namespace FBB {
inline int Size(const N::MyClass&) { return 123; }
}
This header also compiles in isolation.
Goofy wants to use FBB::SomeFunction() and FBB::SupportsFBBSize with N::MyClass:
#include "MyClass.hpp"
#include "FBB.hpp"
int main() {
N::MyClass mc;
FBB::SomeFunction(mc);
}
#include "MyClass.hpp"
#include "FBB.hpp"
int main() {
return FBB::SupportsFBBSize<N::MyClass>;
}
The 1st example compiles, and in the 2nd the main() function returns true. However, if you swap the #include directives, the 1st won't compile, and the 2nd will return false.
There is no solution for this potential include order dependency.
You can play with the above code snippets on Godbolt.
Suppose, you work for a company, where for every DLL there is a dedicated precompiled header. Therefore, these headers can differ.
As a consequence by following the style guide rule you can verify that your header compiles in isolation, but that verification will be only valid in the DLL of the header, and not in the entire code base.
Suppose you write a class Monkey in Zoo.dll, which uses class X. You forget to include X.hpp, but suppose X.hpp is in Zoo.dll's precompiled header:
// --- Monkey.hpp ---
class Monkey {
X x;
};
Monkey.cpp includes Monkey.hpp as the first substantive line of code. It compiles all right, because when it compiles, Zoo.dll's precompiled header (and X.hpp in it) will be silently included in front of the entire translation unit.
If however, someone else wants to use this header in another DLL, where X.hpp is not included in the precompiled header, they will get a compilation error.
Two approaches come to mind.
#include "X.hpp" is not forgotten.B.dll depends on A.dll then B.pch.hpp includes A.pch.hpp.This is a related problem, but not our topic. I mention it only because people seemingly think, that it belongs here.
The common example is the C++ Standard Library, where you use a facility, but you don't include the proper header. Consider the following Fun.hpp:
#include <type_traits>
std::int64_t Fun();
The type std::int64_t is defined in cstdint. In MSVC type_traits includes cstdint (see on GitHub), therefore, Fun.cpp (which includes Fun.hpp in its first line) compiles. But it doesn't compile with Clang and GCC. Demo.
This is similar to an include order dependency. One could say, Fun.hpp requires cstdint in front of it. But, in our topic we are talking about a fixed compiler, and about files under our control.
Fun.hpp is self-contained.Fun.cpp won't compile.Similarly, we could think about updating a 3rd party library to the latest version, and contrive similar examples. We could think about integrating code from other source branches. Finally, we could ask, what if MSVC won't include cstdint in type_traits in the future? All these problems are related, but different problems, and the solution to them is Include What You Use.
In the above Fun.hpp we run into compilation errors when porting from MSVC to a different compiler, but that's not because Foo.hpp is not self-contained, but because we didn't include precisely what we use. We included the definition of std::int64_t indirectly.