> One of the concerns is that C and C++ are being discouraged for new projects by several branches of the US government[1], which makes memory safety important to address.
The biggest memory safety problem for C is array overflows. I proposed a simple, backwards compatible change to C years ago, and it has received zero traction. Note that we have 20 years of experience in D of how well it works.
P.S. Modules would also make a big improvement to C, proved by implementing them in the ImportC C compiler. Modules don't take away anything from C's utility.
But that change is by definition not backwards compatible - neither ABI nor source level.
Using an ifdef to maintain source level compatibility doesn't work as two pieces of code will see the same function using different ABIs.
That said I agree entirely - the conflation of array and pointer is the biggest flaw, it's what "necessitated" the null termination error that people are so fond of calling the biggest mistake.
Fair enough - but I also get the point that it's absurd that "modern" "safe" C++ is invariably more obnoxious and verbose than the unsafe version.
Then when new syntax _is_ added, there is no attempt to fix anything. My favorite example is the iterator syntax:
for (thing: things) ...
Being defined as equivalent to
for (iter = things.begin(), end = things.end(); iter != end; ++iter) { thing = *iter; ... }
Which is great because it makes any kind of range checking or reallocation safe enumeration much slower. All just to allow the spec to avoid making changes to existing "idiomatic" begin()/end() based code.
So prior to this syntax I had made the WebKit Vector class perform bounds checking on any indexed access, in all build modes. So enumeration was generally indexed, because begin()/end() are atrocious, and that meant that enumeration was both bounds and reallocation safe. But then the enumeration syntax came along, specifically in terms of begin()/end() - and I was never able to make an iterator that could have the required semantics that didn't simply shit the perf bed.
So now you have a new "modern" C++ iterator that is strictly less safe than the old form of iteration, and for which a safe and secure iterator is intrinsically hard to optimize due to the semantics of the rest of the language. Hurrah.
Which only do bounds checking in debug builds, or when explicitly use the compiler switches to enable them in release builds, good luck trying to advocate for using at() everywhere instead of braces.
Before C++ got the STL, all collections libraries shipped with compilers used to have bounds checking enabled by default, apparently that is too much performance loss for the standard library.
Walter's proposal added bounds checking being used unless explicitly disabled, like in any sane systems language.
As experience has shown, bounds checking is needed in the release builds, because those array overflows are only discovered by hackers in the released software.
D compilers allow that to be turned off, but it's only appropriate when:
1. evaluating how much the checking costs in runtime performance
Exactly, as any sane systems programming language. :)
I always force enable bounds checking on C++ code, never had a performance issue where the real culprit wasn't something else, wrong algorithm or data structure for the problem at hand.
Nobody has ever accused C++ of having good defaults. That is also unfortunately one of the hardest things possible to change without language-fragmentating mega breakages (see Python3)
I think the core issue here is that WG21 seems hell bent on refusing any new syntax if at all possible, and instead requires "generic" solutions that can be used for many problems. The result of which is these unending train wreck solutions for what should be basic, and we end up with enable_if and SFINAE. At least C++ finally has the idea of specifying an interface for a template instantiation, except of course it's absurd in its own way. Rather than specifying an API, you write "code" and a type conforms to that concept if the code "compiles". So you have no way to say "I expect this type to conform to this concept" other than using it.
The primary goal of "concepts" appears to be to mitigate the awful error messages, but even for that it fails.
For example, let's imagine Hashable from any other language, and look at a C++ concept version:
template <typename T>
concept Equatable = requires (const T& a, const T& b) {
{ a == b } -> std::same_as<bool>;
};
template <typename T>
concept Hashable = Equatable<T> && requires (const T& a) {
{ a.hashCode() }->std::convertible_to<size_t>;
};
Now how do we make sure Thing is actually going to conform to Hashable? with an assert of course, why would we want anything so gauche as declarative syntax?
static_assert(Hashable<Thing>);
You can see the brilliant syntax explicitly disallows us specifying constraints on a concept, and instead we have to use the && expression. My personal belief here is that the banning of constraints in the template name is simply to force people to use logical composition so the people who thought of it can claim people like it.
Hardly. A std library update is often easier and faster to ship than a new compiler toolchain. Especially in C/C++ world, although it is moderately uniquely terrible in this regard
> 5. one construct instead of two (vector and span)
Incorrect. Vector is an owned type, while both span and your proposal are borrowed types.
> 6. overflow behavior selectable with compiler switch
That sounds an awful lot like a negative to me, not a feature. It's not generally desirable for code behavior to change depending on how it's compiled, makes debugging kinda hard.
Unless you're just referring to things like sanitizers, in which case yeah span & vector both have those same flags.
> It's not generally desirable for code behavior to change depending on how it's compiled, makes debugging kinda hard.
It only changes the behavior of what happens after the program has already failed. People want different behaviors, depending on their environment, and like having the choice.
D's slices "T[]" maps to C++'s span<T> -- you're right, though the semantics/APIs of each are slightly different you can treat them as approximate concepts.
The biggest memory safety problem for C is array overflows. I proposed a simple, backwards compatible change to C years ago, and it has received zero traction. Note that we have 20 years of experience in D of how well it works.
https://www.digitalmars.com/articles/C-biggest-mistake.html
It'd improve C++ as well.
I really do not understand why C adds other things, but not this, as this would engender an enormous improvement to C.