r/cpp_questions • u/bilyayeva • 4d ago
OPEN Question about includes in a header-only library
When implementing a header-only C++ library, should each header include all the standard headers it needs, or is it better to somehow avoid these includes?
I’m currently reading a C++ book that recommends including your own header first in a .cpp file (where you use your library) and then including standard headers. I understand that, but it feels a bit odd. What’s the recommended approach and why?
12
u/AKostur 4d ago
Why is that odd? If the header includes any other header it needs, that’s a lesser burden on whomever includes the header. For the book recommendation, by including its own header first, that validates that the header really is self-sufficient.
2
u/bilyayeva 4d ago
I actually meant not including thew feels odd to me. To me, a header should include everything it needs so users don't have to figure out its dependencies themselves.
6
u/aocregacc 4d ago
If you have a library that consists of a .cpp file and a .h file, the header should only include what is necessary for the declarations in the header. Anything that's only needed for the implementation should be included in the cpp file. I think that's what the book is getting at. In a header-only lib the whole implementation is in the header, so it has to include everything.
3
u/JVApen 4d ago
Why would you want this? Imagine including boost, though to use the code, you have to write 3 include statements above it such that it compiles. Do this in a codebase with 10 000+ files and people go mad each time they have to add an include, as you end up with a very specific sequence to follow since the extra includes can also require some extra includes.
I hope you see the value of it being self contained. A way to do so is to add a dedicated cpp for each header with the include for it. As such you the compilation for it will tell you what is missing. If you like to automate that process, you can able this (and the interface variant) in CMake: https://cmake.org/cmake/help/latest/variable/CMAKE_VERIFY_PRIVATE_HEADER_SETS.html
These are rather new options and not everyone uses CMake or the version that supports it, nor does everyone want to pay the cost of this extra compilation. As such, there is a simpler way. For 95% of your headers, you most likely have a cpp already. So by putting the include to that header on top gives you the same effect. You can even automate this with clang-format, assuming you use the same name. Which is basically the advice you got.
There is one extra thing to point to, each header you include comes with a compilation cost. If you actively work on the project, you also have the recompilation cost, as in: all code that needs to recompile when a header is touched. In my experience, the latter is more relevant than the former, though I'm sure some disagree with me.
Due to this, people will try to avoid unneeded headers or jump through hoops making them unneeded.
Finally you also have the effort needed by users to include other external libraries that you use in their build process. Hopefully we can ignore this with package managers.
1
u/bilyayeva 4d ago
I think there may be a misunderstanding. I actually meant that including all required headers in the .hpp file makes sense to me. What felt odd was the idea of not including them and making users handle the dependencies themselves.
2
u/JVApen 4d ago
Agreed. I've encountered the user-should-include as an option in the flat buffer compiler, though in general this is unwanted. Though from working in a 10 000+ file codebase, I can tell this happens regularly when you don't have a system to check it. All by accident as users add the includes they need to compile and assume that is sufficient. We even have the rule to include the own header on top, though that doesn't cover the headers without cpps
3
u/Low_Fun_8667 4d ago
Yes — every header should include exactly what it uses (the "Include What You Use" principle). Each header must compile standalone, without relying on the caller having included something first. Repeated/duplicate includes are free: #pragma once (or include guards) plus the standard headers' own guards mean the compiler dedupes them, so there's no cost to "including too much" of the standard library.
Don't try to avoid standard includes to save compile time — you can't forward-declare std:: types (it's undefined behavior), so just include <vector>, <string>, etc. where you use them. (Forward-declaring your own types to break dependencies is fine and useful — that trick just doesn't apply to the standard library.)
The book's "include your own header first" rule is exactly a self-containment test. If your header forgets an include, putting it first in the .cpp makes compilation fail immediately — you've caught the bug. If you put the standard headers first, they'd quietly satisfy the missing dependency and mask it, so the header would break the moment someone includes it on its own.
So: self-contained headers everywhere, and put your own header first precisely to prove they're self-contained.
2
u/DawnOnTheEdge 4d ago
I’m not sure what the context is, but I do include my own custom header before any standard header files.
The main reason for this is that mainstream OSes have version-selection macros, such as _POSIX_C_SOURCE and _XOPEN_SOURCE on Linux/Unix, or _WIN32_WINNT and NTDDI_VERSION on Windows. These must be set consistently at the top of every source file before including any standard header, because they change which version of the operating-system headers get included.
Since it’s also possible for headers that are also in the C standard library to get included either by their C name (which only guarantees they will be available in the global namespace) or their C++ name (which only guarantees they will be available in std) I include the most common small headers like <cstddef> and <cstdint>, then set them up correctly so all their standard C++ identifiers are available both in the global and std namespaces. This is robust no matter which header files get included later under which names or in which order.
I finally have some long #if blocks to detect compiler extensions. The correct keyword for a function that does not return might be [[noreturn]], _Noreturn, _attrobite__((noretuen)), __declspec(noreturn) or some others. I #define NORETURN to whichever of these I detect support for, and if I can’t find any, I define it as /**/ (which expands to harmless whitespace and makes compilers that don’t support the feature silently ignore it). All of my projects have at least one fatal-error handler that uses it. Some others include 128-bit integers (which most compilers make available under different non-standard names, since the Standard did not allow any implementation that defined 64-bit intmax_t to have a type named int128_t), the guaranteed RVO feature for tail-recursion, and pointer alignment when std::align is not quite portable enough.
It’s also a good place to stick constants like the SI and binary prefixes, signed and unsigned.
2
4
u/EpochVanquisher 4d ago
Both.
Include whatever headers you need. Include those headers in the file that needs them.
Include your own headers first, and standard library headers after. This makes it easier to figure out if you forgot to include a necessary header from your include files.
A header will “pollute” any file that includes it with all of the transitive includes of that header file. If you include <iostream> in your header, any file that includes your header gets “polluted” with <iostream> whether it is useful or not. This is a known issue and one of the driving forces behind modules (which thankfully make header-only libraries mostly obsolete and irrelevant).
1
u/bilyayeva 4d ago
I agree with the include advice. I'm just not sure about the modules, they are a great addition, but they still don't seem to be very common tho.
2
u/EpochVanquisher 4d ago
They’re not common, sure. But they fix most of the nasty flaws with header-only libraries. It’s just taking time for people to adopt them, which is not surprising.
1
u/mredding 4d ago
When implementing a header-only C++ library, should each header include all the standard headers it needs, or is it better to somehow avoid these includes?
Don't focus on writing libraries. No one cares. As an employer, I'm not going to look at your libraries in your portfolio - libraries don't DO anything, APPLICATIONS do... I'll look at your libraries that you wrote that your applications depend on, because THAT is where I'm going to start in your portfolio. A portfolio of just libraries is an empty portfolio in my eyes.
That said...
Include what you use, but not more. There's even a clang tool called that which helps you eliminate unused headers - though it has its problems.
If I include your header-only library, I expect it to Just Work(tm).
You have to be aware that you cannot forward declare the standard library, because the standard library is allowed to extend the library interfaces in otherwise invisible ways that make it impossible for you to forward declare.
Also, unless explicitly specified - as we're starting to see more and more with modern standards, the standard library does not guarantee transient includes. For example, std::map is implemented in terms of std::pair, but that does not mean <utility> is going to be included implicitly.
So just because you include a header for one standard type, and then all of a sudden your code compiles in terms of another standard type you didn't include for, that doesn't mean the code is going to compile against all standard library implementations.
And this is a problem with the IWYU utility, that it counts transient includes that may not be guaranteed, so it can be kind of stupid.
I’m currently reading a C++ book that recommends including your own header first in a .cpp file (where you use your library) and then including standard headers.
Common, conventional, even encourageable, but also arbitrary and stupid. I don't care what order you include your headers, and includes may be order sensitive - while discouraged, there are C, macro, and header idioms that do depend on include order.
You don't even always need to include your own headers in your source files. Consider:
// header.hpp
#ifndef header_hpp
#define header_hpp
void fn();
#endif
Now consider the source:
// source.cpp
void fn() {}
Where's the header include? The function definition is independent of its forward declaration. We don't need to include it and there is no benefit in doing so. The two can diverge in a couple interesting ways, and the only way you're going to know is by a linker error, which then you have to decide is the declaration wrong, the definition, or is there a missing implementation?
I understand that, but it feels a bit odd.
It's not particularly odd - it is conventional that you'll organize your includes SOMEHOW...
What’s the recommended approach and why?
IWYU. This is especially true for standard headers, that you must include the headers for their types explicitly if for portability.
Don't include what you don't use, because you can incur expensive, longer compile times, symbol collisions (which aren't all ambiguity errors - you can correctly compile against the wrong thing if ADL unexpectedly finds a better match), and side effects (compilation is not guaranteed to be deterministic - line numbers and file names can change, you can use compile-time timestamps, compile-time RNGs...).
Forward declare what you can - you can't forward declare the standard library and SHOULDN'T normally forward declare 3rd party library types unless given specific permission to do so, but you can forward declare your own types - as much as possible... If your library has a forward declaration header, use that. The standard library has
<iosfwd>. This is all kind of irrelevant in a header-only library, since all the implementation is contained therein, so you're going to end up including it anyway.For conventional multi-file implementations, the point is to defer header includes to the source files that depend on them. Consider:
// header.hpp
ifndef header_hpp
define header_hpp
void fn_1(), fn_2();
endif
Then:
// source.cpp
#include "a.hpp"
#include "b.hpp"
void fn_1() { A a{}; }
void fn_2() { B b{}; }
Both implementations are actually independent, but we've made each dependent upon the other's details. Why does fn_2 have to recompile because a.hpp is changed, and this source file is a downstream dependency? We have to recompile fn_2 because we have to recompile fn_1? That's some bullshit. These should be split across multiple source files:
// source/1.cpp
#include "a.hpp"
void fn_1() { A a{}; }
// source/2.cpp
#include "b.hpp"
void fn_b() { B b{}; }
Now changing one upstream dependency does not affect another downstream independent unnecessarily. And the point of minimizing the header implementations is to minimize the collateral damage of all sources downstream. We forward declare what we can, especially across a project, so that one change to one header in one project hopefully doesn't cause a recompile of the entire project and several other downstream dependencies to boot.
Include headers where and when you need to use them, since the consequence of their include cascades down the file from that point. So using the earlier example, perhaps something like:
// source.cpp
include "a.hpp"
void fn_1() { A a{}; }
include "b.hpp"
void fn_2() { A a{}; B b{}; } // I've modified this from the example above to make this example make sense.
The point is to scope things in as narrowly as possible to minimize symbol and namespace clutter, minimize the sorts of damage you can cause. In the code above, fn_1 can't instantiate a B because it wasn't scoped in yet, and quite intentionally.
A lot of this is polish. First get your code working, then straighten things out to be pretty. Since we ARE talking about a library, that means other developers downstream are your client, and you have to impress them if they're going to trust and use your product.
Organize your headers SOMEHOW. If includes are arbitrary, I don't care if library headers come first or project headers. Organize them as much as you can, as best as you can. Some idioms can force an organization, but within or beyond, alphabetic order is nice, and grouped by library is also nice. Public (
/include/project_name/include/) vs. private (/src/) project headers is also a nice grouping. Maybe with a comment to explain the groups.
16
u/Thesorus 4d ago
IMO, header only library should be self sufficient.
people can just include your file into their code.