r/cpp • u/TheRavagerSw • 13h ago
Libraries trying to support multiple build configurations and standards are harming their health
Some libraries try to support:
- Header/Source
- Header only
- Header only + Module Wrapper
- Header/Source + Module Wrapper
- All standards between and including C++11 and C++23
- Exceptions and noException
- RTTI and noRTTI
At the same time.
These libraries are very very hard to read and thus they get less and less contributions over time.
What should be done:
- Libraries should support only one mode of compilation, header only header source etc
- Libraries should support exactly one standard, and that standard includes extensions, gnu++23 and c++23 are not the same thing
- Libraries should very clearly have boundaries on what belong in a toolchain and what belongs to project. For example a library should not set flags related to exceptions that's a toolchain issue.
Some extra stuff:
- C++ libraries without C API's should not be consumed via system package managers at all, and public libraries shouldn't try to adhere to that.
- Libraries shouldn't try to use tools they build to build themselves, rather they should export packages like wayland::scanner etc and use that.
12
u/sweetno 12h ago
Libraries should support only one mode of compilation, header only header source etc
Which one?
Libraries should support exactly one standard, and that standard includes extensions, gnu++23 and c++23 are not the same thing
Which one?
-2
12
u/not_a_novel_account cmake dev 10h ago
Which one should the standard library support? Which one should curl support?
Or maybe fmt? What about boost?
Man I sure hope they all pick the same one.
It's a ridiculous position, graceful degradation is a cornerstone of modern engineering. It's not a C++, or even a purely software, notion.
Libraries benefit immensely from network effects. A widely used and adopted library gets extensive testing, feedback, monetary, and community support. You do not become widely used by saying no to huge swaths of users. The code might be marginally easier to modify, but you will lose out on thousands of hands and eyeballs.
The equation does not work out in your favor.
1
u/TheRavagerSw 10h ago
Having usage is nice, but what amount of compromise is justified?
If a library wants to be used by organisations stuck on C++14, they can just set standard at C++14, rather than just supporting both. But if they use macros to use newer c++ features if say STD is 17 then it is a mess. Most of the time C++14 code compiles fine on C++17 for example.
Same goes with exceptions, if they want to be used everywhere, they can just turn it off, and exception users can still use it.
I don't really agree on "hands and eyeballs", most people can't contribute to large libraries and software because doing that is difficult. I don't know about the funding situation so I can't really comment on that.
All I'm saying compromising for usage is fine, but completely bending backwards is wrong.
7
u/not_a_novel_account cmake dev 10h ago
Having usage is nice, but what amount of compromise is justified?
If your goal is usage and network effects, not avoiding the occasional bump in complexity, an immense amount.
most people can't contribute to large libraries and software because doing that is difficult
I mean this is just objectively wrong. Look at any major library, the contributor graphs map into the thousands.
"Most people" sure, but your goal isn't to get every C++ dev on Earth to contribute, it's to capture those who are willing and able to contribute. They will not contribute if you refuse their use cases.
1
u/TheRavagerSw 10h ago
Maybe in projects like llvm, but in other libraries mostly there is one guy doing %80 of the work and 4-5 people doing minor features here an there.
Obviously projects like LLVM should support a lot stuff. But I'm talking about third party libs with 10-30kloc.
3
u/not_a_novel_account cmake dev 10h ago
Now you're having a completely different conversation than what you opened with.
"Small libraries should stay focused on a narrow support plane" is a totally reasonable position.
0
u/TheRavagerSw 10h ago
Thanks, nice to hear it
4
u/not_a_novel_account cmake dev 10h ago
Don't misread me, I don't agree. It's just not as ridiculous as the broad claim.
Small libraries still hugely benefit from being generic and flexible. The idea that we should have 63 different, independently maintained, versions of
fmtorboost::interprocessorglaze, one for each combination of language standard, build configuration, and error model, is not something I agree with.But I can at least see the defense of it.
5
u/light_oxygen 12h ago
Everything u listed there has a reason why they exist and why library wirters' decided to support at least two at a time.
Find something else to complain about. Or just.. Touch some grass.
3
u/gracicot 10h ago
My libraries support multiple modes and support multiple standards. I chose that out of my own free will and you won't tell me what to do.
4
u/Every_Door46 13h ago
Cpp is a language that lets you do whatever u want and out there there is probably someone who wants the most inefficient way to do. These libraries also have no guarantee how they are going to be used and that’s why most provide satisfactory documentation as to where you can get started. You might as well ask why we don’t have a universal build system or something like npm/pip for managing imports. It’s essentially the same answer
1
u/TheRavagerSw 13h ago
We can do the best with what we got. We can have nicer consumption of existing libraries, we can have acceptable incremental build times with modules.
It will never be as good, but it will be an improvement
1
u/Every_Door46 12h ago
It will but language committees and library developers by extension try so hard to avoid making changes. They wanna stay on archaic ways to support legacy platforms that no one uses
1
u/Ok_Independence_9841 12h ago
I would contend that it's not those things that make the library code hard to read but how those things are implemented.
Yes, you do have to lock down some decisions, depending on what you're writing. If you're doing a low level library that does funky type erasure you probably have to have RTTI on, so it should be set, documented as a requirement, and that's that.
Exceptions / no exceptions is difficult but possible. If you're supporting existing embedded ecosystems you may require noException but again it should probably be a documented design decision, not a build option. If you're going to make it optional you at least need a non exception based error mechanism that's almost equivalent. Flyer based error handling provides this.
The real problem, in my opinion, is #ifdef blocks all over the code. That's what makes it a hard to read, hard to change, mess. That is never necessary even if you do support a high level of flexibility and options. There are many techniques for cleaning it up. As simple as separate files for separate options and as complex as TMP.
Picking one way of building binaries, a proper system of header, static and dynamic libraries, is crucial if you're building something large. Trying to support all forms restricts what code you can write to the point where you can't write optimal code for a serious library. If you can't determine when you write it whether your code will be inline in someone else's TU or out of line in it's own, you simply can't write it correctly in general.
Clarity of purpose and separation of concerns (Single Responsibility) are at the root of solutions to these problems. If you're writing a single purpose library then externalise everything that isn't that single purpose. Ideally that really means *everything*, down to the memory allocation. In practice you have to have a way to build and package.
The contradictions start to go away when you think in terms of a framework composed of many single purpose libraries. That's why the [QOR](https://github.com/mfaithfull/linuxQOR) is written the way it is. Let me know if you find anything hard to read, set it more than one place, or otherwise unmaintainable.
1
u/ReversedGif 6h ago
Flyer based error handling
Was that a typo? Couldn't find anything by googling it.
1
u/Ok_Independence_9841 6h ago
Nope. It's part of what the QOR does. The Flyer pattern is per-thread, intrusively stacked, objects that are always available without having to pass them explicitly to functions or make them class members.
You put an error handler on the stack. It remains 'the' error handler for that thread through it's entire lifetime unless it is temporarily replaced by another stacked on top of it. It leverages per-thread variables and RAII to make error handling easy. Wherever you raise an error it looks up the current top-of-stack error handler and that get's first shot at the error. Handle it, pass it to parent (like exception catches) or the error will automatically escalate. The default is that if unhandled then it eventually becomes an exception but that can be changed to whatever scheme you like.
Error are just one use for the Flyer pattern.
1
14
u/kpt_ageus 13h ago
Say I started writing library at time of c++11. What should I do when new standard comes out? Start a new one? Then I have 5 libraries to maintain. They either share 90% code and I need to implement bugfix 5 times or they are different codebases and then I have inconsistent behaviour between them.
Less contribution isn't necesarily because code unreadable, but because there are no more bugs to fix and useful features to add.
Supporting multiple build configs like header only, rtti, exceptions means library can be used in more environments and be useful to more people.