A somewhat more recent trend in the C++ community is the popularity of header-only single-file libraries. Prominent examples are catch2
, JSON for Modern C++
. These are all great, modern and popular libraries, and I personally enjoy using all of them.
But back to the provoking title. This may be a bit of an over-generalization, and it is meant to be a little bit ambiguous. Mathieu Ropert already pointed out that header-only files are but a symptom of the whole C++ modules and package misery
. The aforementioned libraries are all great pieces of software but it is bad that:
- they are exclusively header-only
- header-only is seen as a sign of quality these days
Historically, header-only libraries have been a thing in C++ because of templates. Templates are not functions or variables that can be referenced by the linker. No, as the name so fittingly suggests, they are just templates for those, with the potential to become, or better, be instantiated into, something that actually survives the trip to the executable code. Header-only libraries used to be code that could only materialized in the context of other code.
But the focus has shifted to portability. I guess by coincidence, people discovered that header-only libraries are also relatively easy to import into your project.
It is actually about inlining
Splitting code between headers and implementation files is a trade off, one that is often synonymous with marking functions
or not. Inlining is just one more fine-tuning tool that C++ programmers have at their disposal to make the resulting application behave as they want. Carefully considering whether to inline helps to manage compile times, transitive dependencies and code-bloat.
Even for template-heavy libraries, not all of it has to to be inlined. It is often beneficial for compilation-time, code-size and run-time to use techniques such as thin templates
to make sure some of the code is properly insulated.
Promoting “header-only” as the new buzzword for portability has the side-effect of implying which code is not marked as
That is just ignorant of that dimension of the code. It is equivalent to not making a choice about insulation and inlining.
Sure, header-only is marginally better for dropping into your code, but adding a portable implementation file should be just as easy. Why not deliver portable libraries as a single implementation file and a single header instead? Those could easily be generated by a preprocessing step E.g. catch2’s single-header is generated anyways, so it should not be much harder to split that output into two files. Of course the implementation file should be able to work within your compilation environment. But the same restrictions apply to the single-header file, so there’s really no additional difficulty. And it is really easy to go from the two-file version to the single file by just marking everything in the implementation file as inline and including it in the header.