11 ms·
Single_file_libs: List of single-file C/C++ libraries
- sxp 6y agonothings' guide on how to write single-file libraries is informative if you want to write your own: https://github.com/nothings/stb/blob/master/docs/stb_howto.txt https://github.com/nothings/stb/blob/master/docs/stb_howto.t...
- spacechild1 6y agoUsing something like #define LIBRARYNAME_IMPLEMENTATION is a hack that is only necessary for C, because in C++ you can just mark all functions "inline" and there will only ever be a single implementation, no matter how often you include it.
- flohofwoe 6y agoPlease don't, inline code in headers is the single reason why C++ compiles so slowly, and besides: the inline keyword is also available in C (since C99), but that doesn't mean it's a good idea to use it: The IMPLEMENTATION define cleanly separates the public API declarations (which are required in each source file where functions from this library are called), from the implementation code and any implementation-private declarations. If you put all the implementation code into inline code, this code is parsed over and over again in each source file which would only need to include the declarations. This is the main reason why C++ projects compile so slowly, and what gives "header-only libraries" such a bad reputation in the C++ world. "STB-style single file libraries" don't suffer from this problem.
- spacechild1 6y ago> the inline keyword is also available in C (since C99) but it doesn't work the same way. > If you put all the implementation code into inline code, this code is parsed over and over again in each source file That's true. Good header-only C++ libraries provide "forward" headers, which you can use if you don't need the full definition. But the best thing is to only write C++ header-only libraries if there is a technical reason (e.g. heavy templated code). > "STB-style single file libraries" don't suffer from this problem. That's right, but I also don't see the benefit. To me, telling the user to add a #define to some random source file feels incredibly hacky. On a technical level, it's the same as asking the user to #include a ".c" file. Why not put the implementation in a single ".c" file, which the user can add to their project? I honestly don't understand this...
- eps 6y agoWhoever maintains this needs to pay a bit closer attention to _how_ single-header libs are implemented. In particular, stuffing non-inline functions into .h is, ultimately, a malpractice. Yes, you get to have a single .h, but you end up creating an instance of each function for every .c that includes this .h. Example - https://github.com/tronkko/dirent/blob/master/include/dirent.h https://github.com/tronkko/dirent/blob/master/include/dirent...
- rsaarelm 6y agoThat's why you wrap the function implementations in an #ifdef and instruct the library user to set the corresponding define in exactly one place, like the list maintainer does in his own single header libraries: https://github.com/nothings/stb/blob/b42009b3b9d4ca35bc703f5310eedc74f584be58/stb_image.h#L4-L13 https://github.com/nothings/stb/blob/b42009b3b9d4ca35bc703f5...
- eps 6y agoSure, that's an option.
- jb1991 6y agoIt’s not just an option, that’s how most header only libraries are explicitly created.
- eps 6y agoI don't disagree. My top comment was that if you are maintaining a list of single-header libs, might also pay attention to whether they are implemented properly.
- spacechild1 6y agoI haven't used a single C++ header-only library which required a #define. But that's because in C++ the "inline" keyword creates a weak symbol, so you can #include a header in several places and there will only ever be a single implmentation. For function templates this happens automatically, btw. IMO, if you need a #define like this, you're are not really writing a header-only library, that's basically just a source file in disguise. You could just as well distribute headers + a single source file and just ask the user to add this one source file to their build system (which should really be a trivial task). EDITED for more clarity about C vs C++.
- sigjuice 6y agoI 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.
- huhtenberg 6y agoIt 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.
- rogual 6y agoSometimes 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.
- jb1991 6y agoYet there are very many substantial libraries that go well beyond small tasks that are implemented this way, some of them very popular.
- flohofwoe 6y agoSingle-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).
- huhtenberg 6y ago
- EdSchouten 6y agoAll of that to compensate for the fact that the C/C++ community never came up with a decent & standardised build system. I never have to deal with issues like these when writing software in Rust & Go.
- jb1991 6y agoSo you’ve never had problems with vendoring in Golang? At least this particular strategy makes it trivial to have different versions of different libraries in different projects on the same machine, each project entirely manages its own dependencies without any reliance on language specific tools. That’s why this kind of library format is so extremely popular.
- scoutt 6y agoI agree that there is no easy way when it comes to build systems on C/C++ (other than known environments for specific platforms, like Visual Studio). Also, following the reasoning line of your comment you can make a parallel conclusion: the C/C++ community and level of adoption are so big that the people of Rust & Go had to put together a "decent & standardised build system" to gain traction and consideration.
- acuozzo 6y agoI can only speak for C. With that being said, having a standardized build system would be nice, but it wouldn't solve the underlying problem here which is that portability is often not regarded as important enough to merit thinking about and designing for at the start of a project. The consequence of this is that it creates a system in which C programmers have very little trust in one another's code. I write a lot of C for ARM and I am often hesitant to use libraries that I can't inspect in a reasonable amount of time because I have been burned by e.g. liberal use of type punning which results in bus conflicts as it's idiomatic and works just fine on x86, but is ultimately undefined behavior in most circumstances. (Rant: Just use memcpy, please! Argh!)
- AHTERIX5000 6y agoThese can be quite useful when prototyping indeed but please keep in mind that they are not the most robust code around in terms of security and UB if you are actually shipping a product using these.
- ATsch 6y agoI remember fuzzing stb_truetype and it finding hundreds of trivial segfaults instantly. Perhaps they fixed some but I can't advise using these with any untrusted data, it's obvious security is usually not a priority.
- sherincall 6y agoThere is now a big fat disclaimer at the top of the file saying basically the same thing: https://github.com/nothings/stb/blob/master/stb_truetype.h#L4-L11 https://github.com/nothings/stb/blob/master/stb_truetype.h#L...
- ATsch 6y agoAh, that's good, despite being six years late. However, I do not think there is any good reason for code written like this to exist in the first place. Bounds checking is easy and fast (even if less so in C) and things you consider "trusted" can become untrusted easily and quickly turn otherwise minor bugs like file type confusion into critical vulnerabilities.
- ludocode 6y agoI mostly agree with you. An important qualifier is that these libraries are meant for games, especially games that package their own font files, and especially games that are on locked-down platforms like consoles and mobile phones. On console games it makes sense to exclude bounds-checking your own data because you want to minimize load times. But yes, bounds checking should have been included and have been on by default with an option to disable it.
- petterparker 6y agothis one might fit the description as well: Rax is a radix tree implementation initially written to be used in a specific place of Redis in order to solve a performance problem, but immediately converted into a stand alone project to make it reusable for Redis itself, outside the initial intended application, and for other projects as well. https://github.com/antirez/rax https://github.com/antirez/rax
- jb1991 6y agoThis is not a single file library so it doesn’t quite fall under this category of source code. Most or perhaps all of the nothings files are considered “header only“, something that can be very convenient for adding libraries.
- raphaelj 6y agoAm I the only one to see this single-file lib trend as a consequence of the failure of the C++ ecosystem to produce a modern and unified package management platform? Managing dependencies in C++ is a nightmare, and I'm not even talking about the lack of actual modules inside the language itself. It's so bad that people (incl. me) tend to prefer to copy-paste libraries source code instead of setting up a dependency. A Python, Ruby, Node or Java project would never have to head toward such a poor solution instead of using their respective package managers. But in C++ we do because the alternative (e.g. Conan) is such a nightmare to use.
- flohofwoe 6y agoExactly, and what's especially elegant about single-file-libs is that they solve a real-world problem without requiring any new tooling, and you don't need to convince anybody to use your favourite build system or depedency manager (which is the main problem - you need to have a single standard solution right from the start, any time later is too late). PS: the original motiviation for the STB single-file libs was actually that Windows doesn't have a default place where dependencies are installed: https://github.com/nothings/stb#why-single-file-headers https://github.com/nothings/stb#why-single-file-headers
- humanrebar 6y agoIt helps with the problem, but doesn't solve it. Just because you have five header files instead of five packages doesn't mean you can use all five headers at the same time. For example, some might require C++03 and some might need C++14 or newer. Also, if all dependencies are single file with no non-standard dependencies, that means none share dependencies they should, like threading, telemetry, or logging libraries.
- CyberDildonics 6y agoDo you have examples of this or is it just a guess? Anything in its own compilation unit would be isolated. Anything written using C++03 should compile without too much trouble in a more modern compiler. Also threading is part of the C++ standard library. I don't know what you mean by sharing dependencies on telemetry and logging. That sounds like adding questionable complexity to libraries that are simple and modular.
- todd8 6y agoI remember looking over Niklaus Wirth’s source code for a compiler for his new language Pascal. This would have been around 1975. I was stuck by the fact that it was one big single source file. Later I came across a quote where he said something to the effect that what’s the point of a linker that slower than the compiler. Sometimes I think what’s the point of this tangle of make files (or cmake/conan or whatever). It’s more difficult to get right than one big single source file.
- psykotic 6y ago> It’s more difficult to get right than one big single source file. For all my personal projects, I use a single main.c file which #includes the topologically sorted .c files for each module, one file per module, preceded by a shared #include block for external library headers. If your module dependency graph is acyclic, you don't need any per-module headers; inside a module everything is sorted and you only need forward declarations for mutually recursive functions/types. The only real downside for me is that it breaks 'static' isolation. Even the slower C compilers like gcc, clang and msvc can build 50-100 kloc/sec on my aging laptop with code structured like this. You get qualify of life benefits like fast, simple builds (one-liner scripts for building on each platform, no need for the compiler to chew through the same monstrous system headers for every .c file, fewer symbols for the linker to resolve) and less boilerplate you have to write (no redundant copies of declarations in headers, no redundant #include blocks at the top of every .c file). In case it's something you care about, structuring your code like this also makes it trivial to automatically amalgamate your entire project into an stb-style single-file header library for distribution purposes: it's already 'static' safe, you've already specified the topological order and you've already eliminated all the redundant boilerplate you wouldn't want in a single-file distribution, so it's mostly just a matter of writing a script that inlines the #include "foo.c" directives in the main.c file.
- z0r 6y agoThis is missing my favorite header only library, cppitertools. I think it would qualify if it was considered a set of single header file libraries (one per iterator pattern), but since it has a dozen to choose from I guess it doesn't make this list.
- umvi 6y agoI think this chart would be more useful if the full name of the project were used instead of the file name (i.e. "nlohmann/json" instead of "json.hpp"), and also including the number of repo stars as a column. Just looking through the list of single file JSON libraries, "nlohmann/json" is buried in the middle (most popular C++ json library) and surrounded by small projects.
- DoofusOfDeath 6y agoI'm generally not a fan, but header-only libraries do make it easier to ensure that specific compiler/linker flags are consistent across your code and the library's code. Some examples of when this is helpful: - using gcc's / clang's sanitizer frameworks - tweaking builds for deep-dive optimization and/or debugging - letting the compiler target a specific hardware architecture, e.g. `-mavx512f`. I guess this highlights a limitation in typical Linux systems: a given library can be built with many different configuration options, but there's no (widely known?) way to have all those variants installed at the same time. I think fixing this would require more than just changing cmake, apt/dpkg, or the C++ standard. A clean solution might also require changes to the ELF standard and/or Linux's compilers, linkers, dynamic loaders, and debuggers.
- gmadsen 6y agowhat is the advantage to a single header instead of 10 headers and maybe a main header file? I really don't like looking at 10k+ line files..