Hacker News new | past | comments | ask | show | jobs | submit login
Goals and Priorities for C++ (open-std.org)
140 points by cyber1 on May 21, 2020 | hide | past | favorite | 197 comments



I find the combination of 1) C++, 2) a goal of "Code that is simple and easy to read, understand, and write", and 3) a non-goal of backwards compatibility, to be strange.

The reason C++ is the federal tax code of programming languages is that it is the accumulation of decades of ideas that seemed good at the time--some even were--where each better way to program ended up as the lifeblood of some special interest group that can never be removed, only piled on top of.

By subsetting C++, you can customize it into almost anything you want with just a few exceptions. You can't meaningfully simplify it in a backwards compatible way without breaking most subsets.

So these people are saying it should be simplified in a non-BC way, the only real way to do it, that preserves their own favored subset.

That's what most of us call a new programming language. Trying to turn C++ into !C++ guarantees they'll be fighting over this for years. With the resources Google has, I don't understand why they don't just throw off all C++-based constraints and use those years to create exactly what they need. With half the PL PhDs on the planet in their employ, you'd think somebody would be available. (Maybe even enough for multiple, parallel teams req'd to share ideas with each other.) It would take a few years to mature enough to rely on, but they'd be spending those years in the C++ cmte debates anyway. A new language called "non-BC C++" will need new tools, new libs, etc., anyway, and they'll have the current C++ to keep using until their alternative is ready.

Why not just create that simple, easy, customized-for-server apps language from scratch instead of making it by breaking C++?


> Why not just create that simple, easy, customized-for-server apps language from scratch instead of making it by breaking C++?

It's expensive and inefficient to go it alone. It makes it harder to hire and harder to both benefit from and contribute back to the open source community. So they make their pitch and see if anyone else wants to get on board and only if that doesn't pan out will they try to go it alone. The price of going it alone might actually be higher then the benefit.


From the link:

> Support maintaining and evolving software written in C++ for decades

...

> This requirement should not imply compatibility, but instead some (likely tool-assisted) migratability.

I think the idea is you can gradually deprecate outdated features if there's a way of automatically refactoring them out.

So you might still be able to compile old code, you might just need to run it through a bunch of migration scripts first.


Specifically, absl (Google's C++ library already provides this guarantee). In general the idea is that they will provide backwards compatibility for 5 years, and provide tooling to update source code to new constructs. This is covered somewhere in this talk: https://www.youtube.com/watch?v=tISy7EJQPzI


It’s called Go. The downside of having half the Pl PhDs on the planet in their employ is that they can not come to an agreement.


The people who designed golang did not have PhDs in PL. They were not even PL designers to begin with, and it shows at how poorly designed it is. (And no, one person who happened to work on C doesn't count, since language design evolved a lot since then).

Incidentally, golang did not end up replacing C++ or Java in the slightest, especially at Google. It is in the same space as python.


I have criticisms of Go too, but your statement is factually incorrect. Robert Griesemer did a PhD under Niklaus Wirth.


Thanks for the information, I didn't know that. It just make it weirder how the language turned out. Perhaps he didn't influence the initial design enough?


Having a PhD on a field does not automatically imply you can produce good results, specially if targeted for widespread consumption. It is actually very rare when that happens.

I am not saying that is the case here one way or the other, though.


Under Niklaus Wirth nonetheless, that's just awesome, the grand dad of performant compilers.

Having a PhD means you studied for years to push boundaries, nothing more, nothing less. Lets not forget that the bosses usually decides exactly what should be done with sprints and standup, especially in teams that are not research focused.


Actually I think go is semantically much closer to oberon than to C.


Or for systems coding and performance-critical work, Rust.


While I agree that Rust has advantages for performance-critical work, I don't think "systems" implies performance criticality. In the Unix tradition, "systems programming" seems to be a slightly blurry term for writing the basic tools that come with the system, not necessarily just the kernel and such. I think the term is supposed to be in contrast to "scripting", but I'm not sure the duality is that strong. For example, USENIX '91 had a paper "Awk As A Major Systems Programming Language", which implies that implementing tools such as 'nroff' (one example used in the paper) counts as "systems programming".


In 1991 there was a very distinct job called system administrator and the really high skilled ones wrote all sorts of system administration tools in awk, Perl, Shell, and sometimes in C. This to me was 'systems programming' for the administration, deployment, automation, and management of the 'systems'.

This was in contrast to "developers" who wrote application code in numerous languages COBOL, Pascal, Fortran, BASIC, etc. And they in general, did not manage the underlying system.

Over the years I think those lines have blurred and the roles are all smashed together sometimes called devops or other terms.


Yes, this is close to what I'd also prefer to be my definition. "Systems programming" is about writing the programs that users don't care about. This involves all kinds of infrastructure: certainly the kernel, but also various other bits of critical plumbing. But that doesn't fit the definition from that Usenix paper, as nroff was an important user-visible application program, even if it isn't much anymore. I think it is probably best to avoid using the term "systems programming" except when within a constrained community where the intended meaning is clear. Just look at how much confusion arose from the original announcement that Go was a systems programming language!


I grew up with the understanding that "systems programming" is the opposite of "application programming", or, in other words, the opposite of "programs that the end user will use". So that would include the kernel, but also anything between the kernel and the end user, including compilers, servers, and so on. Yes, it's a blurry line.


BTW the term “systems programming” predates Unix. While I first encountered it through Multics I believe it’s actually an IBM or Burroughs term from the early 60s.


Hmm, interesting. I've only ever heard of systems programming referring to bare metal or close to bare metal programming. i.e. embedded software, kernel-space, or otherwise hardware adjacent programming areas.


> Why not just create that simple, easy, customized-for-server apps language from scratch instead of making it by breaking C++?

They did that already with Go (one can argue that Go has very different priorities than C++, but Go matches "simple, easy, customized-for-server apps" perfectly).


Yeah. Literally the only thing I can think of off the top of my head that you can probably remove from C++ without significant damage is virtual base classes, and even that I'm not really sure about. (Curious if anyone here has had much of a use for them.) But anything else seems like it would just fundamentally destroy the language for various slices of its users.


Virtual base classes are extremely important for certain cases such as extending library classes (that were designed for it). Virtual baseclasses are rarely important I admit, but provide functionality you can’t get any other way.

I recently worked on a large system that had three virtual baseclasses. But trying to do it any other way would have involved a lot of painful head stands.

I designed BFD using a vtable implemented manually in C. It was quite painful though there was no other choice. I’m glad I don’t have to deal with that any more.


Interesting, thanks for sharing! What would've happened if you had just used regular base classes in that case? Would you happen to have an illustrative example that could show what problems it would cause in practice/why it couldn't be designed differently? I mean besides just citing "multiple inheritance", which isn't a problem in itself—what matters is how you intend to use it and what might go wrong.


This did notrequire multiple inheritance.* It is for the serialization system for a complex semantic graph library. The library itself did no I/O. That was fine for writing various UIs and programs that used the library. But how to save and restore the graphs? They included a bunch of statistical and bookkeeping data not exposed in the UI.

I wrote a “serializer/deserializer” package that did t actually save anything but could represent the data and swizzle it so the in-memory structures could use raw pointers. This could be validated as correct in CI.

Then in a separate I/O library I wrote a module that could generate JSON and store it in a file, using specializations of these virtual baseclasses. When the underlying library changed representation client code didn’t have to written.

My JSON thing was too slow so someone wrote one that backed into a SQL database. A lot faster, though versioning went out the window. Anyway someone could write a fast binary format if that matters and they should all continue to work.

* I mostly gave up on MI in the mid 80s when it was clear it made encapsulation harder to understand (and this was in a language with explicit method combinators too). However I still find mixin classes quite valuable, though again not in widespread use.


Oh wow. So it sounds like what you got out of virtual inheritance here is avoiding some kind of ABI breakage? That's interesting, I've never come across this use case before, but I'll be thinking more about it and looking out for it in the future. Thanks!


No, it was about encapsulation. Consider the save dialogue: if wants to have an array of the json saver, sql saver etc and perhaps display them in a pull down list. Virtual inheritance let’s you make the std::vector of the base class. Otherwise what could you use? A pointer, and manypually reimplement the vtable?

Also this means changes to the underlying representation don’t always require changes to the dependent classes, which I suppose you could consider an ABI change.

Maybe an easier way to characterize it is to consider the virtual classes a way to implement plug ins.


One annoying thing about C++ is that certain terms and keywords are overloaded. So, while having virtual functions in a base class is a very common design approach, it isn’t “virtual inheritance” ( https://en.wikipedia.org/wiki/Virtual_inheritance ).

Many people consider virtual inheritance a complexity of the language. Why is it possible for a class to inherit from a base class multiple times through multiple paths (the standard case) or to have the compiler coalesce those paths so that the child class only inherits once from the parent/grandparent? The thing is, once you have multiple inheritance, somebody is going to try to inherit from the same base class multiple times. The language can either support it or declare it an error. It’s not clear to me why it’s wrong to for the language to allow it, even though everywhere I’ve worked with a C++ coding standard has said “if your design requires virtual inheritance, discuss your design with other experienced programmers to see if that can be changed.”


> One annoying thing about C++ is that certain terms and keywords are overloaded.

It's a no-win choice, though I agree C++ chose the wrong path: trying as much as possible to avoid adding new keywords. It leads to the problem you describe, as well as some confusion in this very thread (which could be my fault, but honestly I don't know -- that's the problem!).

The committee seems to have moved away from this over the years which has lead to complaints of too many keywords and identifiers. Which is why I call it no win.

The function virtual specifier in the base clause is reasonable: if you store something into parent_class.instannce_var and there are two copies of parent_class in your structure which one is stored to and is the choice consistent in all compilation units? This specifier guarantees that the answer is "yes". I don't know why it isn't the default though.

More generally, back in the day I used to use a bunch of different method combinators with flavors (later CLOS). Apart from the useful :before and :after, they generally lead to undebuggable code.


I understand why they try to reuse keywords and symbols, and why they intentionally pick really ugly keywords when they add new ones. I can’t say they’re wrong, but I still find it annoying.


They could just go the C-way and introduce new keywords in reserved namespace (`_Static_assert`) and provide opt-in header to make them less ugly (`assert.h` and `static_assert` macro).


Okay then I'm lost. Why can't you have JsonSaver, SqlSaver, etc. inherit from a Saver class the normal way, and store instances of them in a std::vector<std::unique_ptr<Saver>>? Then you'd just need struct Saver { virtual ~Saver() { } virtual void save(...) { ... } } along with struct JsonSaver : Saver { void save(...) override { ... } } and it should work, right? I'm confused why you need a virtual base class on top of that. Are you by any chance confusing "base classes with virtual methods" with "virtual base classes" (which may or may not contain virtual methods)?


Yes, I mean literally base classes with vtables not the "virtual" specifier of a superclass ("base class" in C++). Apologies for the confusion!

I do think it's a useful feature, except as I think most people finally agree, MI is more often than not a cause for confusion.

It's funny that of all the different method combinators (another source of confusion) only that one was chosen.

BTW I don't think a unique_ptr is needed in the case I described.


Maybe I’m misunderstanding, but by virtual base classes you mean base classes with strictly pure abstract methods, that can be used with virtual inheritance? Because in that case there is a very straightforward and useful use case for them, which is composing interface classes using multiple inheritance.

For example in some piece of graph-based code I’ve been working on recently, all types returned through public interfaces are pure abstract interfaces. There are different reasons for this, one of them being difficulty to automatically wrap the implementation classes themselves to Python and Java with SWIG, because they use CRTP and other template constructs that are fundamentally incompatible with automatic script interface generation. There is a base interface class INode with an implementation class template Node<T>, but there are also derived interface classes that add additional interfaces to INode, such as ICompositeNode for example. Without virtual base classes it is impossible to have an implementation class CompositeNode<T> that derives from both ICompositeNode (for the interface) and Node<T> (for the implementation), because it is ambiguous. Virtual inheritance solves this.

I never considered this a very special or difficult use case, it seems pretty much any kind of component-bases programming using abstract interface classes would require this sooner or later?


> Maybe I’m misunderstanding, but by virtual base classes you mean base classes with strictly pure abstract methods, that can be used with virtual inheritance?

No. Virtual base classes come up in multiple inheritance.

Consider you have a class Circle. It derives from both Shape and Savable. But Shape and Savable both derive from a parent base class Base. Let's say that Base has some member function, print. Also assume that neither Circle, Shape or Savable have an implementation of print. So when you have a Circle object, and you call print, does it go to Shape::print or Savable::print? If Base is a normal (non-virtual) base class, it's ambiguous. The compiler can't resolve it, as the Circle object literally has two Base instances inside of it: one from Shape and one from Drawable. The inheritance hierarchy has two roots, both of which are an instance of Base.

But if Base is a virtual base class, then Circle will only have one instance of Base. The inheritance hierarchy has only one root, which is Base.

This is generally called "the diamond problem": https://stackoverflow.com/questions/2659116/how-does-virtual...


Can't you just cast though? Like this: https://gcc.godbolt.org/z/Mq74Bo

I get that it's a bit of an inconvenient workaround, but it doesn't seem impossible to handle? (At least as far as I've thought it through. I might be missing something though.)


I would have to go back to the code to see what the difference is with your example, but I think the problem was with overload resolution, where the base interface/implementation classes have a function by the same name as the derived interface/implementation classes but with a different signature. The compiler error was already at the declaration site, and not at the point the classes where instantiated.

Maybe I'm not even fully grasping the problem myself, but in any case virtual inheritance on the interface classes resolved it ;-S


Virtual inheritance changes the order that sub-objects are constructed and destructed. This might be used where a base class manages the lifetime of a resource and other types in the hierarchy operate on that - and so need the resource to be "alive" even during their destruction.


> This might be used where a base class manages the lifetime of a resource and other types in the hierarchy operate on that - and so need the resource to be "alive" even during their destruction.

Confused, isn't that already the case normally? Base classes are already destroyed after derived classes, and owning pointers die before their targets.


Digraphs and Trigraphs seem pretty silly to me. Multiple inheritance is bad, but idk how much code would break if it were removed.


Multiple inheritance is vital to C++. You wouldn't want to remove it any more than you'd remove interfaces from Java. It would break all kinds of things. With you on digraphs and trigraphs, though.


Only if you write C++ like Java. But there are also coding styles heavily based on templates, and then the use of multiple, or any inheritance is rare. Rust and Go can do without inheritance at all.


> Only if you write C++ like Java. But there are also coding styles heavily based on templates, and then the use of multiple, or any inheritance is rare.

It's super common to implement mix-ins, which is as based on templates as it's possible to be, with multiple inheritance, e.g.

    struct featureA { int x; }; 
    struct featureB { const char* foo{}; void print(); };
etc

and then

    template<typename... Args>
    struct mixin: Args... { 
        template<typename... CtorArgs> 
        mixin(CtorArgs... args)
          : Args{args}...
        { }
    };

    using foobar = mixin<featureA, featureB>;
    void f() { foobar{123, "foo"}.print(); }


> It's super common

Curiously recurring, even! [0]

Although what you're showing seems to be a variation on the curiously recurring template pattern, as the classes being mixed are listed 'symmetrically' without much regard for which will inherit from which. I found a blog post on 'mixin classes', which are like the CRTP but invert the inheritance relation. [1] What you've shown seems to be a third way.

[0] https://en.wikipedia.org/wiki/Curiously_recurring_template_p...

[1] https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp...


yes, it's not proper CRTP but I did not want to spend too much time on that... just show that multiple inheritance has its uses even in fairly generic, non-OOP code :-)


CRTP is a common use case. Also inheritance is often used to implement type level recursion (although this is significantly less needed now that we have argument packs).


Heavy template base code uses inheritance all the time. But here is no expectation of is-a relationship there, it is purely for code reuse. In template code, the is-a is better expressed as modelling a concept (which is done mostly via structural typing, not nominal typing).

It is unfortunate that there is no explicit way to distinguish OOP is-a from code reuse, other than the presence of virtual functions in base classes, but in my 15 years of professional use of c++ I can't remember it ever being an issue.


Right. Sometimes language features support an architectural pattern, sometimes they are just mechanism.

And sometimes the same feature is one, sometimes the other. There is no shame in it. Language features are there to be used.


Digraphs and trigraphs were removed (in C++17 IIRC). Non-twos-complement arithmetic and non-IEEE-754 floating point have also been removed.


Trigraphs were removed. Digraphs are still in the language.


This proposal should read "Google's Goals and Priorities for C++", seeing as how almost half of the authors are from that company.

ABI stability? Backwards compatibility? Shipping binary code? The classic Unix compiler&linker model? Those are some of my favorite features of C++ over the competition. Just because GOOG chose to abandon them in their processes doesn't mean the rest of us should be forced to.


The first paragraph of the abstract:

"This paper describes the goals and priorities which the authors believe make C++ an especially effective programming language for our use cases. That said, our experience, use cases, and needs are clearly not those of every user. We aren’t pushing to directly build consensus on these points. Rather, this is presented as a vehicle to advertise our needs from C++ as a high-performance systems language."

I would say they are quite transparent about that...


>> This proposal should read "Google's Goals and Priorities for C++"

> I would say they are quite transparent about that...

I don't even see the word "Google" anywhere?


I don't think the intended audience of this document is hacker news, it's the C++ language committee. So the question is whether the members of the committee would recognize the authors as belonging to Google (which I assume they would).


Except that the very next paragraph clearly shows that this isn't just a statement of their priorities and goals, it is what they want the entire committee to adopt as the priorities and goals for the future evolution of the standard.


"Rather, this is presented as a vehicle to advertise our needs from C++ as a high-performance systems language."

I read it as as "We understand that this isn't what everyone wants the committee to adopt, but this is what we want the committee to adopt."


I would say not, as that’s what every standards body should say. It doesn’t speak to being dominated by one stakeholders point of view.


it is not only Google, Nvidia is also there and sandia (as an HPC user). I.e. those that don't distribute software but build their own stuff.

Well, nvidia does distribute software, but its users are happy to recompile it seems.


As a non-C++ programmer having to deal with C++ from time to time I don't really have a problem with the language (yes, it's huge, but there is no need to use everything) but with the tooling. Especially it's never clear to me how I should deal with dependencies and build systems, it's all different from project to project and often feel like a kludge coming from languages where setting up the environment is not much more than one or two commands. Even dealing with maven files and maven central repository felt easier than most C++ projects I had to work with.

In lieu of C++ we hear a lot about Rust these days, with focus on performance and reliability. But I suspect the rate of adoption is such because of the productivity aspect (it seems to me the language itself has non-trivial concepts and its fair share of syntax cruft from what I saw): having integrated tooling, dependency management, all being a de facto standard for the language. Same for Go, it's just very easy to start a project, add a library, compile a project... everything is included.

I guess it's probably not a priority for experienced C++ programmers as they are probably used to it, but I'm sure more people could build stuff in C++ without those barriers.


The tooling is improving. It's so much better than it was. With CLion I have a modern refactoring IDE of excellent quality (just don't try to use it with large code bases like Chromium, though, ahem). Build systems have improved remarkably. We have two excellent open source compilers.

I actually enjoy working in modern C++ much more than I did in Java, which I did professionally for almost 10 years before this.

I like the idea of Rust, and followed it since the earliest days when Graydon proposed it. I like the syntax and the tooling. But every time I begin a project in it, the cognitive overhead of the memory safety features just throws me a loop. I have no doubt if I were to dig into a large and established code base using it it would click within a few days, but starting on my own... I lose focus quickly.

I do like Zig, though. It is a very nice C++ alternative.


> I actually enjoy working in modern C++ much more than I did in Java

What do you like about working in C++ more than Java?

Generally, I hear from people working in C++ that it's the only tool for their job, and only use it because no other tool measures up (in a despairing tone). If you don't mind my asking, what do you use it for?


I am not the person you're replying to, but in my experience / opinion if you want to do distributed high-performance numerical computing, executing on heterogeneous hardware, C++ is really far and away the best language to get this done. If you're CPU-only, maybe Fortran is a better option but when GPUs enter the mix I don't see how you're going to have an easier time getting the requisite performance with anything else.

I mean, like, you can interface with TensorFlow via Python but the big-kid parts of the implementation are in C++ / CUDA.


http://blog.plover.com/prog/Java.html

I feel great joy coding C++, more than any other language. C++ is built to be used. Every part of it was put in to solve real problems, and they do. I can write thousands of lines of code, and when it finally builds, the program works first time.


More systems level stuff than you'd ever use Java for. Google/Nest hardware devices (display assistant devices, other stuff).


The Rust ecosystem does a lot of what it does by mostly punting on dynamic linking. The community is happy to compile the world and distribute it in a statically-linked binary.

This makes things a lot easier on both developers and end users but does not in theory scale well with increasing adoption (end users have a bunch of huge binaries bundling the same subset of libraries which all get hit with zero-days at the same time but are updated piecemeal across the apps bundling them). The tooling and package management stories are a similar case - they haven't fragmented yet, but as adoption increases you start having to deal with shit like packages from obscure hardware vendors and commercial language tooling - including compilers, providing functionality either absent from or superior to what's available in in the de-facto-standard project templates. Examples from my experience would be things like the Intel C++ toolchain, MPI compiler wrappers on supercomputers, and vendor-provided toolchains targeting alternative devices like CUDA.

Perhaps their community will deal with these challenges in a super-elegant way, I'm not ruling it out. But I'll believe it when I see it.


The thing is, half of what you described is basically a Linux-ism.

It's irrelevant on Mac, Windows, Android, and iOS, where the operating system is a monolith that applications depend on. Technically, it might be made up of multiple libraries, but the OS is almost guaranteed to be present in full, and it's the only thing that can be guaranteed to be present, and it might use integration systems that applications cannot or rarely use (VDSO, NTDLL, syscalls obviously, but also stuff like the System calling convention).

Or you might just use IPC for all components, like Plan 9.

It's also irrelevant if you're using a bare metal machine with no filesystem, a system with a weird linking paradigm like webassembly, or if your app basically owns the entire computer.


I don't think it's controversial to say C++ tooling is rough. I do think a large number of C++ engineers think that they can care about the language so they don't need to care about anything else, including build systems.

I also think a large amount of C++ is tied to a native platform, packaging system (or source repository), and possibly a native build system. That means the tooling is fragmented, though not universally poor. It's just that the tooling is not always available.


Fuchsia makes the list of OSes to prioritize but not *BSDs. This doc is biased toward the interests of Google. Which is Ok. They should just state that upfront rather than saying “our use cases” and leave it to the reader to figure it out.


Isn't BSD quite similar to Linux? I highly doubt it has any of the legacy platform features they specifically call out as problematic:

"Byte sizes other than 8-bits, or non-power-of-two word sizes

Source code encodings other than UTF-8

Big- or mixed-endian (at least for computation; accessing encoded data remains useful)

Non-2’s-complement integer formats (we supported C++20 dropping support for this)

Non-IEEE 754 floating point format as default floating point types

Source code in file systems that don’t support file extensions or nested directories"


macOS is arguably a BSD


Nominally.


Okay, humor me.

What are you going to do if you declare that implementations must be little endian? (It seems that's where that particular goal is headed.)

Will this be well-defined behavior?

   // Writing a 1 to u64 requires that u32 will read 1,
   // because everything is little endian!

   union u {
      uint32_t u32;
      uint64_t u64;
   }
Or, if not, what's the point?

What's the good in it, other than that: whereas a big endian machine can still have a C++ compiler, that compiler just can't be called conforming any more?

Nothing changes other than classification. Nonportable code depending on little endian continues not to work on that machine, though now that code might be blessed as "strictly conforming".

Someone wanting code to work on that machine will make it work the same way as before, and quite possibly keep their code strictly conforming. Like before, it will be possible to express code without endian dependencies, and that will work on those "broken" implementations that support big endian systems.

What happened to the idea that systems require programming languages, not the other way around?


> What's the good in it

Compilers can be made more simple, saving the time of expert compiler authors to work on improving their compilers in other ways. This lines up with some of the other listed goals,

> Syntax should parse with bounded, small look-ahead.

> No semantic or contextual information used when parsing.

i.e. drop features from the language that significantly complicate the compiler.

> What happened to the idea that systems require programming languages, not the other way around?

Hardware got very standardized and software got very expensive.


[The middle two quoted items did not appear in any version of my comment; my comment is very distant from the area of parsing.]

> Compilers can be made more simple

In relation to this issue, I do not see how. Today, someone can make a C++ compiler for a little endian system, or a retargettable one only for a group of little-endian systems, without any additional difficulty coming from the fact that C++ can also be implemented on big endian. Quite literally, that compiler developer need exert zero brain cycles even thinking about big endian.

C++ doesn't require implementors to have anything to do with big-endian.

A language standard that doesn't talk about endianness at all is smaller and simpler than one which specifies it, because that represents extra detail which generates extra requirements that require more sentences.

For C++ to "support" big-endian, all it has to do is not mention it: not give any requirements about it. That's not something that then needs to be dropped at any time.

A compiler project can drop big-endian; that's obviously different. ISO C++ isn't a compiler project, though.


The two middle quotes come from the article. They illustrate that the authors are interested in removing aspects of the language that contribute to tooling complexity.

The compilers that the authors care about are a small number of open source compilers that have very comprehensive support, and consequentially are very complex, probably just gcc and clang. With regard to this perspective, something like “lines of text in the standard” isn’t a useful metric. Few people are going to read the entire standard, so there’s little harm in making it longer, but many people are going to work on large compiler projects, and many, many people are going to use the compilers these projects produce. The standard can be made more complex in ways that reduce the required complexity of comprehensive optimizing compilers.


Numerous instances of wording in the article strongly suggest that the authors seem to be working under the confusion that ISO C++ is a compiler project, rather than a language specification.

> The compilers that the authors care about are a small number of open source compilers

If so, that is an unacceptable position for people on an ISO committee for that programming language.


> Numerous instances of wording in the article strongly suggest that the authors seem to be working under the confusion that ISO C++ is a compiler project, rather than a language specification.

There is no possibility that any of the authors are working under this confusion. The language specification heavily influences compiler projects because compiler authors tend to target standards. The argument that authors can just write their own non-standard compiler doesn't generally carry much water. Moreover it is extremely common for the standard to be written specifically to assist compiler authors. Consider the numerous instances of undefined or implementation defined behavior.

> If so, that is an unacceptable position for people on an ISO committee for that programming language.

The purpose of a standards committee is to standardize, not to standardize anything in particular. If the committee can be persuaded that these use cases are important to a large body of C++ programmers and that the alternative may be a split in the community, the committee may be inclined to adopt some of these topics as areas for improvement.


> not to standardize anything in particular.

Oh no, no. There is something particular to standardize: namely something that is already out there and working.

Secondly, if there are multiple such somethings that are out there and working, but are not compatible, then this is where the standard is really in its own element, to help iron out that situation and improve interoperability.

In this second area, the standard may engage in a bit of invention. Pure, unprompted invention is something ISO standards should eschew.


> Will this be well-defined behavior?

No, it’s illegal in C++. Use std::bit_cast.


You apparently are being downvoted unfairly while you are correct. Writing to u64 then reading from u32 is undefined behavior in C++. Only reading from the last union member that was written to is defined.


Because he was using future tense, not past tense.


I doubt this will be changed in the future, especially since there have been multiple ways to represent this operation in the language now.


Reading non-active union member? That's nasal demons!


> 2. Non-goals

> 2.1. Stable language and library ABI

> 2.2. Backwards or forwards compatibility

> 2.3. Legacy compiled libraries without source code or ability to rebuild

For fucks sake, C has been stable for decades but somehow C++ just can't manage?

This is such an obnoxious attitude. At least let us automatically generate a set of C-style functions that take an opaque void* representing a C++ class instance if you can't be bothered to do the work.


> This paper describes the goals and priorities which the authors believe make C++ an especially effective programming language for our use cases. That said, our experience, use cases, and needs are clearly not those of every user. We aren’t pushing to directly build consensus on these points. Rather, this is presented as a vehicle to advertise our needs from C++ as a high-performance systems language.

The authors are (mostly) at Google. They build their binaries from scratch from a monolithic source repo with a reproducible build system every time they want to deploy software. Their use case is unusual and they recognize that. The document lays out their justification for what they prioritize and why, for example, they would happily sacrifice backwards compatibility or nonstandard architecture support for more performance.


Calm down my friend. Why should C++ be exactly like C? There are reasons that C is a declining language. Also, I don't get why you think they have obnoxious attitude? They do go into detail for each point, I don't think your characterization is fair.


If anything it seems like C++ is the one in decline. C is still the only option for lots of use-cases; C++ is a) succumbing to feature-bloat and b) having different parts of its userbase siphoned off by Go, Rust, Swift, even C# (games; both Unity and Godot).


The only thing keeping C around are FOSS UNIX clones and embedded developers that are religiously stuck with C89 and their PIC compiler extensions.

Even Arduino like boards use C++ nowadays.

Unity and Godot have their graphics engine written in C++.

Rust, Swift are dependent on LLVM, written in C++.

Go, it isn't really in the same league of programmer expressiveness.


The "only thing" (FOSS UNIX clones) is kind of a gigantic thing.

The Windows kernel is mostly written in C, too. iOS uses Objective C.

Modern C++ isn't bad, but robust classes require an enormous effort together with tests suites that approach the size of test suites required for an untyped Python module.

In practice segfaults are as prevalent in C++ as they are in C. Type errors by implicit conversions are more frequent in C++ than they are in C. Full testing of all conversion rules requires a superhuman effort.

So I think well written C is often safer than well written C++, because one does not have to deal with the case explosions introduced by classes and their implicit rules.

The often quoted safer strings in C++ are just a very tiny part of the equation.

If I had my way, we'd all be writing Ada or Standard ML, but no one listens.


While they are still gigantic, I bet long term they will stop being so, thanks to the cloud, type 1 hypervisors, unikernels and serverless computing.

30 years ago no one though commercial UNIXes would be wiped out by a "toy" UNIX clone project.

Windows kernel has been moving into C compiled as C++ since Vista introduced support for kernel mode C++.

iOS uses C++ for its drivers, Metal shaders and compiler toolchain. That Objective-C implementation relies on C++.

Yes, the Trojan horse of C's copy-paste compatibility is also its Achilles heel of security, however in contrast with C's attitude regarding security, C++ provides the library types to actually implement secure code, if one bothers to make use of them.

If I had my way, C would have bounds checking enabled by default, strong typed enums, proper arrays and strings, but no one at WG14 listens.


There are lots of performance sensitive tools, language runtimes, and cross-platform libraries written in C.


And even more written in C++. Should we start counting which ones?


Hmm, I’m not sure I’d readily agree.


Starting by Clang and GCC, I bet that for most relevant C projects there is somehow an equivalent written in C++, or even more than one, with the exception of UNIX kernel clones.


Let's start with CPython. What is an equivalent written in C++? (Note: there is an equivalent written in both Java and C#. But not C++.)


I guess Numba might quality.

Then we have Pyston, and the ill fated unladen-swallow.


I have spend the quarantine hacking in C. On FreeCIV.

Still used lots and lots of places


Same can be said by any programming language, C is the Cobol of systems programming languages, so as long as there UNIX FOSS clones it won't go away.


Freeciv fan here. What are you working on in it?


Forked Attitude AI and making a rules based AI


C++ has been greatly reinvigorated this past decade with language and compiler modernization. Few C++ programmers decamped to Go; most of Go’s adoptees came from Python. Go doesn’t have all that much to offer a C++ programmer.


> Go doesn’t have all that much to offer a C++ programmer.

Concurrency and parallelism without debugging hell is huge.


> Concurrency and parallelism without debugging hell is huge.

There is plenty of libraries in C++ that haves you concurrency and parallelism without debugging hell.

Hell in concurrent code comes when you have shared state, and that is valid for both Go and C++. Using shared Golang Map in multi-threaded code is a very good example of deathtrap, even if it's in Go.


C++ will be getting a concurrency model, likely in C++23. As with other language features the position papers and implementations refer not only with their authors’ experience with C++ efforts (libuv, asio, pthreads) but other languages as well, such as go.


C++20 offers both with very nice graphical debuggers, which Delve isn't really a match for.


What graphical debuggers for C++ do you recommend?


Visual C++ ones.


That's not really an option for me when I debug C++ that is run on a Linux or Android TV. It also wouldn't be an option when I used to work with C++ that was used on Linux servers. That's a big chunk of C++ use.

While I have your attention, I see your comments here in C++ threads quite often. You seem to like the language and at the same time you have some experience with simpler languages like Oberon. My experience with C++ has been that people happily go on to create giant inheritance hierarchies, with no restraint for multiple inheritance to the point that it's so unmanagable that nobody knows where things go and what actually happens. With all that inheritance, they lose track that the same thing has actually been copied tens of times between different classes and now these tens of classes have code that is basically copy-pasted and who knows what benefit anyone has from all this inheritance. Google engineers are one of the offenders when it comes to overuse of inheritance. Then, modern C++ allegedly fixes some warts, but IME it introduces more of them. E.g. the initialization fiasco (I honestly can't say with 100% confidence what braces do when you initialize std::vector). Then there is this thing that when you create a lambda like so:

  [&] { ... }
And then move it to a function which will create lambdas of this form, just with different captures:

  std::function<void()> create_lambda(args...) {
      return [&] { ... };
  }
The second version will crash later when you run the lambda. Because it turns out that "[&]" just copies the stack pointer and isn't syntactic sugar for enumerating all the variables that you capture. This was non-obvious to me and it was really hard to track down the first time I encountered this problem. I think "[&]" is broken. Similarly, std::thread crashing in destructor when the user doesn't call std::thread::join, nor std::thread::detach. The ambiguity of syntax... I just don't buy that "modern C++" makes things particularly better. It sometimes makes them worse.

Don't you experience the same fatigue as I do?

EDIT: Oh, I just remembered another thing: at a previous job we discovered that MinGW's implementation of std::random_device just returned 0 every time. It is major facepalm for MinGW, but also for the C++ standard for making this behaviour allowed (yes, it's actually compliant with the standard). And even after it was fixed, there are still systematic issues with this whole randomness API. E.g. you can't actually seed it properly[1].

[1]: https://codingnest.com/generating-random-numbers-using-c-sta...


Well, Android Studio also has Clion debuggers integrated, which although not at the same level of visualization capabilities, they are quite further than bare bones alternatives.

Fun fact, due to continuous ranting regarding C++ support from game developers, not only did Google announce at GDC 2020 that they are upping their game regarding C++ tooling on Studio, they will also provide Android plugins for Visual Studio.

When people discuss Oberon, they tend to focus on Oberon (1992) or the minimalist approach that Niklaus Wirth has pursued later with Oberon-07.

When I talk about Oberon, I think beyond my early experiences with Native Oberon, and also include Oberon-2, Component Pascal, Zonnon and Active Oberon into the mix. The Oberon language linage, not all of them done directly by Wirth.

In that sense, for me Oberon is what EHTZ nowadays uses as Oberon, namely Active Oberon.

http://cas.inf.ethz.ch/projects/a2/repository/raw/trunk/Lang...

You will find out that minimalist Oberon is long gone and the language in a certain sense compares to C#, D in complexity, with support for generics, untraced references, exceptions, linkage models, inline Assembly.

Because that is the problem with minimalist languages, library boilerplate just grows to emulate what should have been in the language to start with. In Oberon's case, Active Oberon is the result of many years of research and student thesis doing OS implementations in Oberon, while noticing that stuff like generics, untraced references, inline Assembly are quite useful.

As for C++, while I still enjoy using it, nowadays it is mostly a tool for OS integration libraries and GPU shaders (HLSL/Metal/CUDA), I rather write the main code in .NET languages, Java or some other managed language.


> You will find out that minimalist Oberon is long gone and the language in a certain sense compares to C#, D in complexity, with support for generics, untraced references, exceptions, linkage models, inline Assembly.

Generics is something I can't live without, to be honest. But I don't like C++'s implementation of generics. The error messages they generate and the whole SFINAE business make for a horrible programmer experience. Rust has the best implementation that I know of currently. Inline assembly sure is useful.

Anyway, thanks for the extensive answer. I think I understand your point of view better now.


> I think "[&]" is broken.

I feel like this is consistent with the "pay for what you use" design. Why capture all variables in scope by copies when you can just capture them all as references (which might go out of scope)? If you truly need to capture other stuff, you can do so using more syntax. If "[&]" did what you describe then "wtf, why is it saying I have a deleted copy constructor for some value I'm not using" or "wtf, why is my code so slow because of unnecessary copies of large objects" would be all sorts of fun.


"&" captures references, not copies. I would like it to be just syntactic sugar for "&x, &y, ..." and only include the variables that I actually used in the body of lambda. There is no performance cost to that (unless a crash is somehow a performance win --- hey, the program finished earlier, I guess it's faster...).


But the complaint was about lifetimes. You seem to want copies (if I understood your post correctly).


> it turns out that "[&]" just copies the stack pointer and isn't syntactic sugar for enumerating all the variables that you capture

it doesn't copy the stack pointer, but it enumerates all variables you use and captures a reference to it (I think the &, as used in address-of and references kind of gives it away). [=] {} unsurprisingly captures by value.

Some compilers do warn when you return lambdas capturing local vars by reference, but it is not super reliable.


At one job I wrote the equivalent of the following (it's just that I invoked create_lambda more times):

  #include <functional>
  #include <iostream>
  
  std::function<void()> create_lambda(const int& x, const int& y) {
      return [&] {
          std::cout << "x: " << x << "\n";
          std::cout << "y: " << y << "\n";
      };
  }
  
  int main() {
      int x = 5;
      int y = 7;
  
      const auto f = create_lambda(x, y);
  
      f();
  }
And it crashed. There were no local variables in create_lambda and the lambda crashed in the same function that create_lambda was called in. When I try to reproduce it now, it doesn't work. Not sure what's different, but this is basically what happened.


> std::thread crashing in destructor when the user doesn't call std::thread::join, nor std::thread::detach.

this has been 'fixed' with jthread. FWIW I think it is a mistake, blocking in a destructor is can easily lead to deadlocks (I have first hand experience, it is not just a theoretical issue). Detaching is not an option either.


Swift? I didn't know it had much use outside of mac/iOS. It seems like a cool language otherwise.


It just recently got official support for Windows and Linux, and it had low-key Linux support for a while before that. I've heard of people writing web servers in it. Like Go, it compiles directly to native machine code.


Isn't it still slower than GO because of referencing counting?


Depends what you mean by slow. Go is garbage-collected, Swift is reference-counted. Each has advantages and disadvantages. But also, that doesn't apply to stack-allocated values. It's not as simple as one being faster than the other.


> There are reasons that C is a declining language.

Unfortunately, it is not. C++ is certainly too complicated, that's why it failed at replacing C, and that's why Rust will fail at replacing C as well.


It only failed on UNIX FOSS clones and embedded C only boards.

All major C compilers are written in C++ nowadays.

Apple OSes use C++ for device drivers, GPGPU programming and their compiler infrastructure.

Google OSes use legacy C drivers for Linux and everything else is a mix of C++, Java, Rust, using micro-kernel like architecture where each driver gets their own process.

On Windows it has been long considered persona non-grata and everyone advised to either move to C++ or adopt Clang (also provided via VS installer) if they insist in using C. VC++ only supports C to the extent required by ISO C+++ compliance.

One of the reasons OpenCL lost to CUDA was the initial refusal of Khronos to offer similar C++ tooling, making many researches jump to NVidia.


> On Windows it has been long considered persona non-grata

You say this as if it is the fault of C. I see it the other way around -- Microsoft's refusal to implement C11 is singlehandedly contributing to the decline of C, because they've leveraged their large OS market share to make it impossible to use an update of the language for cross-platform development. They are killing it on purpose, and it's infuriating.


It is, because the C culture is unwilling to accept any language change that improves the language security.

Anyone that wants a C like language with better security has decided that the only path forward is to either ditch it, or impose hardware memory tagging (which Google is imposing on as of Android 11).

Microsoft still supports C in its Checked C variant and just like Google, Oracle and Apple on their platforms, its hardware hardened Azure Sphere Pluton runtime.

C was already on decline in the late 90's, all major desktop and mobile OSes were being written in C++, BeOS, Symbian, Windows with OWL/VCL/MFC, OS/2 with CSet++, Mac OS with MPW/PowerPlant, game developers were moving into C++ with Watcom C++ and PSOne devkit, it was the rise of FOSS UNIX clones with the FSF manifesto to use C as much as possible that changed the wind, thankfully that course has been finally overturned.

The decline of C in an always connected computing world is done by WG14's lack of action.


> Apple OSes use C++ for device drivers

Embedded C++, and the kernel is pure C.


Embedded C++ is a C++ subset, it is as valid C++ example as those embedded C dialects that aren't able to fully support the ISO C standard library, yet people still call them C.

And as of Driver Kit introduction, 100% full C++ is now an option.

In a couple of years from now, when the full transition of kernel extensions to user space modules is complete, the kernel will be the only C piece left.

Which Apple most likely won't bother to rewrite due to the historical baggage of mixing BSD with Mach code that probably no one is willing to re-write.


Fortunately for Rust it's very easy to use a simple subset of the language. Lots of tough borrowing issues don't show up if you stick to the stack, and you can get very far with just the stack and some heap allocated vectors and maps. Not everybody needs to implement an async runtime.


From someone who has been interested in Rust for a long time but is only now really getting into it due to time constraints, why do you consider Rust too complicated to replace C?

From what cursory knowledge I have, Rust maintains both a bare bones standard library suitable for embedded work as well as a more fully featured standard library that is built on top of that minimal library.

What features would keep Rust from becoming a new C other than possibly some issues with ABI stability that from my understanding people are working to resolve?


It makes C++ much less useful to write a library in (or, you have to maintain a fragile C layer). I take it you've never had to deal with a libstdc++ ABI boundary before?


You can continue working in a world without the small string optimization if you want. There is already precedent.


I want a world where the compiler knows when it can use that optimization instead of me bludgeoning it with a global define.

Like you said, it's not even that far fetched.


It’s a library feature not as compiler feature so you can’t really get out of telling the compiler which library to use, Eben if what you say is nothing (meaning “default library”)


Maintaining a C layer is standard practice for many libraries because that enables their use from a variety of programming languages. Consequently, the tooling around that is quite mature.


Sure, but if C++ had a stable ABI, then it would be more practical to directly wrap it. SWIG has some support for C++, after all, and there are a lot of C++ wrappers (e.g. ROOT, pybind11 and Boost.Python can all automagically generate python bindings for C++), but guess what happens when you mix libraries compiled with different std::basic_string ABIs...


It has two on Windows, they are called COM and UWP, although technically it is just one as UWP is an evolution from COM.

It has has one on IBM i and z/OS, as part of the language environments.


C++ has the stable Itanium ABI too, but that doesn't help when needing to bind with code that uses templates or preprocessor macros since those features are inherently language dependent. That's why re-wrapping the code with a C-compatible ABI is often a better approach. COM can also be accessed from C, by the way.


COM can be used by C if you are a masochist and feel like doing COM "Assembly" programming without some of the compiler hand holding, it got even worse with UWP that uses .NET metadata files, with support for a .NET like type system.

I guess when one is bored might try to re-implement ATL, WTL or UWP projections with C macros.


These things come with a performance cost and constraints on how the language can evolve.

I would argue that C++ needs to increase the pace of change if it wants to remain relevant. Rust, Go, Swift and Kotlin are though competition, while C++20 is still lacking in agility, safety and modularity.


What's more modular and agile than being able to link another library you didn't compile yourself without worrying about your language version or compiler?

C++ build systems are already worse than all the languages you mentioned in part because there's no module support.

I also disagree that it would hurt performance. If the logic contained in "-D_GLIBCXX_USE_CXX11_ABI=0" was formalized to allow backwards compatibility, libraries would snap together more easily while still preserving inlining better than C ABI exports are able to.


> What's more modular and agile than being able to link another library you didn't compile yourself without worrying about your language version or compiler?

The point of the document is to lay out the goals and priorities of the authors, not to argue that they are universally applicable. The authors compile everything from scratch with the same version of the compiler, down to the OS kernel.


> Rust, Go, Swift and Kotlin are though[sic] competition, while C++20 is still lacking in agility, safety and modularity.

What is the point of fighting to stay relevant by going after use cases that are being served by these other languages? Is it just to win and have more "mind share"? Is it to make the biggest tribe? That seems like a social goal, not a technical one.


It defends their investment, both in the skills they have built up and the existing code that they have created.


The ABI breaks when you change string implementation to use an optimization for small strings. Therefore you can't optimize them without breaking ABI.

C has no strings.

The optimization was considered vital enough for a rare break to actually happen but there are multiple such clear wins that cannot be implemented because of ABI stability. Sometimes it's a major pain to have to keep piling the cruft on.


These aren't the standard's committee's goals. They're Google's, NVIDIA's, and the other proposal author's goals.

As a community, we absolutely should reject this proposal and insist that the standards committee continue to support compatibility. Repeating Python3's mistake in C++ would be a nightmare!


> For fucks sake, C has been stable for decades but somehow C++ just can't manage?

the -std=c++03 mode of the compiler is pretty stable... but not many people care about that anymore. If they cared google would be building their next kernel in C, not in C++


you can compile with -std=c++20 and still use the C++03 ABI (of course std::string will not be compliant then). The two flags are unrelated.


Most of this I agree with. Frankly, though, Stroudtrup’s design goals are better: multi-paradigm programming, you don’t pay for what you don’t use, etc.

Some points: * There are still tons of 32 bit machines out there—old Windows machines chugging along, usually disconnected (thankfully), and you’d like to be able to use your _current_ codebase to target them.

* If C++ is to focus on performance, it needs much better tooling around UB, be a bit more permissive of old behavior that now triggers UB, with formal semantics, AND it needs to define semi-portable SIMD vector operations. Getting the utmost performance out of modern CPUs entails using vector operations.

* It also makes me sad to say goodbye to big endian.


Though I cut my teeth on BE machines, I know that’s in the past (as are their non-power-of-two word lengths). But 64-bit machines is an unnecessary jump. Even 32-bit microcontrollers are expensive for many applications.


I doubt that C++ dropping 32 bits is ever going to happen.


> it needs much better fooling around UB

Typo or sarcasm?


Ha! Freudian Typo. Thanks!


Why is making C++ (even more) multi-paradigm a good thing? Why not making a new paradigm specific new one instead? (as there are, so just use those)


Because there is no one true paradigm to rule them all. I want to pick the Right one for my current problem and have it work with my other code with a different problem and thus a different paradigm.


C++ needs to just steal the idea of Epochs from Rust https://vittorioromeo.info/index/blog/fixing_cpp_with_epochs...

By default, everything is backward compatible, but to use new features you need to declare this compilation unit as being part of C++23 (or 26, 29). Then code that uses the new stuff also ignores the old and can have different rules. But it can still be combined with legacy code and libraries. You know at compile and linking time when crossing boundaries and can do the right thing. This actually combines nicely with modules since we already need a new build infrastructure to take advantage of modules.


Nothing is stopping you from passing different -std=c++XY arguments to different compilation units in your codebase. I don't know about other compilers but object files compiled with msvc, gcc and clang (the ones I happen to work with) using different -std values (starting with c++11) are compatible* and the respective teams working on these compilers reportedly make conscious efforts to make sure this remains the case.

CMake for instance makes this very easy with its set_properties() call.

* Of course there are caveats and corner cases. See eg. https://stackoverflow.com/questions/46746878/is-it-safe-to-l... But these seem like they could easily be avoided in real-world use-cases.


Yes I can add new features to some files or I can compile with c++17 and link to an old C library. What I can't do is remove features or change the default calling conventions/ABI and still link with old code. If you declare in the interface and in the code that this is new code with new rules then this is possible. When you cross library boundaries the compiler adjusts. That is what rust allows and what C++ could do.


Interesting post. I'm bummed that there isn't more interest in bringing C++ to ASIC/Accelerator scene from the committee. I think projects like Nvidia's Thrust[1] show that C++ is poised to be fantastic medium for software developers to break into experimenting with FPGAs, GPUs, and potential future commercially available equivalents to the TPU. There is some really cool cross-platform software infrastructure that is still under active development.

1 = https://thrust.github.io/ 2 = https://mlir.llvm.org/


> Interesting post. I'm bummed that there isn't more interest in bringing C++ to ASIC/Accelerator scene from the committee.

isn't that purely an implementation issue ? e.g. both Apple and NVidia have brought C++ to the GPU, one with Cuda and the other with Metal... that did not require any help from the committee.


No and yes. Is it strictly the language’s responsibility to deal with the implementation? Not really. I agree in theory.

In practice, Google is behind this statement. Nvidia is behind this statement. Two of the largest supercomputing facilities in the US are backing this. The document heavily implies that these are the goals for C++ based on their use cases. These are also organizations that have had a massive role getting the accelerator space to where it is today.

Sure, MLIR is technically a project underneath the LLVM foundation, but isn’t it mostly Google employees who were working on it. Lattner has moved on from there and is now setting his sights on RISC-V it seems.

As someone who likes modern C+ + and is interested in new, open hardware, I’m really happy RISC-V is being made a priority, along with bare metal compilation. But I’m also confused and here’s why.

I agree entirely that CUDA support for modern C++ is moving along nicely, especially since CUDA 11 supports C++17. However, a good chunk of these authors are Nvidia employees and now they’re implying their “use case” for C++ isn’t associated with accelerators?


It's amusing to see the C++ committee finally taking safety seriously. I tried to get some interest in safety from there many years ago, but they were off into template la-la land back then.


Not to be negative but this reminds me of Python 3, where they want to make a largely but not completely compatible new C++. Like Python 3, it would be great to get a bunch of things fixed and force people to use the new, better, way of doing things. But, this comes with complications.

I am a C++ and Python developer. I did C++ for 20 years then python for 9, then C++14/17 for 3. I really like the new C++ and think it could be made into a better language while still retaining the deterministic performance.

What we don't want is Python 2 to Python 3 situation. That might mean calling it a new language.


Honestly, I think python 3 broke too little. I mean, if you're going to break backwards compatibility, make it worth it. That should've been the time to lock down the language enough that PyPy and other non-cpython interpreters could finally thrive.


I agree. They changed 'print', which broke basically every non-trivial program ever written, but then didn't clean up so many other smaller issues.


Could not agree with you more.


> We of course continue to care that C++ supports all of the major, modern platforms, the hardware architectures they run on, and the environments in which their software runs. However, this only forms a fairly small and focused subset of the platforms that have historically been supported and motivated specific aspects of the C++ design. We would suggest this be narrowed as much as possible.

:(


Well... take abandoning non-8-bit byte sizes. Are there any such architectures where someone was planning on shipping a C++23 compiler, but this would make it difficult or impossible? My impression is that for such platforms, you'd be lucky to get C++11.

Non-little-endian might be a bigger issue - it would rule out quite a few embedded CPUs.


If your code depends on endianness you're either over-optimizing or doing something wrong. At least that's what I've learned over may years of reading the lessons of many people.


If you're going to deploy your protocol buffer parsing library to literally a million servers you may be prepared to optimize the last shred of performance out of it. The authors have an unusual use case.


Replying very late, because I was on vacation.

In embedded, you're often talking directly to hardware chips. How you talk to those hardware chips absolutely depends on the endian-ness of the CPU... unless the chips all have 8-bit-only interfaces.


If you're "over-optimizing" in a way that makes your code depend on platform endianness, you're definitely doing things wrong. Even then, it's quite possible to make compilation error out if targeting the wrong endianness. This isn't something that the C++ standard should be concerned with.


It's clear these authors don't care much about the embedded community, which is too bad, since C++ is basically one of the two choices you have (the other being C). I prefer C on embedded (just because it's easy to accidentally allocate memory in C++), but there are large embedded ecosystems in C++ (Arduino, mbed).


what made you think they don't care about embedded space, just curious. Yes I use c++ for embedded though much less comparing to C.


From the article:

A specific open question is whether supporting 32-bit hardware and environments (including for example x32) is an important goal. While some communities, especially in the embedded space, currently rely on this, it comes at a significant cost and without significant advantage to other communities. We would like to attempt to address the needs of these communities within a 64-bit model to simplify things long-term, but this may prove impossible.

This is not just microcontrollers (hardly niche, but obviously different performance envelope), but also plenty of 32-bit Linux single-board computers (e.g. BeagleBoneBlack). Not to mention the earlier mention of endianess other than little.


this is crucial, thanks! I'm to forgo c++ efforts due to this decision/intention.

on the other hand, rust does not support 32bit arm at tier1 either. https://forge.rust-lang.org/release/platform-support.html

golang so far still supports 32bit, but who knows for how long, after all it's Google who can do anything they want, plus golang is too fat for many embedded boards.

Thank goodness we will have C stick around for many decades in the long run, along with ash probably lua5.1 for scripting that is.


also I hope it's for the future new c++ standard, i.e. for c++17, c++20 we will have full 32bit support, that should suffice for one decade I assume.

otherwise we need get rid of c++ for 32bit embedded fast.


We believe that many divisive issues in the committee come from a disagreement on either stated or assumed priorities.

I’m curious: Which divisive issues would have had obvious resolutions if there had been broad consensus on the C++ committee to adhere to these goals & priorities?


Perhaps standardizing the byte as 8 bits, for one example.

Edit: Actually, they specifically call out little-endianness as a priority.


Surely the standard wouldn’t drop support for big-endian? And I don’t see what you’d gain in exchange. Defined semantics for casting int* to short*? Woo?

These are smart, system-software-focused engineers, so there must be some interesting directions being blocked off by insistence that the standard be byte-order agnostic (and word-size agnostic, since they’re so keen on 64-bit)...


As with most of the things the authors describe, the advantage would be a simpler compiler and a simpler language for static analysis. There are a lot of things in the standard that the authors just don’t have any use for that they would prefer to see removed in the interest of faster compiles, easier compiler development, and more performance optimizations. Removing support for big-endian might actually significantly speed up compilation. I’m sure it adds a ton of branching to hot code paths to check some architecture endianness enum every time the compiler wants to reason about things like bit shifts and known-bits.


Could you not just produce basically two compilers, one for big and one small endian? I think that could be done with templates and such. Sure, bigger code size, but that does not seem something that would pain people from google (as I can tell looking at golang).


Possibly, I'm no professional compiler author. But I imagine doing so would require templatizing a lot of logic, and would significantly slow down the compilation of the compiler itself, which is an important metric for development velocity of compiler improvements. (The linux kernel is famous for taking about the same amount of time to compile through its history. If compile times get long enough people start paying attention to optimizing compile time instead of performance.)

It was eye-opening when I read this blog post and learned that LLVM spends about 0.4% of all compilation time reading the bytes of the string "null-pointer-is-valid" over and over. Hot code paths can be pretty hot.

https://nikic.github.io/2020/05/10/Make-LLVM-fast-again.html


Interesting that they didn't list any of the BSDs in their list of OSes they think should still be supported.


From my perspective, people who wrote this have a lot of very good ideas and principles about software engineering.

But the impression it gives me is that C++ is not fulfilling the expressed requirements, and it is not currently moving in the right direction.

I read it as a quite strong critic of the current state of the language...


To me it looks like the paper describes exactly where C++ has been going since its inception to this day.


I'd be very interested in any example of C++ having "Code that is simple and easy to read, understand, and write" as a goal.


You mean can't do overload resolution, run template specialization and selection, implicit conversions, and move constructor semantics all in your head at once?


Not sure what's the difference between read and understand here. And I would argue the language doesn't even have the biggest effect on whether code is easy to understand or not, some people have an incredible ability to structure code in an easy/impossible to understand way. Also something close to every single language in the universe is going to argue that's one of its goals.

But you don't need to go too far to find something you can argue has such a goal:

- RAII: it's easier to read and write a function when it's not full of resource deallocation code.

- Exceptions: It's simpler to understand code when you move the error handling away.

- algorithms: simpler to read and write than your for loop.

- ...

It's such a vague goal that you can argue it about anything from any language.



https://gist.github.com/fwsGonzo/6b12f502a3873725c17f44dc5e2...

Very preliminary benchmark C++ Vs Rust on RISCV. C++ is just in its own league. Alone.

Having been at CppCon I can attest to the atmosphere of performance first in C++.


What is the context here? This doesn’t describe the test at all, it’s just a bunch of numbers.


The explanation is long but I have written about it here:

https://medium.com/@fwsgonzo/adventures-in-game-engine-progr...

The benchmarks are done here: https://github.com/fwsGonzo/script_bench

I am trying to reach out to someone who is really good at Rust to see if there's ways to balance the scales.

One of the things I am dealing with: https://gist.github.com/fwsGonzo/ff0b7f41c521eb0cc4212f3c42f...


Seems like you're getting the bounds check, which makes sense, there's no real way to eliminate that with this simple example.

> I am trying to reach out to someone who is really good at Rust to see if there's ways to balance the scales.

If you use reddit, posting to /r/rust will be super helpful. If you don't, users.rust-lang.org is a decent spot. If you tweet, I can retweet from the rust account.


The first paragraph for the ABI non-goal section attacks a bunch of straw men about why a stable ABI is bad, then the second paragraph says they would actually be ok with doing what people actually want when they say they want a stable ABI.


My read this is a matter of not wanting to be forced into maintaining a stable ABI vs admitting that a stable ABI is good and useful and encouraging other people to handle it.

Less charitably, it is saying, yes stable ABI is good but it is not our problem.


Mb this is time to design a new language with goals described in this paper. Google has enough resources and experience to do this in the near future.


I feel like you could make a near-C++ that could really get traction. Literally compile to C++ (and then compile from there) and just fix a ton of cruft in the language including:

- Stealing C# attribute notation instead of having the ridiculous __stdcall sort of convention

- Making a real fucking keyword for pure virtual functions instead of = 0

- A real keyword for include guards

- Function pointer syntax sugar

I now realize I'm describing D but D went too far. I just want like three nice changes that still allow near unchanged compilation to C++.


As others have said, it's not worth breaking away from the standard language only to make a few shallow changes. It needs to be a fairly radical improvement or it's just not worth it. If Kotlin were too similar to Java, it couldn't have offered much reason to adopt it.

If you want a modern language that compiles down to C (not C++), there's Vala. It even has some kind of async support now.


>- Stealing C# attribute notation instead of having the ridiculous __stdcall sort of convention

C++17 introduced the attributes in the form of [[attribute]], e.g. [[maybe_unused]], [[fallthrough]]. Clang also supports e.g. [[gnu::packed]] instead of __attribute__((packed)).

>- A real keyword for include guards

"#pragma once" is de facto supported everywhere. The general stance seems to have been to not bother with standardizing, as modules were a better solution anyway.

>- Function pointer syntax sugar

Aliasing function pointer seems mostly reasonable?

    using my_function_pointer = double(*)(int a, int b);


I think you overestimate how much traction such small modifications would get.




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

Search: