Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Pros:

- Transpiling to C++ means it will work wherever C++ works. On any toolchain. With relatively little effort. It shouldn't be hard to add CMake and bazel support, for instance. Even mostly dumb makefiles should be workable.

- Approaching the community this way could avoid fracturing the community more than it already is.

- Targeting specific success metrics (like CVE causes) could provide focus around design and requirements

- Seems simpler and easier to learn and teach.

Cons:

- The pitch assumes C++ modules are implemented, ready to use, and reasonable accessible.

- There will be a lot of tooling to reinvent: static analyzers, syntax highlighters, etc.

- At least for a while, debugging cpp2 problems will involve juggling multiple parallel realities: cpp2 code, C++ code, and actual program behavior (i.e. compiled binaries).

- Doesn't address other major C++ ecosystem problems like build times, packaging, build systems, etc. cpp2 could make them slightly worse.



This is all true, but I feel it sort of misses the point a bit. The goal seems to be mainly to use cppfront as a test bed for new language syntax and semantics, where most of the ideas will eventually make its way into the C++ standard or into existing compilers.

The main reason to use it in production is that it's not meant to be used in production and makes no attempt to be suitable for production use or avoid breaking changes, so the stuff in your cons list doesn't really apply. It's kind of like looking at someone's scale model of a proposed city layout and say that a pro is that tiny plastic houses are cheap to manufacture at scale but a con is that the houses may be too small to live in.

But all of your points will become relevant if cppfront eventually gets to a state where it's a serious transpiler meant to be used in production.


I don't think this analogy quite works. If all you wanted to do was evolve the existing C++, then sure, you wouldn't need any of this to be remotely production ready. But to me, part/most of what makes this interesting is the possibility for backwards-incompatible, radical simplifications to the C++ syntax and/or feature set. Those really are only meaningful if they get to a production ready state. And by being backwards-incompatible, they inherently cannot just be bolted onto the existing C++ language spec (as if we wanted to anyway---the spec is far to complex as it is).

I will say that it's impressive how much of this has already made it into C++ language specs. But given that Sutter has chosen to release this now, I get the sense that of what remains, large portions cannot continue to simply be done that way.


I agree, and would like to note that there isn't a need to simplify C++ imo. It's folly from a business and productivity standpoint to use one language, especially C++, to do everything from writing web apps to doing exploratory data analysis.

Besides, the C++ community seems to overlap with academia by way of its feature updates, reveling in complexity (e.g. "C++20 ranges are totally awesome: memory-safe and performant iterators!").


I've been using my own little Go (subset / my own extensions) -> C++ compiler -- https://github.com/nikki93/gx -- and found it to be a fun way to add some guardrails and nicer syntax over C++ usage. You get Go's package system and the syntax analyzers / syntax highlighters etc. just work.


In his talk he demos mixing syntax 1 and 2 (his terms) in the same file. Preprocessor macros work fine unless you enable "pure" mode.

He also demos error messages, and was able to get errors from a cpp compiler (msvc) that point to his cpp2 file with a line number and readable error.


> Transpiling to C++ means it will work wherever C++ works.

This approach is similar to Rust's editions. IIUC, nearly all of the differences been editions are compartmentalized in the frontend, deprecating syntax and rewriting old edition code to rustc's current IR.


Rust's Editions specifically only change the concrete syntax, although there have been hacks to make some stuff that would otherwise be impossible become possible by retrospectively making how things used to work a special case, and of course if some feature needs syntax then you can't use it from an Edition which doesn't have that syntax (e.g. async doesn't exist in Rust 2015 edition)

Here's the guide explaining the most significant hack:

https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterat...

There was a proposal to do Editions for C++, Epochs, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p18... but it was not accepted.


Rust editions can technically change some aspects of the language semantics, but this ability is used sparingly. An example is how the `iter` method on fixed-size arrays changed in the most recent edition. As long as code from all editions can still be used together, a semantic change is possible.


> An example is how the `iter` method on fixed-size arrays changed in the most recent edition.

Can you explain?

The array type doesn't have an iter() method. It will coerce into a slice, which does have an iter() method, but doesn't care whether it's an array we're slicing.


I was misremembering, it was `into_iter` rather than `iter` that was changed. The documentation for the change can be found here: https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterat...


That's not what they're talking about here. They're saying that any system with a C++ compiler is a target for this new language, as opposed to Rust, which only runs on systems with a LLVM backend.


Yes. This. Assuming some niche embedded cross compilation toolchain uses C++23, it would support cpp2.


Transpilation has historically made source-level debugging more difficult. That's such a huge advantage to have a language that you can actually debug, and this is clearly make it even harder. I feel this is very developer-hostile.


The static analysis should be able to remain because a lot of the concepts he has implemented here are proposed/implemented for "syntax 1" as well, but they require attributes or specific styles. Therefore, the static analyzer should be written for regular C++23 and will support the defaults of "syntax 2".

As far as support in compiled libraries is concerned, that would depend on how those features are implemented in C++23 anyway.


Well, existing static analysis would break or need to add support for cpp2. In the case of pure cpp2, simpler tools could be written, but they would be new code.


> Doesn't address other major C++ ecosystem problems like build times, packaging, build systems, etc. cpp2 could make them slightly worse.

I assume that as long as the cpp2 syntax is clean, you could improve on compile times. In theory, cpp2 files could compile much more quickly that C++, so as you migrate code to cpp2 your builds speed up?

It all depends on how well Sutter has thought through the cpp2 language design I suppose.


I said that because the user surveys conducted by ISO clearly identify dependency management and build times as the biggest pain points for C++ engineers. Every proposal doesn't have to work on those problems, but other than parsing (i.e., language designer tech) tooling seems like an afterthought in this case. Just like all of the big ideas coming from ISO leadership.

Big reasons people look for non-C++ solutions include better build times, dependency management, etc.


Modules are a huge benefit for compile times


I've heard discussions that C++ modules didn't really give that much performance benefits as it initially claimed, when the dependency graph is huge and you have a large number of threads. Some links:

- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...

- https://vector-of-bool.github.io/2019/01/27/modules-doa.html

Are there any notable new updates related to this controversy?


Yes, Herb mentions on his talk, import std on VC++ is faster than only doing #include<iostream>, the module version brings the complete standard library.


But it's adding an extra compilation step. The only way to will improve compile times is if it avoids the need to recompile all source files referring to header that's only been modified to add/remove/modify private methods/data in a class (even data is an issue if objects of that class are instantiated on the stack). In principle it might be possible for an extra transpilation step to help there if you also take over the "make"-style dependency management. I'm guessing c++ modules is supposed to help address this too?


The idea is not adding a compilation step. This is a proof of concept and playground. If it works out it's not going to stay as a transpiler to Cpp1 forever


Sure but unless you exclusively use the new syntax (which should be slightly faster to parse, though I'm not sure what % of total compilation time that constitutes) it's still going to have to do at least the same amount of work, if not more (given there's now more that has to be inferred by the compiler, rather than specified explicitly). But from experience the main issue with c++ compliation time is when modifying header files.


Extra Cons: how do you write in the new syntax when using existing C or C++ libraries?

Imagine writing something using OpenSSL, it might look kind of weird or breaking the flow of the new syntax, just because of its legacy.


> Transpiling to C++ means it will work wherever C++ works.

Only if you only use this Cpp2 thing. Because the C++ you write may not play nice with the C++ this transpiles into.


It support the big 3 (gcc, clang, ms)


I didn't mean compiler support, I meant that if the transpilation result might not follow the coding conventions of your C++ codebase.


who cares if it does? It's just an intermediate product from compiling the code with cppfront. gcc and clang both generate assembly files and then call the assembler on them -- I doubt that assembly code matches your coding conventions either.


> Transpiling to C++ means it will work wherever C++ works.

It also means you don't really benefit from any of the new semantics. Why do all of that static analysis work to establish safety, and then throw the results away by writing out clunky old C++? It all makes very little sense.


.. this doesn't make sense at all. If you prove that a program is safe in language A, it doesn't magically become unsafe when transpiled to language B. Every language works by clunking out instruction sets mostly devised in the 1970/80s, this doesn't remove the safety properties of the higher level language.


A whole program, yes. A library that's written for language A may well rely on arbitrarily complex preconditions for safety that you may have no hope of establishing within language B. Rust devs get bitten by this all the time when trying to reuse "safe" library code within an unsafe context. (That's one key reason for wanting e.g. Carbon as something that isn't just plain Rust.)


Off the top of your head, do you have simple examples of this? I know a bit of Rust but I assumed (apparently wrongly!) that safe-within-unsafe should just work.


I'm not sure what the grandparent post if referring to, but:

- In general, "unsafe" code inside a module may depend on invariants maintained by "safe" code in the same module. For example, the "length" field in a Vec can be changed by the safe internals of Vec. But if the safe code in Vec sets an invalid "length" value, then unsafe code relying on "length" might fail. So once you find an unsafe block, you may need to audit some of the surrounding safe code in the same module.

- Unsafe Rust is actually slightly less forgiving than C or C++, partly because Rust is allowed to set "noalias" on lots of immutable references, IIRC. The Rustonomicon talks a lot about issues like this.


It will work if your unsafe code does not actually opt-in to any unsafe features-- in which case you would not actually need an unsafe block. If it does use any of those however, it's very easy to break the expected preconditions of safe code and create unsoundness, e.g. around references (since code written in safe Rust has little use for raw pointers) or properly initialized data (Rust expects you to use MaybeUninit<> whenever data may not be properly initialized but that's a mere wrapper, which can only be removed by unsafe code).


So if your unsafe code hits an UB then your safe code can be broken…


Of course, undefined behavior anywhere in the program automatically makes the entire program invalid (this is how undefined behavior works in C and C++ compilers as well). But that's not what the parent commenters are talking about. An `unsafe` block in Rust represents a place where the ordinary invariants of the language may be violated. This is immensely useful for auditing, documentation, and manually verifying that the program acts as you expect. But it's a common mistake to assume that this also means that future changes to non-unsafe-blocks cannot invalidate existing unsafe blocks. While it is always necessary for an unsafe block to exist as a sort of "root cause" of unsafety, safety invariants can rely on things that are merely reachable from unsafe blocks. In practice, this means that the boundary for safety in Rust is not the unsafe block itself, but rather the boundary of the module that contains the unsafe block. This also means that, if you're writing unsafe blocks in Rust, it behooves you to make their containing modules as small as possible in order to reduce the amount of things the unsafe block can reach, and therefore reduce the number of changes that might accidentally change an assumption that an unsafe block is relying upon.


I know about that (even though it's a worthwhile addition to this thread for other readers), but I don't think it's what they had in mind, since they were talking about “Rust devs get bitten by this all the time when trying to reuse "safe" library code within an unsafe context” in their original comment[1], and I really can't see what they are talking about except some variation around “from C++, I passed some uninitialized memory to a Rust library and Rust went boom” but maybe I'm just misunderstanding.

[1]: https://news.ycombinator.com/item?id=32878775


Isn't it easy to call safe functions from unsafe Rust that, if called from safe Rust, would lead to a compilation error? For example, accidentally passing a safe function two mut pointers to the same object, which normal Rust ownership wouldn't allow you to?


Technically speaking it is OK to pass two aliasing mut _pointers_ to a (safe) Rust function because safe Rust can't do anything dangerous with raw pointers; both dereferencing and writing through a raw pointer require unsafe. If we are talking about references instead, creating two mutable references to the same object in Rust (unsafe or not) immediately causes UB.


I can't think of any such thing, no. Unsafe Rust doesn't turn off the borrow checker, it just lets you do a handful of operations that you can't do otherwise. The only way I could see that happening would be if you somehow violated ownership invariants in the unsafe block, which is already forbidden.


That's exactly how Typescript works. The point is that the type-safety checks are done at transpilation time, and that step fails if you've violated them. So I'm assuming something similar for cppfront - it will fail to transpile if your code doesn't pass the additional safety checks.


Because you don't actually work with the C++ output I guess? Same with Kotlin/Java — there's no difference in nullable vs non-nullable types for example. But as long as your codebase is 100% Kotlin, you get proper null safety, and there's no need to fall back to what Java has


How are any of the guarantees lost as long as the resulting C++ code isn't touched?


Transpiling and compiling are turtles all the way down.

It's a bit more complicated than, say, typescript to JavaScript, but in spirit is not so very different, right?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: