Hacker News new | past | comments | ask | show | jobs | submit login
Overview: What are Cpp2 and cppfront? How do I get and build cppfront? (hsutter.github.io)
190 points by cyber1 5 months ago | hide | past | favorite | 168 comments



Unfortunately the first example already re-uses one of the less good parts of C++, the "<<" operator for std::cout, which always was a bit of a hack (including strange order of operations since << normally is left shift)


I've always thought that using "<<" in that manner was more of a way to show off the operator overloading feature than anything else.


I think the original point was to make a typesafe printf that's still reasonably efficient without creating a bunch of small temporary strings. Like, if you printf like this:

    printf("Number %d, String %s", n, s);
but the types of n and s aren't int and char*, all hell breaks loose and you have the origin of a million CVEs. But how do you make that function signature typesafe, without the tools of modern templates? You sort of can't. One thing you can do is do string append stuff, but the syntax then is annoying:

    print("Number " + to_string(n) + ", String " + s);
This also creates a bunch of temporary strings, which is not ideal (and still relies on operator overloading, btw). In this world, using operators for this does make some sense:

    std::cout << "Number: " << n << ", String " << s;
Like, seen from this perspective, it's not the worst idea in the world, it does work nicely. The syntax really isn't too bad, IMHO (the statefulness part, though, really sucks). It also allows you to add formatting for your own types: just overload operator<<!

Clearly, properly type-safe std::format is vastly superior, but the C++ of the 90s simply didn't have the template machinery to make that part of the standard library.


It would have been possible to, as a special case, type check the formatting, C compilers began doing that in the 1990s IIRC.


And there are ways of signaling to a compiler that your function accepts a format string and a series of typed arguments in case you're wrapping a C-style printf function.


It wouldn’t have added support for custom types to printf(), which was an important motivation.


That's true, however neither the mechanism to do this in I/O streams, nor the weighty mechanism for std::format come close to what Rust's derive macro offers for Debug, and in my experience asking for so much means you often won't get anything because programmers are lazy.

That is, for a custom type in Rust having rudimentary Debug output is one word added to the list of derive macros, the word "Debug". Almost everybody will write that, it's barely effort.

To get that in C++ I/O Streams you need to write an operator overload function and must be careful to write it correctly. Trivial types usually don't bother, but many C++ programmers are comfortable writing this for an important type.

For std::format you need to write a template which is already pretty scary, and then it needs to implement custom parse and format steps, this is because std::format allows your custom type to define its own custom formatting rules. This is extremely powerful, but everybody must pay for this power. I have not seen most people bother doing all this at all.


You can write operator<< as a free (non-member) function yourself for classes that don’t implement it. Furthermore, it’s not necessarily the same output that you want for debugging.


Don't you think it just serves to highlight that Cpp2 is just C++? That it's explicitly not some new language with C++ FFI like Carbon.


Not sure what is meant by this statement. Both cpp2 and Carbon are different languages, neither of them claim to be just C++.

Both languages will have bidirectional compatibility with C++ so that code written in C++ can be directly accessed from Carbon and code written in Carbon can be directly accessed from C++. Neither of them use an FFI for compatibility.


It's my understanding that Carbon will create wrapper Carbon classes around C++ classes and convert data back and forth between C++ types and Carbon types at the interface boundary when necessary.


Yep, that's the plan.


Not really, Herb Sutter tries to sell cpp2 as "Typescript for C++", or "Kotlin for C++", while ignoring that Typescript doesn't add anything to JavaScript besides types, they did some mistakes on the early days not following up on this, but backtracked and now Typescript is basically a linter/transpiler.

Whereas while Kotlin used the Java ecosystem to bootstrap itself, it is clear that by now JetBrains rather wants to build their own Kotlin ecosystem, just like C++ and Objective-C took their own path after being plain translators into C.


Objective-C++ does exist, so their paths didn't diverge so much that they didn't remain in each others' ecosystems on a permanent basis (on Apple, which is the only place both regularly intersected)


I kinda like it, it's comfortable and pragmatic. I don't have to look at the docs to figure out what's the format specifier for something like a size_t, and you can easily add support for whatever might be needed, so you can comfortably do:

   std::cout << "Player is at" << _player_pos;
And get the x/y/z breakdown automatically.


But you can do that with fmtlib/std::format as well when you provide formatting for custom types, that just becomes:

    fmt::println("Player is at {}", _player_pos);


Should be possible to implement cout more effectively since there is no string parsing. I say “should” because in practice it seems to be the other way around. I don’t know why.


Been a while since I've looked at it, but pretty sure std::format validates and parses most (all?) format strings at compile time. There should be no runtime penalty.


There are some fundamental reasons why cout is slower than stdio/{fmt}: https://stackoverflow.com/a/65325727/471164.


And now try to do task which should be as trivial as add 1 format character: output number in hexadecimal and leave stream in same state that was before your code.

It becomes cumbersome very fast.


Note that the "<<" operator for std::cout is not related to the language itself but related to the standard library.


But in a "C++ 2" helloworld one would really expect to see std::println used instead [1].

[1] https://en.cppreference.com/w/cpp/io/println


Cpp2 is part of the trend for would be "C++ Successor languages" from 2022. The std::println function was standardised in C++ 23, and implementations still don't all provide it in full today.

So in effect Cpp2 pre-dates std::println. Herb will have been aware that it exists and is likely for C++ 23, but it doesn't make sense to ship software which requires features you suspect won't be widely available for several years. A "Hello, world" program should not be relying on bleeding edge features.


Someone on reddit said that the cpp2 repo was a bit old, and that is true, although he may have started this as an experiment influenced by typescript and left it on the side at some times.

Anyway I have no idea if cpp2 would get support from microsoft or other devs, but cpp2 seems like the most humble and "least risky" solution for the future of C++, and I really want it to be.

What I remember the most that Herb Sutter said in his cpp2 talk, is that it aims to avoid 95% of bad coding practices that C++ allows today.

It's safe to say that beyond the valid criticism of C++, that it quite a good goal and it would improve C++, without using a new language, and that's good, because a new language causes problems: new toolchains, new semantics, new specifics, no experience on a new language.

