It seems like header-only libraries are all the rage in C++ these days. In this post, we'll see a useful way to organize your header only library and generate a CMake target that others can "link" to. We will make use of CMake's INTERFACE library feature to do this:
An INTERFACE library target does not directly create build output, though it may have properties set on it and it may be installed, exported and imported.
Hopefully by the end of this post you will understand why this is so useful!
The Directory Structure
There are a few options here, but in general it makes sense to have an
include directory for all the header files in your library.
You may then place your headers directly in the
include directory, as in cxxopts.
Or you may choose to create an additional subdirectory with the same name as your library's namespace, as in type_safe.
So for a project that links with your library, a developer can
#include in one of two ways:
#include <cxxopts.hpp> #include <type_safe/strong_typedef.hpp>
Examples and test files should go into their own directory and not pollute the
This provides two benefits:
- It is easy to use CMake's
- It is easy to install the
includedirectory into a destination
Of course, this is not set in stone. For example, nlohmann's json library places its header files in a
But tests and benchmarks are still relegated to their own directories.
Creating a Header-Only CMake Target
Creating an interface library in CMake is very straightforward:
By specifying INTERFACE as the second parameter to
add_library, we are no longer allowed to provide source files since the library is not meant to generate any build output.
However, if you would like your library to be IDE friendly, then you can specify the source files using
target_sources, which is done by the type_safe library.
target_sources was added in CMake 3.1 and allows headers that are not part of the executable target to be displayed in IDEs.
(There are also other benefits to
Once you've created a target for your header-only library, you should at the very least specify its
If you followed the directory structure above, then it is as simple as:
target_include_directories(my-library INTERFACE include/)
We specify INTERFACE again here so that future targets that link with our library will inherit our library's include directory.
This is extremely convenient and is the main reason we are creating an INTERFACE library.
We can continue to use INTERFACE when using
target_compile_definitions, etc. so that we can propagate dependencies to targets that link with our library.
In other words, the only requirement another developer has in using your library is to:
Doing so will ensure that any INTERFACE-marked include directories, compile definitions, other linked libraries, etc. are also added to
INTERFACE versus PUBLIC versus PRIVATE
It may be handy to understand how INTERFACE, PUBLIC, and PRIVATE behave. If PRIVATE is specified for a certain option/property, then that option/property will only impact the current target. If PUBLIC is specified, then the option/property impacts both the current target and any others that link to it. If INTERFACE is specified, then the option/property does not impact the current target but will propagate to other targets that link to it.
This makes INTERFACE very useful for header-only libraries that aren't compiled (on their own), but rely on other libraries they need to link to.
target_link_library on our header-only library with INTERFACE specified means that the executable that eventually gets compiled with our library will have the appropriate link flags.