Hacker News new | ask | show | jobs
by slovenlyrobot 2377 days ago
"header only" is C-speak for "up and running in 15 seconds" more or less. One major concern with integrating a new dependency in C land is how much of a pain in the ass it makes your build, especially considering a large chunk of C projects do not target typical desktop environments. After the first time you lost a day trying to get some batshit custom build tool to emit the correct link flags for your exotic ARM board, "header only" carries a lot of weight.

On the intermixing of source and header files, it doesn't quite work the way you mentioned. It's not possible to just cut-paste any old public object definition into a header file without at least applying 'static' to it, and that only works where the object truly is private to each translation unit. It's not so easy e.g. to instantiate a global variable shared across the whole program this way, so 'header only' also implies 'almost certainly free of globals', which is a good sign of hygiene

edit: yikes, in this case, the implication is totally invalid

3 comments

Hijacking your comment to ask my question below. Apart from the possibility you can't do header-only for everything (like something that requires a singleton for example) and the other known issues (build times, ballooning of object files and thus executables) is the only other reason you don't see as many header-only libraries in C (like you do in C++ where they are ubiquitous) is that people merely don't do it in C? That is, a social landscape reason, not a technical reason.
It's partly a historical reason as well. There are many projects with very complex Makefiles. Those are hard to integrate into your own project.

Header-only is the other extreme: No makefile at all. With package managers like Conan and vcpkg, using a build-system like e.g. CMake, it's possible to have very simple and short project files which are easy to integrate.

In this regard, C and C++ are a bit behind the times compared to other languages.

Isn't C++ supposed to be getting proper namespaces soon?
I am not an expert in C++, but there exists an opinion that it will not help as much as you might think.

https://cor3ntin.github.io/posts/modules/

The solution for the "singleton problem" is to put the implementation code into a separate part of the header behind an #ifdef XXX_IMPL, and only define this before including the header in one place in the project.

This is also the approach used by this library, see:

https://github.com/jeremycw/httpserver.h#example

This approach also doesn't have the 'build time problems', since the (expensive to compile) implementation code is only seen once by the compiler (unlike many C++ header-only libs, which rely on inline code)

The fact that you cannot define templates out of headers also explains why C++ has a lot of header-only libraries. : if you use templates and have no global references, you are most likely already header-only.
Yeah, templates forces many C++ libraries to be header only. I suspect this came first, and only after a c++ header-only (because they had to be) libraries existed did people realize there were advantages to header only and so people wrote them where they wouldn't have to.
Yes, the social reasons are the only ones, besides the technical reasons :)

An additional technical downside in this case is the code complexity in the client code, from the required modal preprocessor flags.

"header only" is more like "I don't want to learn"-speak in regards to deal with native language toolchains.
The native toolchain on my embedded board is a mess. You don't want to learn to deal with it if you don't have to. This comment applies to every embedded toolchain I've ever worked with. Even doing embedded linux with yocto is a mess of a toolchain and it is the best attempt I've ever seen at creating a good embedded toolchain. The problem is just messy.
Usually that boils down to "we don't want to bother with the vendors IDE", e.g. MPLAB.

I know C and C++ since 1992, used them across multiple OSes, hardly seen anything that would motivate the fashion of header only files.

You've never in 28 years lost a single day to a compiler/linker flags/version mismatch and some random tool? I find that difficult to imagine. The horrifying alternative would be that such situations had been encountered but considered justifiable productive work
No, because I either used what was provided by the OS vendor, or library providers that were selling libraries for the deployment we wanted to do.
Well, you can do:

    int a;
    int a;

    static int b;
    static int b;
And it's a valid way to define a single global/static symbol called `a`/`b`.
They have different semantics. The static one can only be referenced in the current translation unit, whereas the non-static one is a global which can be referenced from any translation unit.

The problem is that in the header-only case defining "int a" means you can only import the header from a single source file. With "static int a" the visibility is wrong. And if you did "extern int a" linking would fail because the symbol is never assigned to a translation unit.

Header-only libraries sometimes require you to define those externs in exactly one "impl" file, which you compile and link to your artifact. Something like this:

    // libfoo_impl.c
    // or in some other translation unit, such as main.c
    #define LIBFOO_IMPL
    #include "vendor/libfoo.h"

    // main.c
    #include "vendor/libfoo.h"
    
    int main() {
      foo_inc();
      printf("%d\n", foo_count);
      return 0;
    }

    // vendor/libfoo.h  
    #pragma once
    extern int foo_count;
    void foo_inc() {
      foo_count += 1;
    }

    #ifdef LIBFOO_IMPL
    int foo_count = 0;
    #endif
main.c and libfoo_impl.c both include libfoo.h, which declares foo_count with the right linkage, but the variable is only defined once, in whichever translation unit defines LIBFOO_IMPL.

Occasionally you'll find a header library which supports this _IMPL paradigm as an option to avoid inlining its functions in every translation unit that calls them.

You're misunderstanding. I'm saying you can have multiple identical `int a;` across multiple translation units and it will point by default to a single global variable across the program. So you can include a single header from multiple places, and you just get a single shared global variable, if the header contains `int a`.