Cpp2 is not a new language, it is the same semantics of C++, except it has a new syntax and enforces good practices.

One very interesting point: in the future, cpp2 allows a cpp2-only compiler to be born, and it would still live next to C++ binaries without problem. That cpp2 compiler might probably be much faster since the cpp2 is a smaller stricter subset.


How about Google's Carbon language?

https://github.com/carbon-language/carbon-lang


Carbon and Cpp2 are both "C++ successors," broadly speaking. However, they take different approaches in their design:

Carbon is a brand-new language that's designed to have seamless interoperability to C++.

Cpp2 is C++, just with a different syntax. (The goal is to even be able to mix Cpp2 and C++ syntax in the same source file. The initial implementation transpiles Cpp2 to C++ source code, similar to how the original C++ implementation transpiled C++ to C, rather than implementing a full compiler.)

Their overall goals appear to be different, too: The Carbon README states, "Existing modern languages already provide an excellent developer experience: Go, Swift, Kotlin, Rust, and many more. Developers that can use one of these existing languages should." With that mindset, it sounds like more of a stopgap for projects already invested in C++. In contrast, I believe that Sutter would argue that C++ still has the potential to be a viable choice for many tasks, and Cpp2 is a way to realize that.


> Cpp2 is C++, just with a different syntax.

Not really, that is like telling CFront or Objective-C (Stepstone days) were C with a different syntax.


> Anyway I have no idea if cpp2 would get support from microsoft or other devs…

Doesn’t he work for Microsoft?


Yes but it’s his own personal project


> Anyway I have no idea if cpp2 would get support from microsoft or other devs, but cpp2 seems like the most humble and "least risky" solution for the future of C++, and I really want it to be.

That ship has sailed. They already have C#.


To be fair C# is vastly different in terms of semantics compared to C++. There's a lot of areas where it's not viable to use C#, and IMHO it also has its own share of legacy and bad decisions from its "Java clone" days that make it impossible to prefer it to C++ sometimes.

Btw Microsoft is definitely interested into adopting new languages, just look all the effort they've been pouring into Rust lately.


Rust is now the official system programming language for Azure infrastructure, alongside managed languages, and it appears there is a soft spot to use Go instead of C#, when looking at what Azure opens up.

WinDev, Office and XBox is another matter, they are deep into C++ and COM culture.


C# is so old by now, if it would be a good replacement for C++ at MS, it would already have replaced it.


Don't mix technology with the internal politics at Microsoft, where some business units won't use anything else besides COM and C++, no matter what.

They are so strong that they were responsible pushing the whole company into the whole Windows 8 debacle with WinRT, where COM was supposed to finally replace .NET.

https://arstechnica.com/features/2012/10/windows-8-and-winrt...


Java and C# are in their "let's get functional" phase.


:(

C# has been at that for more than a decade and a half already. Java's arrival to the party is extremely late and still lacking in many areas.


C# could have been like D back in 2001, yet it had to wait to go open source, away from the wings from WinDev politics, having to provide a replacement to C++/CLI capabilities in a cross-platform way, to finally expose the full CLR to C#, without us having to routinely manually generate MSIL or reach out to C++/CLI.

Singularity and Midori were hardly taken seriously in any form by WinDev.


Is there any discussion or in-depth explanation of the syntax choices? I understand that a goal was context free unambiguous parsing. But there are some things that surprise me.

For example, string interpolation:

    "Hello, (msg)$!\n"
Why “(msg)$” and not “$(msg)”? Surely the latter is easier to parse?



I think $() makes much more sense than ()$ in terms of parsing, when you see $ you're looking for a single word token, and $( you're parsing up to the matching )

Maybe it's not much extra workload, but ()$ requires you to pattern-match all bracketed content in a string as a possible capture, and the parser can't determine whether it's a capture or not until the $ or a matching close bracket. Consider parsing "((x)$)" would require parsing the entire string to determine the first bracket wasn't a capture, then we can treat just the first character as a literal, then we have to re-parse the rest of the string again, and can't be sure it's a capture until the $, and then we can evaluate it as a real expression and then continue with the final $.

Other interesting cases would be "(x+(x)$)" or even "((x+(x)$)$)". I'm not sure I could easily predict the parsing of latter, but prefix and permitted unbracketed single word variable names "(x+$x)" is clearer and the second would either be "(x+$(x)$)" or "($(x+$(x)))" both of which are explicit using the prefix form and could simplify to "(x+$x$)" and "($(x+$x))" where the intent seems clearer.

There's the precedent that many languages already use the prefix form which would help newcomers with familiarity, and in fact many of these languages wouldn't even need the brackets and $var would be sufficient, and I can't really see why the spec requires the brackets in the ()$ syntax in string literals but not for expressions in code.


> Maybe it's not much extra workload, but ()$ requires you to pattern-match all bracketed content in a string as a possible capture, and the parser can't determine whether it's a capture or not until the $ or a matching close bracket.

You don't have to: you can also just parse the string backwards.


That raises more questions though... Like, why does the capture syntax have to be attached to the captured element?

Rust for example has a single `move` syntax for all-capture vs. no-capture toggle, e.g. `|x| x + foo` (`foo` is stored as a reference) vs. `move |x| x + foo` (`foo` is moved into the closure). While I do want an additional mode for uniformly applying specific methods (typically `.clone()`) for captured elements, that is almost enough for typical closures.

Also, if my reading of the documentation is correct, `$` has to be attached to each occurrence of captured elements. Like, `:(i) = i + foo$ * (foo$ + 1)`. Doesn't that look strange? It is even possible to mix two variants of captures like `:(i) = i + foo$ * (foo&$* + 1)`, and it's not entirely obvious to me what will happen to `foo$` when `foo&$*` is updated. Treating these "upvalues" as a sort of an implicit structure (e.g. `$.foo`) is much more consistent, and a prefix form `$foo` can be regarded as its shorthand.


What does "for later use" mean?

One thing I've noticed recently is that pretty much no language has a good way to simultaneously define a nested structure and assign that structure a name "for later reuse".

