Header-only C and C++ libraries
I think in 2026 the most painful part of C and C++ development is the build and project management. I know CMake exists, but so does Meson, Bazel, Buck2 and so many others claiming they are the best.
Older projects still use manually maintained Visual Studio .vcxproj files, Xcode .xcodeproj and even makefiles.
For library development this is very painful, especially if the task you need to solve is very small and self-contained, like parsing a JSON file.
I remember in the past I was frequently recreating project files from scratch by simply dropping all .cpp and .h files into a folder and opening it in an IDE.
I think the #pragma comment(lib, "opengl32.lib") stays forever in my head because of this. I just hated to have to specify which libraries to link against in the project settings.
I do believe this is the Rust killer feature honestly, having Cargo.toml which is the standard and simple way to specify dependencies, build and project settings in one sane format. I really wish C and C++ had something like that, but I don’t see it happening anytime soon.
So, what solutions do we have in the meantime? Let’s say you want to quickly prototype something and need the basics, well in the table below are libraries I used in the past which are “header-only” and solve this in a very simple way. You simply drop the header file on disk and #include it in one of the source files.
| Library | Description |
|---|---|
| stb | stb single-file public domain libraries for C/C++ |
| cpp-httplib | A C++ header-only HTTP/HTTPS server and client library |
| json.hpp | JSON for Modern C++ |
| pugixml | Light-weight, simple and fast XML parser for C++ with XPath support |
| magic_enum | A header only C++ library which provides static reflection for enums |
When prototyping or starting small I usually pick one or two of these, but it is important to understand that they work best if they are used only in a single place in the project.
What I mean is the following pattern:
#pragma once
// forward declaration speeds up compilation time
// we do not need to know the enum values to get the enum name
enum class Color;
const char* ColorToString(Color color);
Color ColorForIndex(int index);
int GetColorCount();
#include <magic_enum.hpp>
#include "magic_enum_example.hpp"
enum class Color { Red, Green, Blue };
const char* ColorToString(Color color) {
return magic_enum::enum_name(color).data();
}
Color ColorForIndex(int index) {
return magic_enum::enum_value<Color>(index);
}
int GetColorCount() {
return magic_enum::enum_count<Color>();
} #include <iostream>
#include "magic_enum_example.hpp"
int main() {
for (int i = 0, count = GetColorCount(); i < count; ++i) {
Color color = ColorForIndex(i);
std::cout << "Color for index " << i << ": " << ColorToString(color) << "\n";
}
return 0;
} Color for index 0: Red
Color for index 1: Green
Color for index 2: Blue