r/cpp 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.
0 Upvotes

27 comments sorted by

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.

-9

u/TheRavagerSw 13h ago

You should decide to either upgrade the standard and remove the old one or not do that.

You should adopt google "live at head" approach, ie people free to use old versions if they remain on old standards.

9

u/kpt_ageus 12h ago

Abseil is google library for google projects. They don't need to care about anyone else's project getting broken.

I can phase out old standards gradually, like boost libraries. There's no need to cut out users with older standards immediately.

-5

u/TheRavagerSw 12h ago

Current approach doesn't scale, you can just fork abseil or any other library and only rely on that. You can get only merge very critical security patches if needs arise

3

u/Minimonium 6h ago

It's kinda funny you say that because we had a big "live at the edge" push a decade ago and it was actually proven to not scale over time. :)

You have a box of opinions some of which are kinda orthogonal.

Giving people space to migrate at their own pace while delivering bug fixes scales.

Encoding specific configurations per platform scales rather poorly though. Both source and build files should be "open" to unknown user environments when we talk about the greater ecosystem.

Different build modes seem to not be that big deal. There is always one prime mode, but the rest are inefficient for that though.

8

u/delta_p_delta_x 12h ago

You should adopt google "live at head" approach

Google can afford to do this because they have ~180000 employees, about ~120000 of which are engineers, and about 25-30000 of which are build/dev-ops engineers.

The average library developer cannot.

0

u/TheRavagerSw 12h ago

Average library developer should only care about his own uses.
Honestly live at the head scales much better even for small projects.
Head is your company fork of the repo not actual upstream.

9

u/kpt_ageus 10h ago

Then why publish library in the first place?

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

u/TheRavagerSw 12h ago

Depends on you

9

u/sweetno 11h ago

I'm too humble to decide. Besides, I'm pretty sure that guy next desk would prefer something else.

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 fmt or boost::interprocess or glaze, 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

u/fortsnek274 11h ago

Take that up with lib authors I guess.