For example -- suppose I'm dealing with some serialized data structure that come from some external system. Very likely the data model behind this value involves "nested values" which have themselves have some type of which might be reused in multiple places by that external system.

When the goal is to just solve problems -- the approach i like to take is to focus on the values i want to consume and produce -- which might themselves contain lots of nested types each with some amount of reuse ...

I'd really like a language feature that supports simultaneously defining a type where it's relevant within some other data structure _and also allows_ giving that embedded thing a name for independent reuse ...

I wonder if this postfix $ syntax is related to that use case at all ...

(... this comment a speculation based on names of things only without even reading the whole article ...)


Raises more questions honestly. This looks more different from today’s C++ than Rust.


I feel like consistency goes too for losing the visual parsing benefits of special syntax. Otherwise, we might as well adopt lisp's syntax.


Something about foolish consistencies…

The priorities of a programming language syntax are to be readable first, consistent second.

When it comes to syntax that is used more frequently then the importance of readability increases, and the importance of consistency drops, because people will become familiar with the syntax through frequency of use, so there is no need for them to be guided by consistency. Yet they will be reading the code more often.

When it comes to syntax that is used infrequently then consistency is of more importance because you want users to be able to intuit the syntax. Since it will be infrequently used, the readability is of lesser impact.


I feel like there are more options than just the ones he listed. Like "Hello ${name}" or "Hello `name`" or "Hello {`name`}"...


Oh, fantastic. Thank you!


> // 'BufferSize' is an object defined as a synonym for the value 1'000'000

> BufferSize: i32 == 1'000'000;

So "value : i32 = 10" is variable, but "value : i32 == 10" is a constant.

The difference is so subtle I'm not sure I like it.

Later in the documentation you can find "equals: (a, b) a == b;" which is a function but it feels like I need to decipher it because "==" is not for alias in this case.

Retaking the example of "equals: (a, b) a == b;" it feels also odd to omit the braces because they are even enforced for if/else branches.

I have to admit that everything was interesting until the "Summary of function defaults" part.


Sutter's 2022 cppcon talk¹ is a great introduction to the topic, both the problems it attempts to solve and solutions it was/is settling on. One of those few talks that left me genuinely enthused about the topic.

[It is probably worth watching for the Compiler Explorer interjection toward the end alone -- Matt Godbolt Appreciation Society]

¹ https://www.youtube.com/watch?v=ELeZAKCN4tY


It's a nice idea, but not clear that it can truly interoperate with vanilla C++ libs which I think is required. Seems to be waiting for modules to be finalized, but whether cpp2 can call cpp, and cpp can call cpp2 without implementing half of a C++ compiler isn't obvious. https://github.com/hsutter/cppfront/issues/594


It's a just a transpiler to valid C++ code, so calling C++ code from cpp2 should be fine, but calling cpp2 code from C++ is an issue. There is no implementing half of a C++ compiler because it just uses clang or something after it is done transpiling.


Looks neat and convincing. But I think the goal (and effort) to make it into the C++ standard would hinder the momentum of adoption. I suspect it would have to go through a lot of politics and bikesheding before making any real-world impact. Imagine that if Typescript had insisted to get accepted into the Emca standard before widespread adoption and promotion by Microsoft; it would probably still stay in the 'experimental' stage.


I wonder how this compares with Carbon -> C++ [0].

Carbon is (was?) a fantastic proposal, but not sure if it has lost steam since it was introduced or how well it is being adopted (be it inside Google or outside)?

Being able to incrementally/interchangeably use/call existing C++ code (and vice versa) seems like a great design choice (in Carbon) without having to introspect the actual generated code.

Not sure how easy it is to get the cppfront-generated C++ to bridge with existing C++ code (and vice versa)?

[0] https://github.com/carbon-language/carbon-lang


The roadmap for Carbon [0] mentions wanting to have basic, non-trivial programs written in Carbon by the end of 2024. They're aiming for a v0.1 release in 2025. If it gains traction, they're aiming for a v1.0 beyond 2027.

I don't think anyone outside Google will seriously adopt this before it reaches v1.0. Even within Google, they may choose other options.

[0] - https://github.com/carbon-language/carbon-lang/blob/trunk/do...


Googles habit of dropping support for useful things make me not willing to trust them. I plan for my current code to be in use for at least 20 more years (i plan to retire before then), I don't want to explain to my boss either why we are maintaining a compiler or why we must rewrite working code.


I suspect you don't touch Go code.


Herb Sutter briefly talked about why carbon was not a good contender, and I think remembering it was because of backward compatibility with C++.

At some point, keeping C++ semantics matters, since having different semantics would obviously prevent using previous C++ codebases, or make it more difficult to make those work together, and that may be why Carbon may not be a good choice.


Carbon isn't "being adopted", it's still being developed. Making a programming language takes time. Let them at least get to a point where they release some form of public beta (i.e a few more years, at least) before talking about adoption.


+100 btw. =D


Related:

Cppfront, Herb Sutter's proposal for a new C++ syntax - https://news.ycombinator.com/item?id=32877814 - Sept 2022 (545 comments)

and also Cppfront: Autumn Update - https://news.ycombinator.com/item?id=37719729 - Sept 2023 (8 comments)


> Because ++ and -- always have in-place update semantics, we never need to remember "use prefix ++/-- unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling ++/--

I actually wish ++ and -- operators were removed. This would simplify everything, nothing to remember whether it's prefix or postfix operator, whether it copies something or not, you would just do "value += 1" and be done with it.

- Less mental overhead.

- Remove an extra way of doing the same thing.


Unfortunately, C++ uses ++ and -- for iterators, many of which cannot reasonably implement += or -=. This distinction is baked into the type system to tell whether or not an iterator supports efficient "multiple advance" (e.g. a linked list iterator doesn't have += but a pointer into a contiguous vector does).

There's no way to fix this in a reverse-compatible way for existing code (which is one of the constraints of cpp2- it must work with all existing C++ so that it is possible for existing projects to migrate regardless of size).


Making ++ or -- a statement that increment the target without returning a value should probably be enough for forward iterators.


A noncopying ++ could be spelled `+= 1` though. So some iterators would support `+= 1`, but not `+= 2`. This would be vaguely similar to how the null pointer constant was defined as an integer constant expression that evaluates to zero: Define `+= <integer constant expression that evaluates to one>` as the increment operator.



Rust and Go made the same choice. In Go, I think i++ is valid - but only as a statement, not an expression.

https://go.dev/doc/faq#inc_dec


But you would break the name! C++ would be a syntax error!

I mean, there are people that already think that...


We could allow overloading operator++ while not defining it on anything built-in....


But it’s C++2, and that would become valid! (C + +2)


I know some of the arguments around C++/cpp2 are flawed, but I do love C++ and a project to move it forward with a step-change is exciting to me.

A few of my friends and I did Advent of Code in cpp2 this year and it was a (very buggy) blast.


> A few of my friends and I did Advent of Code in cpp2 this year and it was a (very buggy) blast.

You mean buggy ironically because you were discovering cpp2, or because cpp2 itself was buggy?


I spent the other day writing an archetype entity component system which made heavy use of template metaprogramming. I try to avoid this if possible, but it was an exercise in seeing how performant I could make it, and so I wanted to offload as much work as I could manage to compile time and avoid things like virtual dispatch. API similar to the basic parts of entt.

My take away from the exercise is that this is not a language for human beings. I was successful in writing it, but it was extremely difficult and frustrating. Part of the frustration is because conceptually what I wanted to accomplish was not difficult, but figuring out how to express it was a nightmare. I am not new to the language, I've been writing C++ since 2009, it was the first language I learned and I've spent nearly every day of my life since then writing at least some C++ code. Even so, I can't say that I truly understand this shit.

I'm hoping cpp2 brings us someplace closer to a language that mere mortals can understand. I don't want the next generation writing C++.


>I spent the other day writing an archetype entity component system which made heavy use of template metaprogramming. >I'm hoping cpp2 brings us someplace closer to a language that mere mortals can understand.

Are you sure the problem is the language itself, and not the inherent complexity of that kind of metaprogramming? As far as I'm aware, Lisp is the language with the cleanest support for such metaprogramming, yet metaprogramming-heavy Lisp code is still quite hard to read. I'm not aware of a programming language in which a compile-time ECS would be easy to write/read.


There are lots of languages for mere mortals. They are not compatible with C++, but that's a feature. The next generation is largely not writing C++.


In this kind of thread I always mentionned Circle[0] from Sean Baxter. It's worth a look.

[0] https://www.circle-lang.org/


Great new idea in cpp. Automated bounds checking in the hello world example sold me straight away. Try and do that as tersely Haskellers. I hope this project gets momentum.


Any good CMake integration besides https://github.com/modern-cmake/cppfront?



I like the idea, although many choices are arguable. For example, having to introduce mandatory/prohibited white space around binary/postfix operators (like "&") completely spoils the goal of having a more rational syntax.


The whole story about what cpp2 tries to be, distancing itself from other C++ wannabe replacements is only due to be coming from WG21 chair, which naturally can't talk about a C++ replacement as per conflict of interests.

Cpp2 isn't an alternative syntax to C++, as much as C++ and Objective-C aren't alternative syntaxes for C, even though they support a subset of it, and were born exactly the same way, code translators into C.

C didn't evolve into them, they became their own ecosystem, tainted by the underlying C compatibility.

The only alternative that is really a Typescript for C++, is Circle.


I do not understand why there is a push to change the C++ syntax. There are plenty of new natively compiled languages like D, Go, Rust, Zig, Nim, Odin, Crystal, and so on. If you do not like C++ you always can use some of them instead. The problems that make C++ unsafe are semantic and not syntactic ones.


I don't want bounds-checking, so lost interest immediately.

This is a step in the wrong direction.


There's barely a point in making a better C++ if you're not going to address one of the most obvious footguns.


The main iteration this cycle for the next C++ is contracts.

Contracts are all about introducing undefined behaviour if you don't satisfy a precondition.

In practice this improves software quality on many levels by clearly defining requirements on interface boundaries that would otherwise be implicit or just documented.

Of course you can have special debug modes where you actually check that contracts are being satisfied.


> The main iteration this cycle for the next C++ is contracts.

As ever the C++ train leaves on schedule with or without anything you suppose is "promised" for that standard revision. This has been the practice since 2011 and I don't expect it to stop unless ISO tells them "Enough" or the whole thing comes apart.

> Contracts are all about introducing undefined behaviour if you don't satisfy a precondition.

Nope. That's explicitly not what the proposal sets out to do. It is likely that, as usual, WG21 will manage to take facilities intended to be safe, file them to a sharp edge and then slit their own throats, but P2900 in its current form doesn't do so. Here's an item from the proposal's list of things they're explicitly not proposing:

"The ability to assume that an unchecked contract predicate would evaluate to true, and allow the compiler to optimize based on that assumption, i.e. the assume semantic"

One of the three significant implementers, Microsoft, actually strongly objects to any idea of introducing yet more Undefined Behaviour into a language that is distinctly underwater when it comes to provable correctness. Microsoft shut down previous proposals in this area, and while they might (wrongly IMO) be sold the compromise position that's advocated by some WG21 members today (some UB in some contract checks), you're asking for a lot more.


I've been involved with the C++ standards committee for 12+ years and my experience is that it is largely political; championing a proposal is more of a demagogic endeavor than anything else. Influential players have been priming contracts (that have been in the works forever) as a core objective this cycle and giving it a lot of momentum for it to happen this time. Of course if people still can't be convinced it's good enough before the deadline, it won't make the cut, but it's on track.

Now regarding undefined behaviour, it's always been the reason why the proposal isn't moving forward. Some people believe religiously undefined behaviour is evil and will oppose anything tied to it. The kind of clause you mentioned is just to appease them and attain a compromise.

But ultimately, contracts and undefined behaviour (depending on the definition you go for) are intimately linked.

Consider lower_bound. The behaviour is undefined if the input is not sorted. That's just the way it is.

You can't magically make a program well-formed if you breach preconditions even if you say optimizers are not allowed to make additional assumptions.


> Consider lower_bound. The behaviour is undefined if the input is not sorted. That's just the way it is.

No, it's a classic WG21 choice in that it's pointlessly worse, that's not "the way it is" it's just the way WG21 decided to define it in C++. More reasonable choices would obviously include the choice made over in std::set where lower_bound gives you a past-the-end iterator if there isn't an apparent answer for your inputs.

Undefined Behaviour is the worst choice, which is why in a horrible way it makes sense as WG21's default - a group known for always choosing bad defaults.

> You can't magically make a program well-formed if you breach preconditions

Of course you can, it's not even hard.


Ignoring the fact that WG21 is not a single person or entity, but 200 people with disjoint interests, your whole argument doesn't make sense.

std::set preserves sorted as an invariant, so there is no such precondition on its lower_bound member.

The important requirement for std::set is that the comparison function satisfies a strict weak ordering. Again, the behaviour is undefined if it does not.

There is no substitute for correctness. You need to satisfy the requirements or stuff doesn't work. Whether the "doesn't work" means an error, a crash, corrupt memory, resource leaks or incorrect results doesn't really make much of a difference: the code is incorrect and should be fixed.


> Ignoring the fact that WG21 is not a single person or entity, but 200 people with disjoint interests, your whole argument doesn't make sense.

How did you get the idea that I mistakenly believed WG21 is a "single person or entity"?

> There is no substitute for correctness. You need to satisfy the requirements or stuff doesn't work. Whether the "doesn't work" means an error, a crash, corrupt memory, resource leaks or incorrect results doesn't really make much of a difference: the code is incorrect and should be fixed.

This is how WG21 ended up here. Programmers who believe that it just "doesn't really make much of a difference". Except, it turns out that everybody else can tell the difference


> How did you get the idea that I mistakenly believed WG21 is a "single person or entity"?

> > classic WG21 choice

> This is how WG21 ended up here. Programmers who believe that it just "doesn't really make much of a difference". Except, it turns out that everybody else can tell the difference

C++ is a practical language for practical programmers solving real problems. Language purists or Rust zealots will be happier elsewhere.


> classic WG21 choice

So if I said that "Uniting and Strengthening America by Providing Appropriate Tools Required to Intercept and Obstruct Terrorism" act is a classic naming choice by the US Congress your understanding would be that I've mistaken the Congress for a "single person or entity" ? Even though in reality this stupid habit (ridiculous acronyms in the titles of legislation) is indeed a hallmark of American law making and not of some imaginary single person ? I guess I can't help you.

WG21 may consist of dozens of people, but the character of its decisions is a reflection of the larger body, not somehow merely a reflection of those individuals. To my mind ISO's process is tremendously ill-suited to the work of developing programming languages, and while JTC1 itself makes sense, neither SC22 nor the various working groups are a good way to develop these languages.

> C++ is a practical language for practical programmers solving real problems. Language purists or Rust zealots will be happier elsewhere.

The problem with this characterization as a "Practical" language is that the user community do not want your "solutions" any more being as how you think it just "doesn't really make much of a difference" what happens to them when, inevitably, you are less than perfect.

That's not very practical at all.


Users should make their own opinions and evaluate what is the solution that best satisfies their requirements -- which in most cases should be to build it themselves (third party code is mostly a liability in software development).

It seems you want to force people to use your "one true way". That's not programming, that's a religion.


With Azure now only considering C++ for existing projects, and new projects require explicit reasoning why C or C++ instead of Rust, I expect some internal pressure at MS to do the right thing.


Web/cloud tech is usually at the complete opposite of the software development spectrum from where C++ shines.

I thought they invented Go for these things?


You think you've heard it all, but then someone says that introducing undefined behavior improves software quality, and just wow, man, I don't know.


Unless I can step through original source code in debugger, watch variables etc. etc. I could not accept any source to source translator in my practice.


It is a good thing that cppfront lets you do that, then!

Cppfront generates #line pragmas which tell the generated .cpp file which source lines to "blame" for each piece of generated code. This isn't something new and fancy for cppfront, it's a bog-standard pragma that your debugger already understands. So it will work the exact same as your current debugging workflow even if you mix cpp and cpp2 source files.


I'm working on a hobby project language that generates plain C as output, and debugger integration has been one of my big worries. If that works, then this is awesome, thank you!


What are the tradeoffs between cpp2 and Carbon?


At this point the focus should be on making interop with python a first class feature.


Like Kotlin to Java

Or ReScript to OCaml

Or Gleam to Erlang


Kotlin is a distinct language with new features that (I'm fairly sure) make it non-isomorphic to Java code though. But I can't speak on the others.


not sure what "(non)-isomorphic" but Kotlin code is interoperable with Java code in both directions. You can easily call Java-code from Kotlin (but I guess that's the easy direction) but you can also call Kotlin code from Java. There exist specific annotations to control how Kotlin code will be converted to bytecode and therefore be invoked from Java, e.g. a function in a Kotlin companion object would normally concerted into a function of a Singleton property called INSTANCE on the base class, but if you annotate it with @JvmStatic it will become a static method of that class in bytecode instead. This means you can write a Kotlin lib that feels very normal to call from Java. here's the relevant part of the documentation: https://kotlinlang.org/docs/java-to-kotlin-interop.html So yes, Kotlin is considered to be a successor language, not just another language (also) compiling to the JVM.


An isomorphism is a structure preserving 1:1 map between two sets or some other pair of structures. While Java and Kotlin are interoperable (because they both target the JVM), they are not isomorphic for precisely the reasons you described. If it was, then you could do round trip machine translation on the syntax in either direction and have a result that's identical to the original. You can't do that because Kotlin extends Java in nontrivial semantic ways (for a concrete example, see Nothing in the article you linked).

Cpp2 isn't trying to be a successor language to C++, the article states that it's trying to present an identical feature set in a new skin where the best practices of modern C++ are more ergonomic without introducing any new functionality.


> Cpp2 isn't trying to be a successor language to C++

Herb is trying to sell it as not a successor because for now that suits him better.

Because C++ is a general purpose language Herb can deliver a "not a new feature" that is so elaborate in practice you would never write the equivalent C++ by hand, but Herb can insist that since technically it can still be transpiled to C++ it's not a different language... right up until that ceases to suit his agenda.

Under this model WUFFS (a higher performance yet entirely safe language for writing stuff like codecs and compression algorithms) doesn't "introduce any new functionality" compared to C, since the present WUFFS-the-language is transpiled into C. But I think C programmers would be astonished to hear that C is now apparently higher performance than C and is able to guarantee it's entirely safe...


The linked article explicitly states

> What it isn't. Cpp2 is not a successor or alternate language [...]


That's correct, Herb Sutter does indeed claim that his C++ Successor Language isn't a C++ Successor Language, as I stated.


Mostly because it would look odd if the WG21 chair, would also sell his language as a C++ successor language, lets not fool ourselves it is something else.


Try to call Kotlin co-routines directly from Java to see how it goes.

Or call Java features from Kotlin, that aren't available on Android.


Or, Cfront to C? :)


all of those compile to the respective languages' bytecodes, not to source code


Surprised to see Gleam instead of Elixir, I haven't heard of the former before.


or more famously, typescript to javascript, which is the example Herb uses.


Most of my personal issues aren't with C++ syntax as such (although it also has many problems). My main gripes are:

1. Very slow compilation.

2. Poor encapsulation, adding private functions requires recompiling all dependents, see (1).

3. Comically huge symbols make debugging much harder than it needs to be -- today gdb OOM'd my 16GB laptop when trying to form a backtrace of a typical QT application coredump.

Unfortunately it doesn't seem like cppfront can fix these issues. It may still be a worthwhile effort in other respects, of course.


I laughed at (3)

Although QT is not a tiny framework, and I don't really know if modern C++ tools are really good enough for this sort of problem, since C++11 to 20 probably caused those tools to explode in memory consumption

But I am not surprised at all. I remember around 2013, I would use bullet physics and the Ogre3D engine, and I had to tell visual C++ to increase its memory capacity because the compiler would refuse to continue.


3) is forcing me to incorporate a symbol strip/upload to sentry piping at $WORK because Eigen debug symbols cause our build sizes to explode.


Adding methods changes the vtable layout so there's no way to not recompile all the dependents. There's no solution to this unless private functions are guaranteed to not be virtual.


There isn't that much work the compiler should need to do. Ideally, a clever compiler could just update the vtables in some intermediate representation of the program and re-emit the binary. The C++ compiler today is so insanely wasteful - edit one header file and the compiler re-parses all the header files N^2 times. It should be possible to make a compiler thats way faster than any C++ compiler today.

Of course, the elephant in the room is the C++ preprocessor. I haven't looked too closely into cppfront, but if I had the chance, I'd give the C macro system a bullet.

My favorite "macro" system by far is zig's comptime, which is beautiful and elegant. Zig code can simply elect to be executed in the compiler instead of at runtime. For example, here's how the print() function compiles in zig. Its a thing of beauty:

https://ziglang.org/documentation/master/#Case-Study-print-i...


The problem is reflection, including SFINAE. Code can branch on if a method exists.


So? It shouldn’t take seconds to make one change to the reflection database. Or update derived code.

Incremental compilation is fundamentally the same problem that web frameworks solve. There’s plenty of efficient ways to do it.


> The C++ compiler today is so insanely wasteful - edit one header file and the compiler re-parses all the header files N^2 times. It should be possible to make a compiler thats way faster than any C++ compiler today.

Well, yeah, that's the problem that modules are supposed to solve. (AFAIK the only fully C++20-standard-compliant implementation of modules is in MSVC, although even Clang modules are adequate for drastically reducing compile times.)

You are still going to have to recompile all your dependent compilation units if the vtable layout changes, as mentioned elsewhere, since all of your callsites have to reflect any changes to vtable lookups.

> My favorite "macro" system by far is zig's comptime, which is beautiful and elegant. Zig code can simply elect to be executed in the compiler instead of at runtime. For example, here's how the print() function compiles in zig. Its a thing of beauty:

How is this different from constexpr / consteval in C++?

std::format (introduced in C++20) is implemented in a similar fashion in that the format string is checked at compile-time (number of args, types, etc), so there's no good reason why C++ couldn't have a print function that behaves the same way and is validated at compile-time [0]. Libraries such Abseil and folly certainly provide this.

[0] It seems that C++23's std::print is not that function, oddly enough.


In typical c++ manner, we can’t have nice things because {obscure internal technicality that don’t impact the user in any way whatsoever}$. As a user I don’t care what a vtable is. Just make it work. Other languages don’t have this problem.

During release builds sure, optimize away with LTO and whatever is needed to make it vroom. During development, waiting several minutes for a minor private function update is just absurd.


C++ is designed for good performance. Solving ABI problems permanently usually means an extra layer of indirection, which hurts performance.

There are also concerns about what you can link against or not once you modify the ABI of widely used types. This is of real concern at least for closed-source software or software that just cannot be recompiled.

I am not saying it should be the right choice, some of that software is legacy. I just say this is a real concern.

As for the private function. You can use a pimpl and use a private (on the cop file instead of header) and use that, for example, in many cases. This keeps compile time down.


Yes, there are solutions, and the simplest one is replace the direct vtable lookup with an indirect one where the methods are referenced not by their numeric offsets in the vtable but via their symbolic names and the offset resolution is performed via a separate lookup table – not that dissimilar from how it is done in ELF shared libraries.

All of them will result in incurring a performance penalty, either at the runtime, or at the start-up time, or in the compiler/linker, and memory blowouts. But it will solve the recompilation problem.


Um... isn't that guaranteed? What would it mean for a private function to be virtual? It can't be overridden by a different implementation in a child class...


Yes, you can have a private virtual member function, and it can be overridden in a child class (unless declared final), apparently.

I too thought that sounded insane, so I just looked it up. I've been programming C++ for twenty five years and the thought of wanting to do this have never ever occurred to me...


It could make sense if you have a method that you want derived classes to be able to override but not to be able to call directly.

This is, admittedly, a pretty niche case but certainly not inconceivable.


Except the derived class can simply change the visibility of the override, so...


TIL.


`final` prevents a child class from overriding a method. `private` does not.


Child classes can override private member functions in their parent class.

Making all virtual functions private is the better way to implement inheritance: it separates implementation from interface, allows for common functionality to be moved to the base class without requiring gymnastics in each and every derived class, and even lets you simplify the public interface to reduce compile times.

Try it, you'll like it. You may never go back to writing Java in C++.


Wait, what? Virtual methods in C++ are opt-in. You only need the 'final' keyword when you're overriding a method in a child class.


under what circumstances does (2) hold? for vanilla methods it's no problem


C++ build systems are typically based on file timestamps. Modifying a header file triggers recompilation of all translation units including that header.

There are workarounds like pimpl (aka. C style encapsulation). But this requires extra boilerplate and indirection. C++ modules might fix it at some point, but after 35 years of not having them in C++ most real life codebases aren't set up that way and may never be.


I don't think of pimpl as a tool for speeding up compilation, but for black box encapsulation.

If the compile time (when adding a method) is really an issue you can chop up and reconfigure your include files. A pain, but perhaps saves you time in the long run.

Of course (waves hands) modules will magically improve things...someday.


I think it can be both things.

Haven't you ever seen someone do

    struct Thing;
    struct OtherThing;
in lieu of just including "thing.h"? I see it frequently in real life code bases and I can't see a reason for it other than compilation time optimisation.


Sure, I do that all the time too. But you can't call a method (or look inside Thing, or pass it as an argument, only a pointer to it) without including the definition.

Hmm, there might be some interesting linker hacks to patch things up post compilation. But then you'd want some way to do the forward declaration for cases where Thing could have been passed in registers...


This is sometimes required to break dependency cycles. Also, you can use this to rearrange declarations in the same file.


Pimpl is both. I use it as black box also, but it does both things.


Adding private functions or fields requires changing the class declarations, which requires rebuilding any code that includes that class deckaration. It shouldn't be like that, especially for methods which shouldnt change anything about the class ABI.

Even worse, this dependency is transitive. Dependencies to allow defining these private methods an fields are exposed too, forcing inclusion of headers to all members of the class, even if it's only implementation details.


>>> 2. Poor encapsulation, adding private functions requires recompiling all dependents

>> under what circumstances does (2) hold?

To add a private member variable or function, you need to put it in the class definition in the header file. Then anything that includes the header needs to be recompiled.


Admittedly, adding a private member variable changes the object size and thus the ABI and thus requires recompilation of dependencies.

Thinking about that, is there any case in which private functions can end up in a vtable? In that case, it'd break ABI too.


> is there any case in which private functions can end up in a vtable?

Yes, but it generally isn't something that is done.

https://godbolt.org/z/5oPovKzoT


they don't need to be. dependents will continue functioning, because ABI hasn't changed


Now explain that to my build system.

But even if you managed to do that, first compilation is still much slower than it should be, because anlot of headers have to be included (transitively) to allow even declaring these fields and methods.


If you touch a header, even private only, includers will rebuild. Modules might fix this.


How is dependency management not in this list hahaha holy crap c++ is so fucking shit to work with in this regard compared to another "modern" language.


It was when debugging a memory leak which occurred because I forgot to declare the base class destructor as virtual that I started to think C++ was a rather unfriendly language and not really designed to be easy to use.

Then a few years later I read the spec for std::launder that I realised C++ was not really designed to be understood.

It's a shame because it's actually a rather nice language in some ways. Here's hoping that this project or something similar takes off and separates the good bits from the bad.


The evolution of C++ has been a multi-decade history of dealing with difficult reality.

I have great hope that Herb can create with his cppfront project “The Very Best of C++” to carry that tremendous legacy forward.

If I was to throw my hat into a “C++ successor”, it would be https://www.hylo-lang.org/ with its “all the safeties” and “tell you when you’re doing it sub-optimal” approach.


Hylo is interesting in principle for exploring this particular notion (mutable value semantics) as a way to potentially write software without the lifetime annotations Rust needs.

But I don't find it promising that after apologising in 2023 for missing their self-imposed 2022 deadline to ship something that works and other people can use, in Q2 2024 it doesn't look like their new 2023 roadmap got done either. Maybe they're going to eventually deliver this amazing thing. Maybe they're just going to learn some lessons (probably for the Swift community) and never ship Hylo per se. Certainly 2025 "Take over the world" looks... ambitious with nine months left to do all the stuff left from 2023 and all the work described for 2024 on top.


For me Hylo is the most elegant object model I have seen so far in the sense that it achieves dafety without all the anniyations and borrow checks that Rust performs. It just fits well the model for thinking yet it enables lots of optimizations. Of particular mention is that copies are lazy and can be passed freely and will only be done transparently on demand. That is what keeps the model simple.


Yeah... its shocking to me how difficult it is to read the C++ standard library. Surely, the standard library is written by the authors of the language. It should be a positive example of how they hope their language is used, right?

Here's the source of C++'s vector class:

https://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a0111...

In comparison, vec in rust. (Note you need to scroll down a few pages to start seeing non-trivial functions. There's a lot of block comments.):

https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#398

Or list in Go:

https://cs.opensource.google/go/go/+/master:src/container/li...

To my eye, that C++ code is by far the hardest code to read.


Notice that there are in practice three distinct implementations of the C++ standard library. They're all awful to read though, here's Microsoft's std::vector https://github.com/microsoft/STL/blob/main/stl/inc/vector

However you're being slightly unfair because Rust's Vec is just defined (opaquely) as a RawVec plus a length value, so let's link RawVec, https://doc.rust-lang.org/src/alloc/raw_vec.rs.html -- RawVec is the part responsible for the messy problem of how to actually implement the growable array type.

Still, the existence of three C++ libraries with slightly different (or sometimes hugely different) quality of implementation means good C++ code can't depend on much beyond what the ISO document promises, and yet it must guard against the nonsense inflicted by all three and by lacks of the larger language. In particular everything must use the reserved prefix so that it's not smashed inadvertently by a macro, and lots of weird C++ idioms that preserve performance by sacrificing clarity of implementation are needed, even where you'd ordinarily sacrifice to get the development throughput win of everybody know what's going on. For example you'll see a lot of "pair" types bought into existence which are there to squirrel away a ZST that in C++ can't exist, using the Empty Base Optimisation. In Rust the language has ZSTs so they can just write what they meant.


Part of the distinct "C++ std" style is due to naming rules and textual includes. Every name that's not part of the standardized interface starts with __ because __foo and _Foo are blanket reserved names, so a user can't complain that std:: explodes when he does "#define _Base 0".


> It should be a positive example of how they hope their language is used, right?

should it though? there's a million ways to learn C++. Reading the std code definitely isn't one - technically the std could be entirely compiler builtins. If you want to read positive examples take A Tour of C++ 3rd edition (https://www.amazon.ca/Tour-C-Bjarne-Stroustrup/dp/0136816487)


"Do as I say, not as I do" is known to be poor pedagogy.

If you find that expert practitioners don't do the things you think students should be doing, it suggests that something is wrong and needs fixing. In the standard library implementations it's very obvious that something is badly wrong, and yet for decades C++ has resisted the hard work of fixing it.


Yet thousands of people learned C++ well enough to use it in a professional setting, so surely << "Do as I say, not as I do" is known to be poor pedagogy. >> does not hold in the general case.

> If you find that expert practitioners don't do the things you think students should be doing, it suggests that something is wrong and needs fixing

It does not and is a very naïve world view. In any trade expert practitioners' way of working is wildly different from what you would learn in a classroom (and generally makes said student's hair raise on their head when they see. This is not too relevant with C++ though as the std implementation "ugliness" is mainly driven by material constraints.

Besides in general in programming this is not even possible. Like, from your argument one wouldn't be able to learn how to use the Win32 API or Cocoa API since the operating systems using them are closed-source and you cannot see how they are implemented & used by the teams who develop these APIs.


No, I reject the claim that "my argument" (not in fact mine) says you can't learn an API unless you can see how it's implemented. I don't think that's a remotely plausible reading of what was written. Instead I agree with the claim they actually wrote that since software is intended first and foremost to be read, it makes sense that the standard library, software you'll be using as a programmer should be a positive example and not a horrible distorted mess.


> This is not too relevant with C++ though as the std implementation "ugliness" is mainly driven by material constraints.

These "material constraints" are clearly completely artificial - since they don't show up in other languages (rust, go, swift, haskell, etc etc). For example, in C++'s std header files:

- Symbols are hand-mangled. (Why? Wasn't that the whole point of C++ namespaces?)

- There are no comments. I'm guessing the reason for this is that C++'s idiotic build process dedicates an insane amount of CPU time to redundantly reparsing the std header files over and over, forever. This has a very real performance impact across the ecosystem. Either that or the authors just don't believe in commenting their code.

- Templating seems to make the code even more unreadable. Which is strange, because rust's standard library also uses generics and yet it is totally readable.

- Different C++ compilers have different implementations of the standard library, with different performance profiles and quality standards. There is no good reason for this.

> Like, from your argument one wouldn't be able to learn how to use the Win32 API or Cocoa API since the operating systems using them are closed-source and you cannot see how they are implemented & used by the teams who develop these APIs.

This wasn't the central argument, but you're still kinda right about this!

I don't have much experience with windows, but I can tell you from my personal experience that it is significantly harder to understand Apple's platform APIs because the code is closed source. There are lots of important methods in Apple's APIs with obscure, technical names and next to no documentation. You have no idea what they do, or if they'll solve the problem you're facing. Its crazy frustrating. When working with opensource code (eg rust, javascript, java, etc) I'm constantly reading the source code of library functions I call to understand how they work and what they do. Its like a backstop for documentation. If the docs are missing or not good enough, having the code available means I can still almost always figure out how to solve my problem.

I can't find it now, but there was a comment thread about windows engineers a few decades ago admitting they made some APIs obscure and badly documented on purpose so they could make money writing and selling technical books on the side on "Windows Internals". Because the source code wasn't available and documentation was shoddy, you needed to buy those books in order to understand and use some of the windows APIs correctly.

So yes, library code should be readable. When you can't read the libraries you're using, it causes all sorts of problems.


I work on clang and don't know go yet still find the go version easier to read.


I think the c++ version could be more understandable but it’s as if the authors intentionally made it as obtuse as possible.


The authors are required to make it obtuse. They're required to use warts on all of the names because most of the code is in the head files and is generative code compiled by users of the library rather than the vendor. In order to avoid naming conflicts they can only use obscured names in their implementation of any but the defined API (eg. naming any internal functions, macros, or variables with leading underscores).

So, the authors did intentionally make it as obtuse as possible for your benefit. It's written to be used, not studied, by all kinds of developers in all kinds of circumstances.


They could supply a "pretty" version for people who want to review it. Every time I have to step through code (and accidentally step into STL code) it looks sloppy and gross, like a swamp. No comments or organization. I would expect something neatly formatted, and comments saying "This is overload-4 of std::copy()..." etc.


Professional software developers have a lot to do just to get their job done on time and within budget. Having to duplicate all their code just so that people who contribute nothing to the end product can have an easy time understanding it is just never going to be a priority worth addressing.

The problem here is not really the code, it's the reader.


Who says they have to duplicate it? Just write the original version clean, clearly, and concisely. Then run it through a mangler to rename variables to avoid collisions.

The STL is maintained by volunteers, it's a FOSS project. So your appeal to Serious Business doesn't hold.


[flagged]


Why would you use cmake to build a single file??


That's the problem, it's a single cpp file because it's built manually. The source code is spread among many hpp files for no reason other than being able to include everything in that single cpp file. The project organization of cpp2 is abysmal.




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

Search: