Then you'd just repeat the Perl 5/Raku split. You'd have a "new" C++ that isn't actually C++ but it's called C++ with a different version number and people will then bicker for a few years until they realize that the "new" C++ isn't actually C++ and that the "old" C++ just vehemently refuses to die, so they name it something else.
The C++ standards committee can standardize a backwards-incompatible "new" C++ for all I care as long as they don't call it C++.
> The C++ standards committee can standardize a backwards-incompatible "new" C++ for all I care as long as they don't call it C++.
This x2. It's only C++ if you can optionally refactor random lines of code in a project written in compliance with C++98 using new features and still get a valid program.
Once you mess with the language in a way that your C++98 code is either not supported or requires rewrites or redesigns to comply with the new standard then quite obviously it is not C++ anymore.
Under semver, they could call it "C++ 2.0" and be fine since according to semver anything with a major version change is free to break backwards compatibility.
Oracle calls the 6 month releases a 'major' version.. .but many corporate shops only care about the LTS versions (mostly 8 or 11) which are getting 90 day 'minor' patches. Java 11.0.6 is current till mid April, in which case 11.0.7 should hit.
Our shops won't cut over to something beyond Java 11 until Java 17 (LTS version) hits in a few years. Just happy that we were able to make the jump from 8 to 11.
It is still a major improvement being able to be on Java 11.
Poor Android Java developers are now faced with being stuck with a pseudo Android Java subset of Java 8, or migrate to Kotlin.
While any Java library author that wants to share their work with the Android community is faced with the dilemma of keeping two parallel versions of their library, forget about Android, or also convert themselves to Kotlin as well, even if that isn't their thing.
J++ already exists. And so is -90 degree rotation (U++). The only variation that left is ∩++. But I think that 🅱️++ is better. It is modern looking and with hint that language took step backward from original in order to be more simplistic.
So there is a path, but it has to take an upgrade-path into account for big legacy projects.
In practice, moving to a new compiler can be a big project for companies with larger code bases. Sensible deprecation of features could (and in practice) is part of that.
Even though the standard is quite careful, in practice big C++ projects sometimes rely on non-conforming behavior of the specific compiler version they use. An example is the MSVC template support which allowed constructs that the standard didn't.
The committee does a lot of work to keep the standard compatible with c++ as it is used and this proposal seems to be a good example of them trimming out dead and bloated parts, without negatively impacting existing code.
The original proposal for that deprecation points out a lot of issues. Basically it boils down to the fact that many operations were badly specified, outright misleading, predate the c++ memory model or were artifacts of maintaining the parallel to const that volatile had in C at a large cost and without people actually using it, one example they could track down even noted how it did not behave the same way as primitives would.
Your example is a really poor one. The volatile keyword was only used to provide hints to the compiler on whether or not it could apply some optimizations. Omiting a volatile keyword is perfectly backward compatible, as is compiling code without support for volstile. Thus the only practical consequence of adding/removing random volatile keywords from your source code was how your build could (and not should) be optimized.
I'm of the general opinion that what people think volatile does vs. what the compiler really does is quite different. Perhaps the situation has improved since this paper appeared.
Formally ignoring or forbidding volatile where it presently doesn't mean anything (despite fervent wishing) wouldn't break anything. Compilers would of course continue supporting whatever they do, but would be allowed to warn about dodgy uses.
On MSVC, volatile has traditionally meant something akin to atomic. It still will, regardless of what the Standard says and other compilers do.
You can't call non-volatile methods of volatile objects (just as you can't call non-const methods of const objects), and you can overload methods on volatility like constness. It may not have the "don't cache this in a register" semantics it did in the single-core C era, but it can't just be discarded.
Code that compiles today would still compile if restrictions were lifted, modulo overload resolution changes that might introduce new ambiguities or name collisions.
"Can't" is a big word when applied to the ISO committee. They would want to consider the magnitude of consequences. Since all implementations would provide a "make like before" switch, consequences would be limited.
They can choose to make breaking changes, but they can't eliminate "volatile" without breaking anything. I've definitely seen code that relies on it, albeit for strange reasons.
No, at best you saw code that expects the compiler to not apply specific optimizations that C++ doesn't suggest or enforce. It's a compiler issue, thus if anything that code needs to be compiled accordingly.
No, there are other reasons to use volatile declarations that have nothing to do with the original purpose, and that are completely unaffected by any potential effect on optimization. At the level I have seen, it is used as a simple tag to generate a type related to T that is neither T nor const T.
I use volatile on memory mapped input ports. It tells the compiler that if I am reading twice from this address without writing to it in between, don't optimize out the subsequent reads because this is not a memory location whose contents are under the control of the program. If the compiler ignores this, the software most certainly will break and break hard. For some applications the results will be crashed vehicles, runaway industrial processes, and dead people. Compilers are not free to ignore volatile and it is not a suggestion to the compiler, it is a directive which must be obeyed. Compilers that don't do this are broken compilers.
Thanks, not the first two. I'm not sure what volatile pass by value means and did not know that that was ever a thing. The third one, yeah, I use that, and you have it marked as non deprecated, so that's good.
I think this idea of "no broke compatibility"/"no rewrite" is never properly considering the presence of COST * TIME.
In short time, is bad.
The more time pass, is good.
The cost of STUPID C/C++/JS behavior is measured in the billons.
Is not the makers of this langs aware? Yes. They know how could be fixed? Sure. Then WHY is not fixed?
Because not considering time in the calculation (and how many MILLONS OF PEOPLE are affect). But today, are DECADES behind of 100% certainty of the cost of this mistakes, and proved beyond doubts that the langs that fixed them ARE better.
In the face of reality and facts, why resist so much?
---
The thing is HOW get out of this mess?
The big irony is that the JS world show how (but not commit with the proper force): Build transpilers. Make "Better C++" that transpile to "BAD C++". Make clear that everyone must move forward, but provide this as partial step.
Make auto converters. Fix damm stupidity like dangling IFs, (seriously some stuff is not brainer). Have a clear vision in how move forward.
And drop the ego. C/c++ not need to turn into Rust, but why believe it could not truly get better?
Now, in the case of C/C++ exist a big trouble in the case of being the "de facto" ABIs for the rest of the world. Apple have the same issue with swift -> ob-c with the case of nullability (which apis never return null even if in theory could?).
Them do annotations to the APIs. This could allow to mechanize the transformations and provide ABI translations that could be injected in the compiler.
When people design a language which they know is going to be incompatible, they tend to go all the way with it. If we're breaking it anyway then might as well do a better standard library interface than the stuff designed in the 90s and all that sort of thing, right? And that's good to have for new projects.
But there is a benefit in having something which makes the smallest possible compatibility-breaking change to fix the defect in the original. So you have a "new language" with a borrow checker, but the standard library functions all have the same names and the same purpose and time complexity as the C and C++ ones, which would allow people to run the existing code through a transpiler which for 80% of the lines can be translated directly to working code in the new language, and produces diagnostic errors for the other 20% that explain what needs to be changed.
Which removes 80% of the work from transitioning an existing codebase to the new language. And then more people do that.
Rust doesn't have function/operator overloading yet or it won't have it at all? I am vastly for clarity and simplicity over vague, implicit stuff but doing e.g vector operations can get very unreadable very quickly without it.
Or you would have the Ethernet situation: a new better tech that is called Ethernet while having mostly nothing to do with the old one, and people migrate to it, and the old tech is eventually not used anymore.
Hardware has the difference that it eventually stops working and/or becomes obsolete, forcing people to migrate to newer models every so often. Code is immortal.
Nevertheless, in Ethernet's case, while it changed backwards incompatibly at the beginning, since then, consumer Ethernet has been backwards compatible all the way from 1990's 10BASE-T (10Mbit) to the still-rare 10GBASE-T (10Gbit). In data centers you have faster variants that require different cables, for routers... but even there I believe the connections to individual servers typically use the old twisted pair.
To follow in the footsteps of the Ethernet situation the new c++ would have to be better enough to make people switch, which I think is unlikely given the number of programs written in the current c++
This works with supersets and I kind of love it. Like switching to Typescript. My code is just valid immediately. There's no migration downtime. And I chip away and one day it's all done and I disable "allow implicit any".
Maybe that's what Python 3 should have done: don't remove stuff, but just make it discouraged and let a python3only" flag treat them as errors when ready.
What we got was kind of a mess. Because python2 is very often valid python3... But not always.
In a way that's already available today with -Wall, Wextra -Weverything -Wevenmoreeverything -Wallforrealthistime, clang-tidy, MISRAC and various other static analyzers.
I keep encountering backwards incompatibility between various flavors of Py3. It's a mess and if you have a large codebase, staying on top of the latest version is a nightmare. Or if you ship a module and want to support the diverse ecosystem, it's a nightmare.
> If your codebase is not usable anymore because your language evolved. That's a failure.
> I can still compile the C's from the 80's today if I want to. Same for Fortran, Same with C++.
I couldn't disagree more. It's a cute trick but not useful that code from 80's is still compilable with a compiler from 40 years later.
There's so many better ways to handle legacy like that, like just freezing the toolchain. Which you've almost certainly long since done if you're actually working with code that old, as the platform it runs on is also frozen.
You can't actually run code from 80's out of the box today (16-bit legacy support has long since stopped being a thing), so why does it matter if it still compiles? And, critically, why does it matter if it compiles with the latest toolchain?
There needs to be a supported sliding window of a generously long time, but it doesn't need to be ~infinite support. That's entirely unreasonable & unnecessary. We don't expect that of any OS, library, etc... so why should we expect it from a compiler & language?
> I couldn't disagree more. It's a cute trick but not useful that code from 80's is still compilable with a compiler from 40 years later.
Still a good number of packages you are using right now on your Linux distribution have a core more than 15-20 years old and still they compile only with minor modifications with the last GCC.
Compatibility does matter. It should however not prevent evolution. If handle properly, nothing block us to get a proper versioning and evolution path on the toolchain we use.
And on many aspect C++ up to now has been very successful to do that.
> You can't actually run code from 80's out of the box today
Good C is close to immortal. Doom is from 93, and still you can compile it and run it with the last compiler in 2020 with minor modifications.
Of course compatibility matters but as you noted (twice!) it's not perfect and doesn't need to be. Minor modifications are necessary to keep up. That's all that's being proposed by the paper.
So now we're just debating the size of ongoing breakages instead of just their existence.
And no good C is not close to immortal. Good luck compiling K&R C with modern compilers, for example.
> You can't actually run code from 80's out of the box today (16-bit legacy support has long since stopped being a thing)
If you wrote your 1980s C code:
1. Using the proper C standard.
2. Using the standard library (and perhaps even common Unix libraries).
3. Were careful not to make assumptions about sizes (which, by the language standard, you shouldn't make anyway).
The your code will compile and work just fine. If you used Makefile's to build it, which you very well may have, and you were using a standard (= Unix) system, there's not a bad chance your build system might work too. :-)
> If your codebase is not usable anymore because your language evolved. That's a failure.
Only if it claimed to be eternally BW compatible. Some languages never made that claim.
To me a language is a dependency like any API, just with a much bigger surface touching my code. I'd say it's the hardest API to replace in any code. "BW compatibility" is just as much a feature as "breaking BW compatibility", I just like languages (or any API really) to be upfront about it's policy toward BW compatibility.
And I've observed a lot of langs change their attitude towards it over time. Especially considering pre-1.0 times, I find a lot of langs happily break BW compatibility in their early days.
GPs point is that strings are handled differently in between versions. Essentially, Py3 introduced two types (text, bytes) where only one existed before (str). You can't preserve the behavior of the old type that magically coerced between them if the new apis expect different types.
> Then you'd just repeat the Perl 5/Raku split. You'd have a "new" C++ that isn't actually C++ but it's called C++ with a different version number and people will then bicker for a few years until they realize that the "new" C++ isn't actually C++ and that the "old" C++ just vehemently refuses to die, so they name it something else.
Well, just FYI the "new" C++ is not the "old" C++ anymore. Languages "evolve". Try to compile an old program on the new compiler. The same is valid for other languages (C, fortran, perl).
> The C++ standards committee can standardize a backwards-incompatible "new" C++ for all I care as long as they don't call it C++.
They already do this. For C for example you have C89, C93, etc.
Huh? Old programs on new compiler work with very minor changes and often with no changes at all. You might have to tweak some compiler flags, but this is per file. Your project can easily mix up C++ versions.
Same for C - that old library in C89? Still usable in modern projects.
Just checked on gotbolt.com - clangg, icc, msvc all compile gets just fine. They also work with k&r style definitions as well.
I am sure there are broken C compilers out there which don’t implement entire language. Heck, I use one sometimes. But I don’t think this proves your point at all.
No, those compilers would likely reject it, if I had activated c17 mode. But I did not -- so it worked.
This is a real power of C and C++ -- each file gets its own mode. That K&R library you made in ancient time? C++98 classes? All still works and links to the modern stuff. You can be sure that whatever code you wrote today would still be usable, possibly with minor modifications, 20 years later. Sure, the compiler names and build systems will change, but the code would work.
It'll most likely be abandoned in droves. One of the main reasons to stick with C++ (and C) is that the investment you put in the code you're writing right now, no matter how fugly it is, will be there and keep working in the future.
Also:
> I think it also makes sense for C++ developers whose projects are relatively short-lived, e.g. some games.
There is a difference between games and engines - the games might be short lived, but most C++ engines are very long lived, going back decades (e.g. see how the recent Half-Life Alyx can trace its codebase back to Quake 1 or pretty much anything based on the Unreal Engine going back to the mid-90s - there are screenshots of UE1 running on what appears to be either Windows 3.1 or NT 3.51). Even when "engines" are being made "from scratch" this is often a marketing gimmick and the "from scratch" was just renaming the engine because it got a new rendering model and people can't tell the difference. EVEN when this isn't case, often new engines are based on existing code and/or libraries.
> but most C++ engines are very long lived, going back decades
While true the engines are also staffed and can absolutely keep up with changes. They regularly are tasked with new toolchains, platforms, OSes, and APIs to leverage already. This would just be more of the same, and if you look at the goals of the paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p213...
That lines up pretty damn well to what game engines want as well.
> One of the main reasons to stick with C++ (and C) is that the investment you put in the code you're writing right now, no matter how fugly it is, will be there and keep working in the future.
The things they talk about actually abandoning are things like byte sizes other than 8-bits or source code in file systems that don’t support file extensions or nested directories.
Chances are any existing C++ codebase is already not compatible with such systems that they want to more formally deprecate/abandon in the first place.
You are talking about section 1.8. I agree, it is not that scary. What is scary, however, are sections 2.1 and 2.2. They contain stuff like this:
> Our experience is that providing broad ABI-level stability for high-level constructs is a significant and permanent burden on their design. It becomes an impediment to evolution, which is one of our stated goals.
This does not name it, but doesn't this refers to all those discussions about changing internal representation of std::string, std::vector and so on?
This does sound pretty scary. Once you have separate classes for "C++23 std::string" and "older std::string", you can no longer link together files built with different versions.. and this means you lose a lot past compatibility.
> you can no longer link together files built with different versions
True but this is already something that isn't really "supported" broadly. Some standard libraries work hard to keep that working, but others tend not to, and nobody knows if it's supposed to work reliably or not. And support for this binary compatibility also varies across platforms, like I don't think Microsoft bothers since you're supposed to always bundle a specific version of the runtime with your binary.
Single-version prebuilt libraries become more of a problem, but are those that widespread vs. source-based libraries?
But I think the broader point here that the paper is really pushing is for the committee to actually make a statement on things like this instead of their historically vague "it sort of works but we're not saying it will or indeed saying anything at all"
Agreed, I think looking at the Python 2 and 3 migration catastrophe gives a glimpse into what this could look like, but I imagine it would be much worse given the types of (large) projects that are backed by substantially "dated" C/C++ code.
You could do so much better than the python 2 to 3 transition.
If the python 3 interpreter could still run python 2 code. If you could mix and match python 2 and 3 code on a per-module, per-file or even per file basis, then the transition could have been so much smoother.
- the languages were not that different, so no need to learn the new version
- python core devs gave 10 years to make the transition. Then extended it.
- it was possible to write code running on python 2 and 3
- python is very expressive, hence the code base have way less numbers of lines than in C++
- python cared only about one implementation, Cpython. The rest of the world needed to follow. Some didn't, like Jython and stackless, and the community didn't blink.
Despite all that, the transition was very painful.
I've been coding in Python for 15 years, and from what I see in the numerous places I get to work, yes, it worked.
It's just that aghast people are the most vocal. You don't hear the 90% of people that are happy. They don't take the time to speak up. But the unhappy complain all the time.
I’m not speaking about developers complaining, I’m speaking about the fact that it’s still a fractured ecosystem and plenty of popular programs, packages, and scripts still in use or even still actively developed rely on or integrate with Python 2 (exclusively).
> I’m not speaking about developers complaining, I’m speaking about the fact that it’s still a fractured ecosystem and plenty of popular programs, packages, and scripts still in use or even still actively developed rely on or integrate with Python 2 (exclusively).
Such as? What actively developed tools or ecosystems rely or integrate with python2 exclusively?
I'm several years into the "upgrade" and find myself still swearing daily at the idiocy of the whole thing. Hundreds of scripts used maybe once a year, such as 'dups.py' I tried to run today, broken by a missing parenthesis, and a function moved around.
Utterly pointless and reputationally ruinous. I don't do serious work in Python any more
I recognize it may be difficult to understand, but this is a thread about backwards compatibility. Of course if I had any faith in the contemporary Python community, I would still be treating Python as a serious programming language and not be suggesting this is a difficult concept for someone to grasp.
There's really not much to it, but if it's a foreign concept it may require a kind of conceptual leap. Essentially, this code worked perfectly well, and suddenly it no longer worked. From your perspective, as you quite effortlessly put it, this code is broken. For others elsewhere in more conservative parts of the world, it was not my code that broke, but the surrounding ecosystem.
There is a fabulously unending depth to explore in the chasm lying between these opposing world views. It would be more than possible to write a book on the topic and fail to cover it all, however here are some of the most important aspects, from my perspective at least:
* given a perfectly functional tool relied on heavily by its user to perform their job, and given that tool suddenly decides to change shape such that it no longer fits the user's hand without retraining, nor fits with the remainder of the user's toolset, including custom tools the user has invested in producing, the continued utility of the no-longer-functioning tool is called into question, along with a deserved reappraisal of the tool's applicability in the context of the user's original intended problem domain.
* when the reason for its reshaping is to solve what are highly important problems from the perspective of the tool, but much less so from the perspective of the average user, and that user's application of the tool to real world problems, it can no longer be said that the tool is simply an implement that may be called and relied upon at any point in future -- the tool develops a chaotic life and importance all of its own, and may choose to reshape once again at any future moment (and indeed in this case it has). It is no longer a tool, but some sentient entity demanding unpredictable ongoing costs and attention paid all of its own.
* given a tool that promises to cease functioning 'correctly' at any future moment based on its own whim, preferences, industry fashions and styles, in an ecosystem where many similar such tools exist that explicitly promise not to cease functioning over the same time period, it is a fool's errand to pick the tool that promises to externalize additional costs on the user when alternatives exist that avoid any such cost.
* given tool designers who externalize almost frivolously minor technical costs on to every user, where each 'minor' change is amplified perhaps 10000 times over and directly translates into expensive engineering time, the question is easily raised whether the philosophy of the tool is appropriate for its advertised utility, and whether continued reliance on the tool makes business sense. In economic terms, what was the cost to productivity of the retraining and re-tooling of users compared to any alleged future productivity improvement?
* had I written these scripts in bash, C# or C++, they would not have broken even remotely to the same degree. Of course these are not some completely unevolving entities either, however all take the promise of forwards compatibility deadly seriously, and it is more than possible to find 10-20 year old programs written in C++ or bash that continue functioning to the present day. From my perspective, they are therefore excellent and highly dependable tools.
I think in your rush to appear superior, you missed the much simpler explanation. Your first comment was easy to misunderstand:
> print vs print() is what I think the parent comment was referring to.
as another person mentioned is likely what you meant, but it was easy to take your comment as meaning that you had mismatched parens somewhere, which would imply broken python2, as well as 3.
> * given a tool that promises to cease functioning 'correctly' at any future moment based on its own whim, preferences, industry fashions and styles, in an ecosystem where many similar such tools exist that explicitly promise not to cease functioning over the same time period, it is a fool's errand to pick the tool that promises to externalize additional costs on the user when alternatives exist that avoid any such cost.
This is clearly a falsehood, if the tool provides additional value. To bring things back to the topic at hand, if C++ were allowed to break ABI compatibility in very specific ways, it could be faster. stl container types are slower than many third party ones (absl, for example).
Which is to say that if you want "the best" that C++ has to offer, you have to be willing to have your libraries make backwards incompatible changes.
To jump back to python,
> given tool designers who externalize almost frivolously minor technical costs on to every user, where each 'minor' change is amplified perhaps 10000 times over and directly translates into expensive engineering time
I disagree that this happened. The examples you give are trivially fixable with 2to3. There are harder problems that 2to3 doesn't solve, but it sounds like you don't have any, so the frivolously minor technical costs are frivolously minor, and translates into running `2to3 my_code/` one time, to fix all the missing parens and most of the moved functinos.
> bash that continue functioning to the present day
I have yet to encounter a 20 year old program written in bash that functions to this day. It might be syntactically valid, but it won't do what it intended to do.
I mistakenly thought you wanted clarity on my reasoning, instead it seems I've been made a fool of by providing an opportunity for you to argue a position I already understood and couldn't care less about. It's funny, this is also pretty much the reason I stopped relying on Python.
You're welcome to absorb all the externalized costs your heart desires, but in future please consider reviewing HN's rules before bandying attacks like "smug" and "superior".
Understanding ones views doesn't imply agreeing with them. A tool is only useful if, well, it's useful. A slow C++ is less useful than a fast one. So how is it that you can claim so strongly that the value from backwards compatibility is greater than the value from speed by default?
Bluntly, if I could break the ABI to get a 10% speed boost across the board, is that not worth it?
And I'm well aware of the site guidelines. I certainly don't think asking someone to tone down the holier-than-thou in their comments is a violation of them. Just the opposite, it's encouraged. So I'll continue to ask that you do so if you choose to respond.
The Unicode issue is one of the most painful sticking points. Handle legacy filesystems with the chance of filesystems that have previously valid (non / non \0 containing filenames) files in various encoding soup nightmares? Python3 is NOT the the tool for that job! Which means you can't write any systems tool stuff in Python3 because there's a good chance it'll blow up unexpectedly, or that you suddenly have to handle all sorts of things that in any other language you can just GIGO and move on.
Python 3 promotes the most likely scenario: you are on a modern system with normal looking filenames.
It would be unwise to design an API that promotes a niche need like dealing with a legacy file system with corrupted file names. This is what Python 3 fixed, making the easy things easy, and the complicated things possible, not the other way around.
But Python 3 is absolutely up to the task of handling legacy file systems with random encoding mixed in, you just need to tell it explicitly you are doing so.
Let's create a file with a completely garbage name, made of random bytes, which is allowed on Unix:
>>> import os, sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=5, releaselevel='final', serial=0)
>>> with open(os.urandom(32), 'wb') as f:
... f.write(os.urandom(200))
If you pass bytes to any file system function, it will return file names as bytes:
You do exactly the same as what you did with Python 2, and treat the files as raw bytes entirely, without thinking about the content.
Some API in Python require text. If you want to pass the file names to those API, you can use surrogate escape, which let you convert back and forth between arbitrary bytes and utf8 text, without loosing information:
This is a good thing, it forces the dev to be explicit about the places in your code where you are dealing with a specific scenario. It also makes you pay the price of doing so opt in, not opt out.
If you have to do a robust version of this with Python 2, you will have to do that anyway: at some point mixed encoding will bite you if you don't have a neutral representation for them. Python 2 gave you the illusion of robustness, because it said "yes" to most operations.
I remember quite well that a lot of Python 2 programs didn't work in Europe because your user directory would contain your name, which could be non ascii. Python 2 programs are opt in to deal with it. It's the opposite philosophy, and caused so many crashes.
I failed to mention that it's only useful to do this manually if you need to decode path with mixed encodings, or share said path with the rest of the world.
If all you need is to work with arbitrary mixed bags of file names, you can just pass "str", and Python will use automatically and transparently surrogateescape everywhere.
The first versions of python 3 were indeed, not suitable for serious work. Python 3 started to be usable from 3.3, interesting from 3.4, comfortable from 3.5, and objectively way better than 2.7 from 3.6.
It's not a surprise, as most softwares need iterations to get get good. Python 2.7 has not started as the amazing tool it is now, and as I started my career with 2.4, you remember some funny stuff.
This is why Python 2.7 was kept around for 13 years after Python 3 first came out.
Now, 3.6 came out in 2016. It solves many issues 2.7 had, and add tons of goodies. It's very ergonomic, can be installed easily. It's a great software.
No one disputes that today's Python 3 is better that today's Python 2, especially for new projects. (A point can be made that Python 2 now is perfectly frozen in time and so 100% stable, but most people do not care that much)
The only thing this means is that the botched upgrade did not end up killing Python; it says nothing about whether it was done badly or not.
(I have nothing against python, I just believe it is important to understand why and how what happened happened to avoid similar errors in the future)
> python is very expressive, hence the code base have way less numbers of lines than in C++
This doesn't match reality. In reality, many Python projects have way more lines of code than equivalent C++ projects. Probably because of the 'expressiveness' you cite; you can't really showcase your love of coding and job security though artificial complexity without 'expressive' bells and whistles. (This is the idea that lead to languages like Go, I'm pretty sure.)
> many Python projects have way more lines of code than equivalent C++ projects.
That is mathematically impossible. Even if the syntaxes were exactly the same (which they are not, python syntax is on average shorter), the low level nature of C++ requires your code to do operations that Python does need to do, such as memory management.
Once you've written sufficient unit tests to prove your code works as well as a C++ equivalent does just by compiling, I don't think Python ends up being more dense though.
I'd also argue that operator overloading etc lets C++ be just as expressive as Python, the libraries just need to be designed with that in mind.
This is a 1rst year of college exercise. Nothing remotely complicated. I'm not choosing some fancy machine learning or data processing stuff for which Python has magic libs. Every language can do that easily.
In Python 3.7, which is already 2 years old, the code would be:
import json
import datetime as dt
with open("agenda.json") as fd:
agenda = sorted(json.load(fd), key=lambda people: people["name"])
for people in agenda:
hired = dt.datetime.fromisoformat(people["hired"])
print(f'{people["name"]} ({people["age"]}) - {hired:%d/%m/%y}:')
for email in people["emails"]:
print(f" - {email}")
The entire script is there. There is no trick. This is not a code golf version of of it; I could make it shorter. It really is standard Python. There is no 3rd party lib either.
It's not specific to Python, you would get this expressiveness with Ruby or Perl.
I don't see in which world you would get that in regular, honest to god, day to day, portable C++.
You have to declare types, many includes, you'll have headers and a main function. You have the memory and references to manage.
It doesn't make C++ a bad language.
It doesn't make python a better language.
The C++ version will take way less RAM than the Python version for example.
It's just the nature of those languages implies that.
For fun, I whipped this up in Rust. I decided to go with an explicit struct to serialize it into, because it makes the error handling easier, and is a bit more idiomatic. I kept unwrap because it's similar to the python semantic of throwing an exception. It's pretty close though!
use chrono::NaiveDateTime;
use serde::*;
use serde_json;
#[derive(Deserialize)]
struct Person {
name: String,
age: u32,
hired: String,
emails: Vec<String>,
}
fn main() {
let data = std::fs::read_to_string("agenda.json").unwrap();
let mut people: Vec<Person> = serde_json::from_str(data).unwrap();
people.sort_by(|a, b| b.name.cmp(&a.name));
for person in &people {
let datetime = NaiveDateTime::parse_from_str(&person.hired, "%Y-%m-%d %H:%M:%S").unwrap();
println!(
"{} ({}) - {}",
person.name,
person.age,
datetime.format("%d/%m/%y")
);
for email in &person.emails {
println!(" - {}", email);
}
}
}
> many Python projects have way more lines of code than equivalent C++ projects
To:
> It's a bit longer in C++ but frankly not by that much
With the proof being literally __twice__ more characters, with one line being more than a 100 characters long.
I understand that C++ doesn't have a native JSON lib, and sorting is a bit out there, but giving 3rd party lib access to Python feels like cheating:
import pandas as pd
agenda = pd.read_json("agenda.json", convert_dates=["hired"])
for _, (name, age, hired, emails) in agenda.sort_values("name").iterrows():
print(f"{name} ({age}) - {hired:%d/%m/%y}:")
for email in emails:
print(f" - {email}")
I mean, I can also create a lib that does the entire script, install it, and just do 'import answernh; answerhn.print_json("agenda.json")'
Now again, this is not so say "Python is better than C++". That's not the point.
If you want to write a game engine, C++ makes sense, verbosity doesn't matter, and you don't want the Python GC to kick in. If you want to iterate on your Saas product API quickly, it's probably not the best choice.
Why pretend otherwise? What the point of stating the sky is red?
To be fair to the C++ version, you need to give as many guarantees from your code as C++ does from compiling. To me, in this case this is mainly the output formatting. Python won't tell you until runtime that you have a malformatted string, so add in some unit tests to make sure your string formatting won't crash, and you'll be at a similar number of lines of code as the C++ or Rust version.
> Python won't tell you until runtime that you have a malformatted string
Neither will C++, as far as I know, or do you mean that the compiler will validate the formatting of the string literal (python has linters that will validate this too!).
Hell, even with statically verifiable types, the python is shorter.
> Neither will C++, as far as I know, or do you mean that the compiler will validate the formatting of the string literal.
With iostreams there are no format strings so there is nothing to validate. Libraries such as https://github.com/fmtlib/fmt are allow to do compile-time parsing of the string though.
I think it shows quite well that the expressiveness difference is not as a large as many would think.
Also, the difference in tone towards jcelerier compared to steveklabnik seems not warranted here. Both IMHO contributed an educational example. Your response here equally does (or does not) apply to the Rust version, doesn't it?
Rust is fantastically expressive for a low level language.
And modern C++ is not 15 years old C++. You have auto, lambda, etc. I just think it's silly to try to pretend to be more expressive than scripting languages.
It's like Schwarzenegger trying to pretend it's as flexible as gymnast. What for? The guy is amazing at what he does.
> I just think it's silly to try to pretend to be more expressive than scripting languages.
So far we have a Python, a Rust and a C++ program in this thread which output the same thing given the same input, which, by the definition of expressive power of programming languages means that they are exactly as expressive as each other (given this information only and notwithstanding small differences such as error handling in this case).
Also you mentioned in your original thread :
> Even if the syntaxes were exactly the same (which they are not, python syntax is on average shorter)
indeed
> the low level nature of C++ requires your code to do operations that Python does need to do, such as memory management.
Notice that there is nothing which looks like memory management in my code. There are entire classes of programs that can be written without thinking about when to allocate or free your memory at all - just declare your variables on the stack and with std types, and that is handled for you (and that's not a modern C++ thing ! it's been like that since the very beginning). Opting in to memory management is an active, conscious effort in these cases (or a residue of people who only were taught java / c# at school and sprinkle their code with `new`).
I think it does, I should not require an external dependency for JSON. Having standard JSON support is far far more useful than another micro optimizations.
Eventual deprecation is not a valid justification for making current technology less useful.
Now I would buy your argument if C++ were as conservative as C, but it clearly isn’t. It keeps trying to push itself as a modern general purpose language, while ignoring what it actually takes to be useful as one.
The original post was about 'projects', not code snippets.
If you've done any serious (large, long, multi-team) projects in a dynamically-typed language, you know how quickly they turn into a big ball of mud where you're spending more time refactoring your refactorings than writing useful code.
I agree about the "rocky transition" from Python 2 to 3. Still if the Python migration story gives any inference towards the outcome of an eventual C++ transition, the new language would be an even more massive success; it seems that Python 3.x is doing alright right?
That is somewhere in the logical region of saying 'Stephen Hawking was really smart, maybe amyotrophic lateral sclerosis is a good thing'. The transition to Python 3 was not pretty. Still isn't; Python 2 documentation features prominently when I'm trying to look up information.
The to abandon backwards compatibility in C++ is to make a mockery even of the name of the language (see the "C" in there). If they want to create a new language they should call it something different. Willfully abandoning backwards compatibility and keeping the name is an abuse of one of the great brands in software.
I was just trying to get a response to how the Python transition mentioned was so (edit:) "catastrophic". I don't understand the need to bring fiction writers, fatal diseases or laden words like "mockery" or "abuse" to the debate.
Yeah, I think Python 2 has been successfully killed at this point. There are a decent number of projects which still need to migrate (and I recently became responsible for a couple) but it’s been a long time since I heard anyone claim they can keep Python 2 forever. The maintenance burden of keeping Python 2 is rapidly increasing as libraires drop support.
Since python3 has many more users than python2 this is not a relevant comparison. It was not smooth, but now it’s done and it worked. There have been many C++ forks which have failed to replace it, e.g D, Rust, ...
> One of the main reasons to stick with C++ (and C) is that the investment you put in the code you're writing right now, no matter how fugly it is, will be there and keep working in the future.
Except slowly people will be writing parts of it in Rust, or they would be using libraries written in Rust (because availability), and then after a while people get annoyed of using more than one language and the whole project will be rewritten in Rust. Of course, at that point, a new language will be invented and Rust code will be looked at as rusty :)
Rewriting parts in Rust would require Rust to have good C++ interop, which it doesn't. At all. Rust has good C interop, so if your code is already divided into C-abi shared libraries then you're golden. But if it's not then Rust migration really isn't an option.
And realistically I'm not entirely sure how you can add C++ interop to Rust without also ruining Rust. It's a hard problem to say the least.
Half Life Alyx is built on Source 2, which was extended from Source 1, which is a heavily modified version of GoldSrc which was based on the Quake 1 engine.
If C++ abandoned backward compatibility, then this C+++ would compete with modern languages like D, Rust and it's absolutely not clear what the winner would be. As long as C++ is backwards compatible, it have tremendous advantage of having billions of LoC which don't have to be rewritten.
Some here are saying "Why not just use Rust/D/Zig/Nim/Crystal if you're going to break backwards compatibility?" I believe the proposal is closer to "We've removed this old feature, run this tool to replace it with the new one." C++ will keep looking like C++, not like Rust. Here's Google's "Codebase Cultivator" explaining the idea for their Abseil library:
Per Hyrum's law, someone will rely on any given behavior. So, if you choose backwards compatibility, it becomes harder for the committee to evolve the language over time.
The path of breaking changes can be made less painful, and does not, necessarily, invalidate all the code that is already written.
With that being said, the problem right now is not the decision of keeping backwards compatibility or not, but the fact that the standard should be explicit about it, so people know what to expect.
Yeah there's a lot of discussion here as if the proposal is some radical breaking thing when in reality it's stupid stuff like "stop pretending a byte isn't always 8-bits" or "if you're a platform that's still shipping a 10 year old compiler, you're not supported anymore in the latest language version." Problems that you'd never even dream of in most other languages.
Because C++ is not a single implementation language, rather driven by a standard with multiple implementations.
Such a solution is only possible if driven by the standard, otherwise it will never be fully available, just like the static analysis tooling varies by vendor.
Unless you manage to integrate this directly into the compiler stack and have it work 100% of the time with no "ifs" or "buts" I don't think it'll work.
Maybe you could do like Rust with their "Epoch" system that lets you interoperate code using various standards in order to migrate progressively without breaking backward compatibility. I suspect that it would be a lot harder to make it work for C++ however, mainly due to its extreme reliance on #includes (especially for anything using templates) and more common use of macros.
I'm not saying it's impossible but I suspect that it would fragment the ecosystem quite a bit. Removing "old features" tends to have massive side effects in a language like C++ with metaprograming, overloading, multiple inheritance, unlimited macro usage and complex symbol resolution rules.
So I think "why not just use Rust/D/Zig/Nim/Crystal" is warranted feedback for these proposals (and you could probably add Go, C#, Java and a few others).
Sometimes I wish Google would just stop participating in open source and standardization. The proposal actually suggests supporting Fuschia at the language level, but not any of the BSDs, and replacing the ABI with protobuf (or similar).
Much of this proposal would create insurmountable engineering problems for everything except the Google monorepo, or similar infrastructure at $1T companies building monoliths.
Historically, most innovation has happened outside of those environments.
Sabotaging the toolchains of smaller shops is not going to pan out well for the industry at large.
They list an "initial non-exhaustive list of platforms" which includes "OS/kernel." I don't think it's fair to say that they are excluding the BSDs even if they aren't explicitly mentioned. The point is that new C++ shouldn't need to support old and historical platforms.
Many people don't realize just how conservative C++ is (the OP only briefly mentions this). The committee not only avoids breaking existing source code, but even binaries, although binary compatibility is not defined in the standard at all.
C++ 11, 14, 17, 20, and 23 are all binary compatible (modulo some possible oversights). There are many quality-of-life improvements that got voted out because then a binary compiled in C++11 wouldn't be compatible with one built in C++23 anymore.
As a simple example ([1] has many more): the only reason we have both std::scoped_lock and std::lock_guard is because changing the definition of the latter would have required re-compilation of existing code, so a new class had to be introduced.
ABI stability even overrides performance in committee decisions (see the std::regex example, but I also heard it affects the footprint of unique_ptr - edit: found it [2]).
tl;dr breaking source code compatibility seems way off if implementors don't even dare to break binary compatibility.
There would then be two C++ languages. The overwhelming majority of users would stick with the old one, a scant few would take up the new one, and the rest would move on to some other language.
I'm currently working on a C++ project that has been taken out of cold storage and when the old rewrite question pops up the tech stacks considered for the job do not feature C++ at all. The main reason is that nowadays there are better tools and frameworks to implement non-performance critical parts of an application such as the GUI, which incidental takes over more than half the total lines of code, and the rest can be implemented easily with small support services developed in whichever tech stack you choose. Thus C++'s "jack of all trades" trait is no longer a major factor in the decision. Meanwhile the lack of a stable ABI starts to feature prominently as a reason not to adopt C++.
Most of the pressure to break compatibility comes from Microsoft and Google, representing a truly minuscule fraction of C++ users. However, both are heavily represented on the ISO Standard committee, and very, very little happens there without their express approval.
Introducing a new ABI in competition with the current ones (e.g. ia32 and x64) would be less destructive than officially abandoning those currently in use.
For this to work you would need stable bridge for calling between old and new version, and with c++ lacking stable ABI and high dependency to header-template interfaces (which is part of the bad c++ one would want deprecated), this could be an issue. Sure, extern C already exists but it's a leaky abstraction and it won't be added to existing libraries overnight. Without this you can look at python 2 to 3 migration to see what will happen.
Thankfully, GC offers productivity and allows for safe code by default, while D still offers the necessary language features to do all the low level tricks from C, C++, Rust, Objective-C, if one actually needs to go down that level.
I like being able to write safe and GC-free code by default. Rust would be even better if it was possible to opt into GC, but I would still want to to be GC-free by default. It's good that the entire library ecosystem has grown up GC-free and that should continue.
If I hadn’t already embraced C# for that sort of thing, I’d probably have turned to D years ago. Between C# and Rust, I rarely find myself needing other tools aside from Makefiles and shell scripts.
I’ll never understand the portion of the C++ community that insists on modernizing the language to the detriment of everything else. When is enough enough? Just let the language be and focus on improving the standard library, not changing language features.
You are missing the point. Almost all of this has to do with the binary compatibility of the std library, not the core language. Things like unordered_map/map cannot be improved because the binaries would not longer be compatible. lock_guard could not go from one mutex to one or more because it would have a different name in the symbol table for some compilers, so we get a new scoped_lock that adds another name to do the same thing. push_back on vector cannot return a reference as does emplace_back for similar reasons too. All of those would leave the source code compatible, but break linking, often silently.
These are some of the ones off the top of my head, there are more. So the fix in these cases is another name, and that has the problem of making the language needlessly more complicated and harder to learn.
So, the author wants something between FORTRAN's Ratfor and F. I can see it as a supplement to the C++ standard but not being the actual standard. It would make people distrust C++ greatly if implemented as the standard.
I don’t see how you can draw this conclusion. Taking C++ and breaking backward comparability just means you can move the standard for C++ foreword without concern for supporting 10+ year old source code. I cannot see a reason this would impose some stringent, immutable by default ownership/borrowing requirement, a radically different type system, a lose of template meta programming for macros (in the case of Rust) or the adding of garbage collection and an invasive runtime with a language imposed concurrency model to the exclusion of others (in the case of Go).
Sure as time passed, the non backward compatible C++ would add features not currently in scope for C++, but the radical redesign of the fundamental language is not what this proposal suggests.
Those two languages were created without consideration of backwards compatibility with C/C++ source code and were designed for the same job. It stands to reason that given no constraints of compatibility, you'd get something similar to those languages.
Now obviously, those languages do not have all the features of C++ nor the same design principles. But as soon as you have all the features of C++ with the same principles, you have something so close to C++, that it doesn't make sense to break backwards compatibility.
I think the concept would look more like Rust. It is still compiled to machine code, not VM opcode. You do not need a VM and do not require a garbage collector, JIT, etc. which makes it suitable for more use cases.
But: if it's a completely new language (no backward compatibility to C++) - why do we need this? Can't we just use one of those new languages that are already available (Rust, Zig, Odin, Nim, just to name a few).
The rational argument for what is being proposed is that companies with (total) hundreds of millions of lines of C++ code can migrate it incrementally and (hopefully) mostly mechanically as the language evolves ... which should be much cheaper and less risky than porting code to a new language.
There are probably also strong non-rational components, e.g. C++ people would like to keep working on C++, leveraging their existing skills and status, rather than jump to another community.
Those languages are all pretty different from C++, right? I'm assuming the new C++ would be very similar to old C++ with a bunch of things "considered harmful" removed.
Well I’ve seen at least three recent CppCon talks where they say that ‘for’ loops are harmful and a template function should be used always, so I would hope there would not be a removal of these things, just a non-concern with requiring that changes don’t break the compilation of 10+ year old code.
(btw., since debate initiated, C++ standard attempts on garbage collection too - as well as on many powerful concepts incorporated into C# from here and there - and IL is compiled into machine code before execution, naturally, also making C# really had an intention of making a better but similar C/C++ without its crippling compatibility barriers towards improvements. C++ is more universal still - but considerably less than C - and it is all right, each have advantage here and there. You usually do not work on all use cases anyway! ;) No need to abandon backward compatibility with C++, abandoning will become a new language, we have quite a few already, it is done already!)
Well we'll have to maintain the old version. It'd be simpler to keep on calling it "C++," but then we need a catchier name for "new C++." How about "Rust?"
The C++ standards committee can standardize a backwards-incompatible "new" C++ for all I care as long as they don't call it C++.