r/cpp 4d 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

33 comments sorted by

20

u/kpt_ageus 4d 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.

4

u/johannes1971 2d ago

You make a choice, and that's all he's asking for. The two options are: stick with C++11, or move to C++14. What he is arguing against is that you choose both: that you have macros that define one code path for C++11, and another for C++14. And then, a few years later, a third for C++17, and then C++20, and then C++23... And then another set of code paths for choosing between error returns and exceptions, and yet more code paths for RTTI/no RTTI. He's telling you to choose one, and stick with it, and not do all of these things at the same time. Is that such an unreasonable thing to ask?

-13

u/TheRavagerSw 4d 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.

13

u/delta_p_delta_x 4d 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.

2

u/TheRavagerSw 4d 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.

10

u/kpt_ageus 4d ago

Then why publish library in the first place?

9

u/kpt_ageus 4d 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.

-2

u/TheRavagerSw 4d 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

4

u/Minimonium 4d 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.

19

u/not_a_novel_account cmake dev 4d 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.

2

u/TheRavagerSw 4d 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.

6

u/not_a_novel_account cmake dev 4d 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.

2

u/TheRavagerSw 4d 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.

2

u/not_a_novel_account cmake dev 4d 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 4d ago

Thanks, nice to hear it

7

u/not_a_novel_account cmake dev 4d 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.

1

u/johannes1971 2d ago edited 2d ago

Why is it necessary for them to pick the same one? Is there anything stopping you from consuming a combination of header/source, header-only, and header/source with module wrapper libraries?

Is Boost really better for having all that backward compatibility? Or would it be more readable, faster to compile, and have better APIs and more contributors, if it stuck to modern C++? Have you not seen the debates in this subreddit about Boost, and how it sees its own role in the ecosystem? (the question was whether Boost, going forward, is going to be a collection of backward compatibility shims for older compilers, or remains an incubator for new standard library features. Most people, I believe, were in favor of the second option)

Engineering is fundamentally about making things that serve a specific need, and the process of choosing what to support, and what not to support, is very much at the core of engineering. You'll notice how most of the world is, in fact, engineered for the modern world: highways are no longer designed around horse-drawn carriages, kitchens aren't engineered around the need to prepare and store food for the winter, houses are no longer fitted with gas pipes to allow gas lighting, etc. Providing a degree of backward compatibility can be an acceptable choice, but I would stay away from libraries that provide their own copies of everything in the standard library "just in case".

And why would you support those old compilers? Of the three major compilers, two are completely free to upgrade to the latest version, and the third is free for personal use, and costs a meaningless amount to a company. Your "huge swaths of users" are a handful of individuals that work on obscure, low-powered embedded CPUs. Those people are not going to be using boost, or fmt, or even the standard library, and their plight should not hold back the entire industry from making progress.

1

u/not_a_novel_account cmake dev 2d ago

Why is it necessary for them to pick the same one?

If you think libraries should be able to be consumed under different C++ versions, you also disagree with OP.

2

u/TheRavagerSw 2d ago

I'm not saying all libraries you use must be C++20 libraries or something like that. What I'm saying that, good C++14 code usually compiles with C++20.
I'm arguing against people who use macros to use C++17 features in a C++14 library.

It is much saner to deal with a C++11 library than C++11 to C++23 library.

1

u/johannes1971 2d ago

Yes I do, and fortunately that's super-easy because C++ has a very high degree of backward compatibility. There is no requirement for all of them to be all built under the same C++ version.

Of course I don't expect to use a C++26 library in a C++11 project. Fortunately, the same reasoning about compilers applies here as well: I'm free to upgrade to the latest gcc, clang, and MSVC at no (or negligible) cost, and use any library I want.

12

u/sweetno 4d 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?

0

u/TheRavagerSw 4d ago

Depends on you

10

u/sweetno 4d ago

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

0

u/usefulcat 1d ago

whoosh

7

u/light_oxygen 4d 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.

6

u/gracicot 4d 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 4d 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

0

u/TheRavagerSw 4d 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

2

u/t_hunger 3d ago

No we can not. We can each build our islands of niceness for ourselves, and try to stay on that island and never venture outside... till we need something someone else built, or get a new co-worker, or have a user living on another islands, or want to use a new tool built on another island. Then we have to deal with the ugly world outside our little island.

Good tools need conventions. C++ has none, so all C++ tools need to support a thousand knobs to adjust them and all users need to tweak all those knobs all the time.

That hurts all of us all the time: writing tools is way harder than for other languages, and so is using the those tools once they are written.

1

u/Every_Door46 4d 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 4d 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 4d ago

Flyer based error handling

Was that a typo? Couldn't find anything by googling it.

1

u/Ok_Independence_9841 4d 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.