Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This is as cool as it gets.

I have a honest question about the source code, a lot of time since I coded anything meaningful in C, but I remember the header files did not have that much code on it, while here I see most of the code is in the header file. Why is this the case? Inline compilation? What are the advantages?



There is a strong tradition of single-file C/C++ libraries [1] because it has been difficult to integrate larger C/C++ libraries into the codebase.

[1] https://github.com/p-ranav/awesome-hpp


There's an important difference between STB-style 'single file libraries', and C++ 'stdlib-style' header-only-libraries: STB-style libraries put the implementation code into a separate section inside an "#ifdef IMPLEMENTATION" block, while most C++ header-only-libraries use inline code (and thus pay a higher compilation cost each time this header is included).

STB-style libraries skip the implementation already in the preprocessor and compile the implementation only once for the whole project.


So what's the compelling reason to ship these as a single source file, instead of using the more logical and standard solution of a single header and a single source file?

Unlike C++ header-only libraries, C libraries like this still need their implementation included exactly once.

I understand the benefits of keeping the implementation in a single source file (simplifies integrating in existing build systems, interprocedural optimizations, etc.) but combining the source and header files too makes them awkward to use, with little benefit beyond being able to claim "our implementation is only 1 file!" which is about as impressive as saying "our implementation is only 1 line!" if you've just deleted all the whitespace.


> So what's the compelling reason to ship these as a single source file, instead of using the more logical and standard solution of a single header and a single source file?

C doesn't have a packaging format or dependency manager, so having to keep track of two files would be pretty cumbersome.


In general, STB-style headers simplify C/C++ build system shenanigans.

E.g. one benefit is that you can configure the implementation via preprocessor defines without the defines leaking into the build system. Just add the config defines in front of the implementation include.

It's also trivial to write "extension headers" which need to directly peek into the (otherwise private) implementation of the base library. Just include the extension header implementation after the base library implementation.

PS: but also see: https://github.com/nothings/stb#why-single-file-headers


> In general, STB-style headers simplify C/C++ build system shenanigans.

Too vague. How is it better, concretely?

> E.g. one benefit is that you can configure the implementation via preprocessor defines without the defines leaking into the build system

That's not a benefit. How is this:

   #define LIB_IMPLEMENTATION
   #define LIB_KNOB 123
   #include "lib.h"
Any better than the two-file version:

   #define LIB_KNOB 123
   #include "lib.c"
?

> It's also trivial to write "extension headers" which need to directly peek into the (otherwise private) implementation of the base library. Just include the extension header implementation after the base library implementation.

I'm not sure what this means. Can you give an example? Is this being used in the above library?

> PS: but also see: https://github.com/nothings/stb#why-single-file-headers

OK, that only offers this explanation:

> Why not two files, one a header and one an implementation? The difference between 10 files and 9 files is not a big deal, but the difference between 2 files and 1 file is a big deal. You don't need to zip or tar the files up, you don't have to remember to attach two files, etc.

This is kind of nonsense to me. The difference between 2 files and 1 file is barely relevant, and doesn't weigh against the awkwardness of having no clear distinction between declarations and definitions, and having to pull in definitions by setting a magic preprocessor header.


    > Any better than the two-file version:
    >    #define LIB_KNOB 123
    >    #include "lib.c"
See, now you have 3 files, the .h/.c file pair of the library, and your own implementation source file with the configuration which includes a .c file. How is this better than having two files?

> Too vague. How is it better, concretely?

I can only assume from that question that you haven't had the "pleasure" to work much with C/C++ build systems yet, at least when it comes to integrating 3rd party libraries.

Re. extension headers:

    #define IMPLEMENTATION
    #include "base_library.h"
    #include "extension_library.h"
Since the implementation of the base library is in the same compilation unit as the extension library, the extension library has direct access to the private implementation details of the base library without having to add a separate 'private but actually public API'.

> no clear distinction between declarations and definitions

STB-style libraries have this clear distinction, C++ style header-only libraries don't.


> See, now you have 3 files, the .h/.c file pair of the library, and your own implementation source file with the configuration which includes a .c file.

The third source file is what the library authors suggested; I'd just create a seperate Makefile target for the implementation, which is then linked into the binary. (Yes, that's an extra target, but that costs very little and allows you to avoid unnecessary rebuilds.)

Why don't you give me a concrete counterexample where the single-header-setup makes things simpler? If it's so obviously beneficial it shouldn't be so hard to come up with a concrete example.

