Hacker News new | past | comments | ask | show | jobs | submit login

I honestly don't understand this trend of single-file libs. Why not provide .c and .h files? If I already have a project with several .c files then surely I can add one more to whatever build system I have? I would rather I didn't have to add questionable #ifdefs each time I add a new file.



It's easier for simple command line tools which just consist of a single source file (or generally projects so simple that they don't need a build system), and at the same time it doesn't add any overhead for integration into more complex projects compared to .h/.c pair. In general, more problems are moved from the build system into code (mainly the configuration, you can configure the implementation via defines in the source code in one place before including the implementation instead of passing them in via command line arguments (meaning more complexity in the build system), or a separate config.h header.

I tend to think of single-file libs as a "poor man's module system". The entire "module" is in a single file that you drop into your project, and you're ready to go (and ironically it's a lot more straightforward than the C++ module system).

The real-world differences between single-file and header/source pair aren't such a big issue as many people make it out to be though, either way is fine. The actual problem is libraries that consist of dozens or hundreds of source files and a complex build system setup.


It is form over function.

This works for simpler libraries and templates, obviously, but once the function becomes less trivial, it should really be going into a separate source file.

Readability and manageability aspects aside, keeping implementation in a header causes all dependencies of that implementation to be pulled into your code as well. Including the heavy platform-specific stuff. If I need to do some string conversion, I really don't care for the implementation sneaking in <windows.h> into my sources just so that it can call WideCharToMultiByte.


stb actually went to extreme lengths on his SDL/GLFW-like library where he didn't even depend on windows.h, i.e he put just the structs and function(pointers) he needed into the header (under stbxxx_ prefixes), hardcoded magic numbers instead of windows.h's #defines, bootstrapped with __declspec(dllimport) GetProcAddress (and even that symbol can be omitted with some trickery, there is a way to get to kernel32.dll functions from any .exe)


Sometimes it's not quite so bad, because the single file will contain both implementation and header, with an #ifdef to switch between them, so you're supposed to make your own source file which uses a #define to include just the implementation part.


Single-file-libs done right have the implementation in a separate #ifdef/#endif block. They don't pollute your code any differently than a regular .h/.c setup would (at least outside that one source file which includes the implementation).


> They don't pollute your code any differently than a regular .h/.c setup would.

For that to happen you'd need to have a separate .c to include just that .h with IMPLEMENTATION defined. So in the end there's still an .h and a .c, except the .c is now completely superficial.


Yeah I just added that while you wrote your comment, sorry.

You can still put multiple implementations into the same source file and compile that into a library, for example:

https://github.com/floooh/sokol-samples/blob/master/libs/sok...

That's what I usually do for projects that don't just consist of a single source file (e.g. simple command line tools).

An actual advantage of this approach is that you can add any configuration defines in that same source file, instead of passing them in from the build system via compiler command line args.

Also for bigger projects you can have dozens or hundreds of header files but only a small number of source files (e.g. one source file per "system" or split by change frequency, or by any other criteria (like whether the implementations include Windows.h or other system headers). A small number of compilation units means fast compile times (since it's essentially a unity build), but at the same time you have enough control over the project's file structure to balance compile times vs "namespace pollution" vs what changes trigger a rebuild.


You can avoid the .c file by building the .h and passing the compiler a -DIMPLEMENTATION option.


Yet there are very many substantial libraries that go well beyond small tasks that are implemented this way, some of them very popular.


Way simpler to integrate into your project. If there are multiple options, I'll chose the single header lib any day. If there aren't and the library turns out to be cumbersome to integrate, I may just reinvent the wheel or do something different. E.g., I'd never ever integrate boost ever again into any of my projects and just reinvent the wheel instead or just not do whatever would have required boost.


This was harder to say back in 2003, but these days a lot of boost is already integrated into standard c++ anyway. We already have refcounting smart pointers, a unified threading model, ways of handling datetime, etc.


I agree. The concept of single-file or header-only libraries is often abused. Except when generics are involved, having a .c/.cpp source file is preferred. Note that while the repo is named "single_file_libs", many libraries in it consist of a pair of .c and .h files. These developers also agree with you.


just came here to say that I don't understand it, either. What's so hard about adding a couple of source files to your project?


> questionable #ifdefs

There’s nothing questionable about this practice, it’s pretty widely used throughout the industry.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: