Live data from Hacker News

What to do with C++ modules?

nibblestew.blogspot.com

111–120 of 269 posts

Re: What to do with C++ modules?

#111
post #29

Earlier quoted context omitted.

This is true ... except that Rust doesn't actually do any better in that regard. Rust solves 1 category of problems in a way that is not without its costs and other consequences. That is it. There are projects where this is very important, there are other projects where its virtually useless and the consequences just get in the way. It is not magic. It doesn't make anything actually 'safe'.

sure, but don't forget that rust gives us also a nice tooling, functional syntax sugar like pattern matching, enums, monads; and other more or less useful things like explicit lifetimes

Of course. This is kind of to be expected as it has the benefit of hindsight of 25+ years. It is infinitely easier to design better things with all the accumulated experience and know-how of what works and what doesn't under your belt. It would have been truly horrifying if that had not been the case.

That being said, Rust is really about lifetimes. That's the big ticket selling point. My point above was that 1) it isn't a silver bullet and 2) it can be a real hindrance is many applications.

Re: What to do with C++ modules?

#112
post #43

Back in the 90s, I implemented precompiled headers for my C++ compiler (Symantec C++). They were very much like modules. There were two modes of operation: 1. all the .h files were compiled, and emitted as a binary that could be rolled in all at once 2. each .h file created its own precompiled header. Sounds like modules, right? Anyhow, I learned a lot, mostly that without semantic improvements to C++, while it made…

I can't think of a C++ project I've worked on that didn't rely on being able to include C headers and have things usually just work. Are there ways of banning C macros from "modular" C++ without breaking that? (Many would find it unacceptable if you had to go through every C dependency and write/generate some sort of wrapper.)

D resolved this problem by creating D versions of the C system headers.

Yes, this was tedious, but we do it for each of our supported platforms.

But we can't do it for various C libraries. This created a problem for us, as it is indeed tedious for users. We created a repository where people shared their conversions, but it was still inadequate.

The solution was to build a C compiler into the D compiler. Now, you can simply "import" a C .h file. It works surprisingly well. Sure, some things don't work, as C programmers cannot resist put some really crazy stuff in the .h files. The solution to that problem turned out be we discovered that the D compiler was able to create D modules from C code. Then, the user could tweak by hand the nutburger bits.

Re: What to do with C++ modules?

#113
post #69

Earlier quoted context omitted.

static if was more or less added in C++17 under the name `if constexpr`. It's not exactly the same since the discarded statement is still checked if not dependent on a template, but like most things in C++, it's similar enough to footgun yourself.

"if constexpr" introduces a new scope, while "static if" does not. A major divergence, enough to make the features very, very different in terms of what they can actually be used for.

That was a giant mistake on C++'s part, as you cannot do this:

    static if (feature) { int bar() { betty(); }}
    ... lots of code ...
    static if (feature)
        bar();
Forcing a new scope cuts the utility of it about in half, and there's no way around it. But if you need a scope with D's static if:

    static if (expression) {{ int x; foo(x); }}
the extra { } will do it. But, as it turns out, this is rarely desirable.

Re: What to do with C++ modules?

#114
post #54

Earlier quoted context omitted.

In order to make things work smoothly, the module has to have its own namespace, and a namespace that is closed. D also has an `alias` feature, where you can do things like: alias Q = abc.T; where from then on, `abc.T` can be referred to simply as `Q`. This also eliminates a large chunk of purpose behind the preprocessor.

Neat, closed namespaces sound great! C++ has adapted the ‘using’ keyword now to seem fairly similar to alias, but can’t completely subsume macros unfortunately.

Closed namespaces means the module can be reliably imported anywhere without changing its semantic meaning.

Re: What to do with C++ modules?

#115
post #67
post #12

Earlier quoted context omitted.

What has to change in C++ templates for this to work? It seems particularly tricky to define a template in a module and then instantiate it or specialize it somewhere else.

Including or importing a templated class/function should not require bringing in the definition. That's why #includes and imports are so expensive, as we have to parse the entire definition to determine if template instantiations will work. For normal functions or classes, we have forward declarations. Something similar needs to exist for templates.

And it doesn't in D. We call such modules ".di files", which consist only of declarations.

D does not require names in global scope to be declared lexically before they are used. C++ only does this for class/struct scopes. For example:

    int bar() { foo(); }
    int foo() { bar(); }
compiles and runs (and runs, and runs, and runs!!!).

Re: What to do with C++ modules?

#116
post #71
post #23

Earlier quoted context omitted.

There are a number of areas in programming where I'd always choose C++ over Rust - gameplay programming, retained-mode GUI programming and interpreted programming languages to name a few have very complex circular memory models that are somewhat solvable with weak_ptrs or refs stored in member variables passed through constructors but would be absolutely obnoxious to deal with and get right with the borrow checker.

Rust has Arc and Weak that can be used for solving it in a similar way C++ does. The primary difference is forcing usage of Mutex to avoid a large class of data race issues IMHO, and that's what makes it harder to get right. Async in Rust is also more mature than C++ coroutines. So there's also that. https://play.rust-lang.org/?version=nightly&mode=debug&editi...

Rust doesn't have computed goto (gcc/clang extension heavily used in interpreters like CPython and LuaJIT for direct threading).

You’re forced into function-call or jump-table dispatch, which tends to be slower.

Re: What to do with C++ modules?

#117

Earlier quoted context omitted.

C++ is widely used in embedded. Most compilers support it. Usually you turn some things off (e.g. -fno-rtti, -fno-exceptions) and try to stick to some sane subset of the ++.

C++ in embedded is basically C with classes. It is a nice simple language and often doesn't even include templates (or if it does they are very simple data structures that are not even heap allocated). C++ as C with classes is a pretty good language!

[deleted]

Re: What to do with C++ modules?

#118
post #16

Earlier quoted context omitted.

optional is heavily used in new codebases. variant isn't, yet. We'll eventually get some kind of structural pattern matching that will make variant or it's successor more idiomatic. C++ does have quite a bit of rot, you're right. But that's the price of building technology people actually use. Carbon doesn't seem to have any fundamentally new ideas, we'll see how well it fares in the wild.

> variant isn't, yet. We'll eventually get some kind of structural pattern matching that will make variant or it's successor more idiomatic. Thats the fantastic thing about c++, you can already write an easy to use match, but they just chose not to include that in the stdlib but rather want you write that yourself. Example: match(foo, [](Foo&) { }, [&](Bar& bar) { }, [&](const auto& mode) { // catch all }); Also opti…

It would be pretty ugly to make this more introspective in C++.

For example, if you had to match patterns to distinguish between these three possibilities when looking at a tree: (val), (val (tree left) (tree right)), (val (tree subtree)). Here, the possibilities are not really separate types, but rather different ways of constructing a type. This sort of pattern shows up in general purpose programming (even meat and potato imperative programming) pretty often.

There was a proposal for this championed by Stroustrup over ten years ago, but it went nowhere IIRC. https://www.stroustrup.com/pattern-matching-November-2014.pd...

Re: What to do with C++ modules?

#119
post #13

Earlier quoted context omitted.

C++ has a habit of incorporating all the best ideas from it's 'succcessor' languages once these ideas are mature.

Rust is basically the wanted fixes to C++ that C++ itself could never adopt for legacy reasons. So I'm afraid no by definition C++ can't adopt Rust's ideas because Rust's ideas were originally impossible C++ ideas.

> C++ itself could never adopt for legacy reasons.

I agree with your point except for the 'never' qualifier. It was certainly true when Rust was born.

C++ has proven the 'never' part wrong multiple times. I think, by 2030, the only thing that C++ would lack that Rust has right now is the unified toolchain/packaging ecosystem because people are not going to settle that debate.

Everything else is well on its way to be implemented, in one of three forms - core language features (eg: concepts), language features that primarily enable writing more powerful libraries so that you do not have to come up with language features for everything (eg: reflection), and finally tooling support from the compiler as a test bed of what the language could guarantee (lifetime checks and annotations in clang).

Of course Rust is innovating pretty well too, I am very interested in seeing what async/coroutines are going to look like in a few years.

Re: What to do with C++ modules?

#120
post #71

Earlier quoted context omitted.

Rust has Arc and Weak that can be used for solving it in a similar way C++ does. The primary difference is forcing usage of Mutex to avoid a large class of data race issues IMHO, and that's what makes it harder to get right. Async in Rust is also more mature than C++ coroutines. So there's also that. https://play.rust-lang.org/?version=nightly&mode=debug&editi...

Arc/Weak can only get you so far. The problem I have with Rust in the mentioned domains (gameplay programming, retained GUI, interpreters) is what makes it so good, sharing xor mutability. If you're writing lots of multi-threaded code it's fantastic. If your problem domain is like the ones mentioned it hurts you. These are all highly non-parallel problems. They don't gain much from being parallel, and because Rust im…

Shared mutability can cause race conditions in single-threaded environments as well, if you need asynchronous contexts where mutations can be interleaved between suspension points. Think of events, message passing channels, I/O, timers, ticks...

If you're sure you're never going to need multi-threaded environment, you have an option as well: Replace std::sync with std::rc, Mutex with RefCell in the above toy example and that's about it.

If you want to use some asynchronous runtime, replace std::sync with tokio::sync (or std::rc), slap async/awaits along with a single-threaded runtime and that's about it.

Of course, the code above is just a toy example and business logic is much more complex in real world, but compare this to what it would take to write same C++ logic in async.

I found Rust's approach massively more ergonomic compared to C++ approach of passing closures around for asio-like IO contexts or coroutine compiler-magic which opens new novel avenues to shoot myself on the foot, well, to the extent I could grasp it.

It's true Rust forces you to pay all this cost ahead of time. It's also true most applications don't require this level of safety really, so it becomes ridiculous to pay it upfront. And even for some that require such high level of safety, you can skip bunch of bolts on a plane door and it will still be a billion dollar company at the end of the day, so...

Post reply on HN