> #define IMPLEMENTATION > #include "base_library.h" > #include "extension_library.h"

Thanks for explaining, but that doesn't really motivate having a single file, since you could just as easily do:

  #include "base_library.c"
  #include "extension_library.c"
And you've saved a line (and removed the need for obscure preprocessor macros).

> ... without having to add a separate 'private but actually public API'.

Personally, I'd say this is exactly the right way to model this, but even if you disagree: it sounds like this simplifies things mainly for the library/extension authors, and not the library users, because if the extension author forward-declares the implementation details they depend on, there is no additional file to manage for the users either.


> The difference between 2 files and 1 file is barely relevant,

To you. To me doing Rust stuff, it's been quite helpful to have single-file C programs; it makes it a lot easier to compile and link things.


Packing the entire library in a header file is a somewhat recent and much welcome trend.

See for example:

https://github.com/nothings/stb

https://github.com/nothings/single_file_libs


I wouldn't call it recent. Boost has been doing it since it was started in the late 1990s.


Boost or C++ stdlib headers are different than STB-style single-file-libraries though in that the implementation code is inline and needs to be parsed each time the header is included.


Boost did it because C++ template definitions must be in headers.

That seems unrelated to this idea that C code using it is equally reasonable. That's a developer choice.


The original C++ templates used to be entirely contained in the headers. I assume that is no longer the case, but it has been a long time, since I wrote C++.

When I was writing template SDKs, the headers were pretty much the entirety of the library.


Single header libraries are hugely more ergonomic and easy to use. Just get the file, #include it and you're done! Most C libraries on GitHub prefer this mode of distribution now


>> Single header libraries are hugely more ergonomic and easy to use. Just get the file, #include it and you're done! Most C libraries on GitHub prefer this mode of distribution now

I find that odd. How is that significantly better than dropping 2 files into a project and #including the header? A good build system should just compile all the C or C++ files in a given place, so nothing needs to be changed other than dropping the files in and #include where called from.


The difference will be painfully clear once you try to walk a junior developer through installing and linking new files to a large project in Visual Studio, vs "copy this .h file here and include it".

Whether or not a "good build system" should handle it, the fact that single-file libraries are much preferred these days should demonstrate most people don't have such a build system


So much of the C/C++ ecosystem revolves around knowing your build tools that, for most intents and purposes, it should honestly be considered an integral part of the languages. If I ever teach an introductory C/C++ course, I would dedicate at least 1/4 of my time to explaining:

* What a translation unit is, and how compiling a translation unit to an object file works

* What a linker is, and how the linker combines object files into a library/executable

* What make/autotools/cmake are, and how they work

* What FHS is, and how it standardizes the lib/bin/include paths that C/C++ build tools read from/install to

And so on. Any C/C++ course that goes beyond a “Hello world” program without explaining these concepts in detail does its students a disservice.


Yeah, in our class we had to figure out this mostly on our own, so after it I'm still at the level where I barely manage to make make work, I'll have to look into those other ones, thanks !


its not 'significantly better'.

but also ask "why are the tables of content prefixing the contents of all my books?"

its taken ages, but with this model, c lib authoring has finally taken something that was a core strength of pascal: single-file modularity.

though in theory there's no difference between theory and practice, in practice, there is. and this kind of packaging makes a lot of sense for a lib like this.


Why use two files instead of one?


What if you want to use the functions in other C files? What if you want to compile them all separately?


1) Include it wherever it's used. The convention is to use the preprocessor[1] so it only appears once in the final binary

2) Most developers just want to compile to a single binary. Any developer who for some reason needs separately compiled objects should be able to quickly achieve that from a single-file library

[1] https://github.com/phoboslab/qoi/blob/324c2243b2fb35f741a88d...


Maybe you've been living under the same rock as me. I was under a similar impression untill I decided to learn cmake last week.

I was blown away by its simplicity and power.

You can download almost any C/C++ library from GitHub then add two lines to your cmake file. It will then compile and link with your project.

Blown away.

Cmake can generate project files for Visual Studio, makefiles, and countless other formats. Better yet, you can open a cmake folder directly in Visual Studio. No need to generate a project :)

This comes as no surprise to anybody, I'm sure, but cmake is great.

It was tricky to find good information about it though. It has been around forever, but the nicer features were added recently it seems.




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

Search: